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