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