Line data Source code
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 "BroadcastChannel.h"
8 : #include "BroadcastChannelChild.h"
9 : #include "mozilla/dom/BroadcastChannelBinding.h"
10 : #include "mozilla/dom/Navigator.h"
11 : #include "mozilla/dom/File.h"
12 : #include "mozilla/dom/StructuredCloneHolder.h"
13 : #include "mozilla/dom/ipc/StructuredCloneData.h"
14 : #include "mozilla/ipc/BackgroundChild.h"
15 : #include "mozilla/ipc/BackgroundUtils.h"
16 : #include "mozilla/ipc/PBackgroundChild.h"
17 : #include "nsContentUtils.h"
18 : #include "WorkerPrivate.h"
19 : #include "WorkerRunnable.h"
20 :
21 : #include "nsIBFCacheEntry.h"
22 : #include "nsIDocument.h"
23 : #include "nsISupportsPrimitives.h"
24 :
25 : #ifdef XP_WIN
26 : #undef PostMessage
27 : #endif
28 :
29 : namespace mozilla {
30 :
31 : using namespace ipc;
32 :
33 : namespace dom {
34 :
35 : using namespace workers;
36 : using namespace ipc;
37 :
38 : class BroadcastChannelMessage final : public StructuredCloneDataNoTransfers
39 : {
40 : public:
41 0 : NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage)
42 :
43 0 : BroadcastChannelMessage()
44 0 : : StructuredCloneDataNoTransfers()
45 0 : {}
46 :
47 : private:
48 0 : ~BroadcastChannelMessage()
49 0 : {}
50 : };
51 :
52 : namespace {
53 :
54 : nsIPrincipal*
55 0 : GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate)
56 : {
57 0 : nsIPrincipal* principal = aWorkerPrivate->GetPrincipal();
58 0 : if (principal) {
59 0 : return principal;
60 : }
61 :
62 : // Walk up to our containing page
63 0 : WorkerPrivate* wp = aWorkerPrivate;
64 0 : while (wp->GetParent()) {
65 0 : wp = wp->GetParent();
66 : }
67 :
68 0 : return wp->GetPrincipal();
69 : }
70 :
71 0 : class InitializeRunnable final : public WorkerMainThreadRunnable
72 : {
73 : public:
74 0 : InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsACString& aOrigin,
75 : PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
76 0 : : WorkerMainThreadRunnable(aWorkerPrivate,
77 0 : NS_LITERAL_CSTRING("BroadcastChannel :: Initialize"))
78 0 : , mWorkerPrivate(GetCurrentThreadWorkerPrivate())
79 : , mOrigin(aOrigin)
80 : , mPrincipalInfo(aPrincipalInfo)
81 0 : , mRv(aRv)
82 : {
83 0 : MOZ_ASSERT(mWorkerPrivate);
84 0 : }
85 :
86 0 : bool MainThreadRun() override
87 : {
88 0 : MOZ_ASSERT(NS_IsMainThread());
89 :
90 0 : nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate);
91 0 : if (!principal) {
92 0 : mRv.Throw(NS_ERROR_FAILURE);
93 0 : return true;
94 : }
95 :
96 0 : mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo);
97 0 : if (NS_WARN_IF(mRv.Failed())) {
98 0 : return true;
99 : }
100 :
101 0 : mRv = principal->GetOrigin(mOrigin);
102 0 : if (NS_WARN_IF(mRv.Failed())) {
103 0 : return true;
104 : }
105 :
106 : // Walk up to our containing page
107 0 : WorkerPrivate* wp = mWorkerPrivate;
108 0 : while (wp->GetParent()) {
109 0 : wp = wp->GetParent();
110 : }
111 :
112 : // Window doesn't exist for some kind of workers (eg: SharedWorkers)
113 0 : nsPIDOMWindowInner* window = wp->GetWindow();
114 0 : if (!window) {
115 0 : return true;
116 : }
117 :
118 0 : return true;
119 : }
120 :
121 : private:
122 : WorkerPrivate* mWorkerPrivate;
123 : nsACString& mOrigin;
124 : PrincipalInfo& mPrincipalInfo;
125 : ErrorResult& mRv;
126 : };
127 :
128 : class BCPostMessageRunnable final : public nsIRunnable,
129 : public nsICancelableRunnable
130 : {
131 : public:
132 : NS_DECL_ISUPPORTS
133 :
134 0 : BCPostMessageRunnable(BroadcastChannelChild* aActor,
135 : BroadcastChannelMessage* aData)
136 0 : : mActor(aActor)
137 0 : , mData(aData)
138 : {
139 0 : MOZ_ASSERT(mActor);
140 0 : }
141 :
142 0 : NS_IMETHOD Run() override
143 : {
144 0 : MOZ_ASSERT(mActor);
145 0 : if (mActor->IsActorDestroyed()) {
146 0 : return NS_OK;
147 : }
148 :
149 0 : ClonedMessageData message;
150 0 : mData->BuildClonedMessageDataForBackgroundChild(mActor->Manager(), message);
151 0 : mActor->SendPostMessage(message);
152 0 : return NS_OK;
153 : }
154 :
155 0 : nsresult Cancel() override
156 : {
157 0 : mActor = nullptr;
158 0 : return NS_OK;
159 : }
160 :
161 : private:
162 0 : ~BCPostMessageRunnable() {}
163 :
164 : RefPtr<BroadcastChannelChild> mActor;
165 : RefPtr<BroadcastChannelMessage> mData;
166 : };
167 :
168 0 : NS_IMPL_ISUPPORTS(BCPostMessageRunnable, nsICancelableRunnable, nsIRunnable)
169 :
170 : class CloseRunnable final : public nsIRunnable,
171 : public nsICancelableRunnable
172 : {
173 : public:
174 : NS_DECL_ISUPPORTS
175 :
176 0 : explicit CloseRunnable(BroadcastChannel* aBC)
177 0 : : mBC(aBC)
178 : {
179 0 : MOZ_ASSERT(mBC);
180 0 : }
181 :
182 0 : NS_IMETHOD Run() override
183 : {
184 0 : mBC->Shutdown();
185 0 : return NS_OK;
186 : }
187 :
188 0 : nsresult Cancel() override
189 : {
190 0 : mBC = nullptr;
191 0 : return NS_OK;
192 : }
193 :
194 : private:
195 0 : ~CloseRunnable() {}
196 :
197 : RefPtr<BroadcastChannel> mBC;
198 : };
199 :
200 0 : NS_IMPL_ISUPPORTS(CloseRunnable, nsICancelableRunnable, nsIRunnable)
201 :
202 : class TeardownRunnable final : public nsIRunnable,
203 : public nsICancelableRunnable
204 : {
205 : public:
206 : NS_DECL_ISUPPORTS
207 :
208 0 : explicit TeardownRunnable(BroadcastChannelChild* aActor)
209 0 : : mActor(aActor)
210 : {
211 0 : MOZ_ASSERT(mActor);
212 0 : }
213 :
214 0 : NS_IMETHOD Run() override
215 : {
216 0 : MOZ_ASSERT(mActor);
217 0 : if (!mActor->IsActorDestroyed()) {
218 0 : mActor->SendClose();
219 : }
220 0 : return NS_OK;
221 : }
222 :
223 0 : nsresult Cancel() override
224 : {
225 0 : mActor = nullptr;
226 0 : return NS_OK;
227 : }
228 :
229 : private:
230 0 : ~TeardownRunnable() {}
231 :
232 : RefPtr<BroadcastChannelChild> mActor;
233 : };
234 :
235 0 : NS_IMPL_ISUPPORTS(TeardownRunnable, nsICancelableRunnable, nsIRunnable)
236 :
237 : class BroadcastChannelWorkerHolder final : public workers::WorkerHolder
238 : {
239 : BroadcastChannel* mChannel;
240 :
241 : public:
242 0 : explicit BroadcastChannelWorkerHolder(BroadcastChannel* aChannel)
243 0 : : mChannel(aChannel)
244 : {
245 0 : MOZ_COUNT_CTOR(BroadcastChannelWorkerHolder);
246 0 : }
247 :
248 0 : virtual bool Notify(workers::Status aStatus) override
249 : {
250 0 : if (aStatus >= Closing) {
251 0 : mChannel->Shutdown();
252 : }
253 :
254 0 : return true;
255 : }
256 :
257 : private:
258 0 : ~BroadcastChannelWorkerHolder()
259 0 : {
260 0 : MOZ_COUNT_DTOR(BroadcastChannelWorkerHolder);
261 0 : }
262 : };
263 :
264 : } // namespace
265 :
266 0 : BroadcastChannel::BroadcastChannel(nsPIDOMWindowInner* aWindow,
267 : const PrincipalInfo& aPrincipalInfo,
268 : const nsACString& aOrigin,
269 0 : const nsAString& aChannel)
270 : : DOMEventTargetHelper(aWindow)
271 : , mWorkerHolder(nullptr)
272 0 : , mPrincipalInfo(new PrincipalInfo(aPrincipalInfo))
273 : , mOrigin(aOrigin)
274 : , mChannel(aChannel)
275 : , mInnerID(0)
276 0 : , mState(StateActive)
277 : {
278 : // Window can be null in workers
279 :
280 0 : KeepAliveIfHasListenersFor(NS_LITERAL_STRING("message"));
281 0 : }
282 :
283 0 : BroadcastChannel::~BroadcastChannel()
284 : {
285 0 : Shutdown();
286 0 : MOZ_ASSERT(!mWorkerHolder);
287 0 : }
288 :
289 : JSObject*
290 0 : BroadcastChannel::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
291 : {
292 0 : return BroadcastChannelBinding::Wrap(aCx, this, aGivenProto);
293 : }
294 :
295 : /* static */ already_AddRefed<BroadcastChannel>
296 0 : BroadcastChannel::Constructor(const GlobalObject& aGlobal,
297 : const nsAString& aChannel,
298 : ErrorResult& aRv)
299 : {
300 : nsCOMPtr<nsPIDOMWindowInner> window =
301 0 : do_QueryInterface(aGlobal.GetAsSupports());
302 : // Window is null in workers.
303 :
304 0 : nsAutoCString origin;
305 0 : PrincipalInfo principalInfo;
306 0 : WorkerPrivate* workerPrivate = nullptr;
307 :
308 0 : if (NS_IsMainThread()) {
309 0 : nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
310 :
311 0 : if (!incumbent) {
312 0 : aRv.Throw(NS_ERROR_FAILURE);
313 0 : return nullptr;
314 : }
315 :
316 0 : nsIPrincipal* principal = incumbent->PrincipalOrNull();
317 0 : if (!principal) {
318 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
319 0 : return nullptr;
320 : }
321 :
322 0 : aRv = principal->GetOrigin(origin);
323 0 : if (NS_WARN_IF(aRv.Failed())) {
324 0 : return nullptr;
325 : }
326 :
327 0 : aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
328 0 : if (NS_WARN_IF(aRv.Failed())) {
329 0 : return nullptr;
330 : }
331 : } else {
332 0 : JSContext* cx = aGlobal.Context();
333 0 : workerPrivate = GetWorkerPrivateFromContext(cx);
334 0 : MOZ_ASSERT(workerPrivate);
335 :
336 : RefPtr<InitializeRunnable> runnable =
337 0 : new InitializeRunnable(workerPrivate, origin, principalInfo, aRv);
338 0 : runnable->Dispatch(Closing, aRv);
339 : }
340 :
341 0 : if (aRv.Failed()) {
342 0 : return nullptr;
343 : }
344 :
345 : RefPtr<BroadcastChannel> bc =
346 0 : new BroadcastChannel(window, principalInfo, origin, aChannel);
347 :
348 : // Register this component to PBackground.
349 0 : PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
350 0 : if (actor) {
351 0 : bc->ActorCreated(actor);
352 : } else {
353 0 : BackgroundChild::GetOrCreateForCurrentThread(bc);
354 : }
355 :
356 0 : if (!workerPrivate) {
357 0 : MOZ_ASSERT(window);
358 0 : MOZ_ASSERT(window->IsInnerWindow());
359 0 : bc->mInnerID = window->WindowID();
360 :
361 : // Register as observer for inner-window-destroyed.
362 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
363 0 : if (obs) {
364 0 : obs->AddObserver(bc, "inner-window-destroyed", false);
365 : }
366 : } else {
367 0 : bc->mWorkerHolder = new BroadcastChannelWorkerHolder(bc);
368 0 : if (NS_WARN_IF(!bc->mWorkerHolder->HoldWorker(workerPrivate, Closing))) {
369 0 : bc->mWorkerHolder = nullptr;
370 0 : aRv.Throw(NS_ERROR_FAILURE);
371 0 : return nullptr;
372 : }
373 : }
374 :
375 0 : return bc.forget();
376 : }
377 :
378 : void
379 0 : BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
380 : ErrorResult& aRv)
381 : {
382 0 : if (mState != StateActive) {
383 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
384 0 : return;
385 : }
386 :
387 0 : PostMessageInternal(aCx, aMessage, aRv);
388 : }
389 :
390 : void
391 0 : BroadcastChannel::PostMessageInternal(JSContext* aCx,
392 : JS::Handle<JS::Value> aMessage,
393 : ErrorResult& aRv)
394 : {
395 0 : RefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
396 :
397 0 : data->Write(aCx, aMessage, aRv);
398 0 : if (NS_WARN_IF(aRv.Failed())) {
399 0 : return;
400 : }
401 :
402 0 : PostMessageData(data);
403 : }
404 :
405 : void
406 0 : BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
407 : {
408 0 : RemoveDocFromBFCache();
409 :
410 0 : if (mActor) {
411 : RefPtr<BCPostMessageRunnable> runnable =
412 0 : new BCPostMessageRunnable(mActor, aData);
413 :
414 0 : if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
415 0 : NS_WARNING("Failed to dispatch to the current thread!");
416 : }
417 :
418 0 : return;
419 : }
420 :
421 0 : mPendingMessages.AppendElement(aData);
422 : }
423 :
424 : void
425 0 : BroadcastChannel::Close()
426 : {
427 0 : if (mState != StateActive) {
428 0 : return;
429 : }
430 :
431 0 : if (mPendingMessages.IsEmpty()) {
432 : // We cannot call Shutdown() immediatelly because we could have some
433 : // postMessage runnable already dispatched. Instead, we change the state to
434 : // StateClosed and we shutdown the actor asynchrounsly.
435 :
436 0 : mState = StateClosed;
437 0 : RefPtr<CloseRunnable> runnable = new CloseRunnable(this);
438 :
439 0 : if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
440 0 : NS_WARNING("Failed to dispatch to the current thread!");
441 : }
442 : } else {
443 0 : MOZ_ASSERT(!mActor);
444 0 : mState = StateClosing;
445 : }
446 : }
447 :
448 : void
449 0 : BroadcastChannel::ActorFailed()
450 : {
451 0 : MOZ_CRASH("Failed to create a PBackgroundChild actor!");
452 : }
453 :
454 : void
455 0 : BroadcastChannel::ActorCreated(PBackgroundChild* aActor)
456 : {
457 0 : MOZ_ASSERT(aActor);
458 :
459 0 : if (mState == StateClosed) {
460 0 : return;
461 : }
462 :
463 : PBroadcastChannelChild* actor =
464 0 : aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel);
465 :
466 0 : mActor = static_cast<BroadcastChannelChild*>(actor);
467 0 : MOZ_ASSERT(mActor);
468 :
469 0 : mActor->SetParent(this);
470 :
471 : // Flush pending messages.
472 0 : for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) {
473 0 : PostMessageData(mPendingMessages[i]);
474 : }
475 :
476 0 : mPendingMessages.Clear();
477 :
478 0 : if (mState == StateClosing) {
479 0 : Shutdown();
480 : }
481 : }
482 :
483 : void
484 0 : BroadcastChannel::Shutdown()
485 : {
486 0 : mState = StateClosed;
487 :
488 : // The DTOR of this WorkerHolder will release the worker for us.
489 0 : mWorkerHolder = nullptr;
490 :
491 0 : if (mActor) {
492 0 : mActor->SetParent(nullptr);
493 :
494 0 : RefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
495 0 : NS_DispatchToCurrentThread(runnable);
496 :
497 0 : mActor = nullptr;
498 : }
499 :
500 0 : IgnoreKeepAliveIfHasListenersFor(NS_LITERAL_STRING("message"));
501 0 : }
502 :
503 : NS_IMETHODIMP
504 0 : BroadcastChannel::Observe(nsISupports* aSubject, const char* aTopic,
505 : const char16_t* aData)
506 : {
507 0 : MOZ_ASSERT(NS_IsMainThread());
508 0 : MOZ_ASSERT(!strcmp(aTopic, "inner-window-destroyed"));
509 :
510 : // If the window is destroyed we have to release the reference that we are
511 : // keeping.
512 0 : nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
513 0 : NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
514 :
515 : uint64_t innerID;
516 0 : nsresult rv = wrapper->GetData(&innerID);
517 0 : NS_ENSURE_SUCCESS(rv, rv);
518 :
519 0 : if (innerID == mInnerID) {
520 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
521 0 : if (obs) {
522 0 : obs->RemoveObserver(this, "inner-window-destroyed");
523 : }
524 :
525 0 : Shutdown();
526 : }
527 :
528 0 : return NS_OK;
529 : }
530 :
531 : void
532 0 : BroadcastChannel::RemoveDocFromBFCache()
533 : {
534 0 : if (!NS_IsMainThread()) {
535 0 : return;
536 : }
537 :
538 0 : nsPIDOMWindowInner* window = GetOwner();
539 0 : if (!window) {
540 0 : return;
541 : }
542 :
543 0 : nsIDocument* doc = window->GetExtantDoc();
544 0 : if (!doc) {
545 0 : return;
546 : }
547 :
548 0 : nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry();
549 0 : if (!bfCacheEntry) {
550 0 : return;
551 : }
552 :
553 0 : bfCacheEntry->RemoveFromBFCacheSync();
554 : }
555 :
556 : NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel)
557 :
558 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel,
559 : DOMEventTargetHelper)
560 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
561 :
562 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel,
563 : DOMEventTargetHelper)
564 0 : tmp->Shutdown();
565 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
566 :
567 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BroadcastChannel)
568 0 : NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
569 0 : NS_INTERFACE_MAP_ENTRY(nsIObserver)
570 0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
571 :
572 0 : NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper)
573 0 : NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper)
574 :
575 : } // namespace dom
576 : } // namespace mozilla
|