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 "mozilla/dom/Notification.h"
8 
9 #include "mozilla/JSONWriter.h"
10 #include "mozilla/Move.h"
11 #include "mozilla/OwningNonNull.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/Services.h"
14 #include "mozilla/Telemetry.h"
15 #include "mozilla/Unused.h"
16 
17 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
18 #include "mozilla/dom/BindingUtils.h"
19 #include "mozilla/dom/ContentChild.h"
20 #include "mozilla/dom/NotificationEvent.h"
21 #include "mozilla/dom/PermissionMessageUtils.h"
22 #include "mozilla/dom/Promise.h"
23 #include "mozilla/dom/PromiseWorkerProxy.h"
24 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
25 
26 #include "nsAlertsUtils.h"
27 #include "nsComponentManagerUtils.h"
28 #include "nsContentPermissionHelper.h"
29 #include "nsContentUtils.h"
30 #include "nsCRTGlue.h"
31 #include "nsDOMJSUtils.h"
32 #include "nsGlobalWindow.h"
33 #include "nsIAlertsService.h"
34 #include "nsIContentPermissionPrompt.h"
35 #include "nsIDocument.h"
36 #include "nsILoadContext.h"
37 #include "nsINotificationStorage.h"
38 #include "nsIPermissionManager.h"
39 #include "nsIPermission.h"
40 #include "nsIPushService.h"
41 #include "nsIScriptSecurityManager.h"
42 #include "nsIServiceWorkerManager.h"
43 #include "nsISimpleEnumerator.h"
44 #include "nsIUUIDGenerator.h"
45 #include "nsIXPConnect.h"
46 #include "nsNetUtil.h"
47 #include "nsProxyRelease.h"
48 #include "nsServiceManagerUtils.h"
49 #include "nsStructuredCloneContainer.h"
50 #include "nsThreadUtils.h"
51 #include "nsToolkitCompsCID.h"
52 #include "nsXULAppAPI.h"
53 #include "ServiceWorkerManager.h"
54 #include "WorkerPrivate.h"
55 #include "WorkerRunnable.h"
56 #include "WorkerScope.h"
57 
58 namespace mozilla {
59 namespace dom {
60 
61 using namespace workers;
62 
63 struct NotificationStrings
64 {
65   const nsString mID;
66   const nsString mTitle;
67   const nsString mDir;
68   const nsString mLang;
69   const nsString mBody;
70   const nsString mTag;
71   const nsString mIcon;
72   const nsString mData;
73   const nsString mBehavior;
74   const nsString mServiceWorkerRegistrationScope;
75 };
76 
77 class ScopeCheckingGetCallback : public nsINotificationStorageCallback
78 {
79   const nsString mScope;
80 public:
ScopeCheckingGetCallback(const nsAString & aScope)81   explicit ScopeCheckingGetCallback(const nsAString& aScope)
82     : mScope(aScope)
83   {}
84 
Handle(const nsAString & aID,const nsAString & aTitle,const nsAString & aDir,const nsAString & aLang,const nsAString & aBody,const nsAString & aTag,const nsAString & aIcon,const nsAString & aData,const nsAString & aBehavior,const nsAString & aServiceWorkerRegistrationScope)85   NS_IMETHOD Handle(const nsAString& aID,
86                     const nsAString& aTitle,
87                     const nsAString& aDir,
88                     const nsAString& aLang,
89                     const nsAString& aBody,
90                     const nsAString& aTag,
91                     const nsAString& aIcon,
92                     const nsAString& aData,
93                     const nsAString& aBehavior,
94                     const nsAString& aServiceWorkerRegistrationScope) final
95   {
96     AssertIsOnMainThread();
97     MOZ_ASSERT(!aID.IsEmpty());
98 
99     // Skip scopes that don't match when called from getNotifications().
100     if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) {
101       return NS_OK;
102     }
103 
104     NotificationStrings strings = {
105       nsString(aID),
106       nsString(aTitle),
107       nsString(aDir),
108       nsString(aLang),
109       nsString(aBody),
110       nsString(aTag),
111       nsString(aIcon),
112       nsString(aData),
113       nsString(aBehavior),
114       nsString(aServiceWorkerRegistrationScope),
115     };
116 
117     mStrings.AppendElement(Move(strings));
118     return NS_OK;
119   }
120 
121   NS_IMETHOD Done() override = 0;
122 
123 protected:
~ScopeCheckingGetCallback()124   virtual ~ScopeCheckingGetCallback()
125   {}
126 
127   nsTArray<NotificationStrings> mStrings;
128 };
129 
130 class NotificationStorageCallback final : public ScopeCheckingGetCallback
131 {
132 public:
133   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)134   NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
135 
136   NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
137                               Promise* aPromise)
138     : ScopeCheckingGetCallback(aScope),
139       mWindow(aWindow),
140       mPromise(aPromise)
141   {
142     AssertIsOnMainThread();
143     MOZ_ASSERT(aWindow);
144     MOZ_ASSERT(aPromise);
145   }
146 
Done()147   NS_IMETHOD Done() final
148   {
149     ErrorResult result;
150     AutoTArray<RefPtr<Notification>, 5> notifications;
151 
152     for (uint32_t i = 0; i < mStrings.Length(); ++i) {
153       RefPtr<Notification> n =
154         Notification::ConstructFromFields(mWindow,
155                                           mStrings[i].mID,
156                                           mStrings[i].mTitle,
157                                           mStrings[i].mDir,
158                                           mStrings[i].mLang,
159                                           mStrings[i].mBody,
160                                           mStrings[i].mTag,
161                                           mStrings[i].mIcon,
162                                           mStrings[i].mData,
163                                           /* mStrings[i].mBehavior, not
164                                            * supported */
165                                           mStrings[i].mServiceWorkerRegistrationScope,
166                                           result);
167 
168       n->SetStoredState(true);
169       Unused << NS_WARN_IF(result.Failed());
170       if (!result.Failed()) {
171         notifications.AppendElement(n.forget());
172       }
173     }
174 
175     mPromise->MaybeResolve(notifications);
176     return NS_OK;
177   }
178 
179 private:
~NotificationStorageCallback()180   virtual ~NotificationStorageCallback()
181   {}
182 
183   nsCOMPtr<nsIGlobalObject> mWindow;
184   RefPtr<Promise> mPromise;
185   const nsString mScope;
186 };
187 
188 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
189 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
190 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
191 
192 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
193   NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
194   NS_INTERFACE_MAP_ENTRY(nsISupports)
195 NS_INTERFACE_MAP_END
196 
197 class NotificationGetRunnable final : public Runnable
198 {
199   const nsString mOrigin;
200   const nsString mTag;
201   nsCOMPtr<nsINotificationStorageCallback> mCallback;
202 public:
NotificationGetRunnable(const nsAString & aOrigin,const nsAString & aTag,nsINotificationStorageCallback * aCallback)203   NotificationGetRunnable(const nsAString& aOrigin,
204                           const nsAString& aTag,
205                           nsINotificationStorageCallback* aCallback)
206     : mOrigin(aOrigin), mTag(aTag), mCallback(aCallback)
207   {}
208 
209   NS_IMETHOD
Run()210   Run() override
211   {
212     nsresult rv;
213     nsCOMPtr<nsINotificationStorage> notificationStorage =
214       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
215     if (NS_WARN_IF(NS_FAILED(rv))) {
216       return rv;
217     }
218 
219     rv = notificationStorage->Get(mOrigin, mTag, mCallback);
220     //XXXnsm Is it guaranteed mCallback will be called in case of failure?
221     Unused << NS_WARN_IF(NS_FAILED(rv));
222     return rv;
223   }
224 };
225 
226 class NotificationPermissionRequest : public nsIContentPermissionRequest,
227                                       public nsIRunnable
228 {
229 public:
230   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
231   NS_DECL_NSICONTENTPERMISSIONREQUEST
232   NS_DECL_NSIRUNNABLE
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest,nsIContentPermissionRequest)233   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest,
234                                            nsIContentPermissionRequest)
235 
236   NotificationPermissionRequest(nsIPrincipal* aPrincipal,
237                                 nsPIDOMWindowInner* aWindow, Promise* aPromise,
238                                 NotificationPermissionCallback* aCallback)
239     : mPrincipal(aPrincipal), mWindow(aWindow),
240       mPermission(NotificationPermission::Default),
241       mPromise(aPromise),
242       mCallback(aCallback)
243   {
244     MOZ_ASSERT(aPromise);
245     mRequester = new nsContentPermissionRequester(mWindow);
246   }
247 
248 protected:
~NotificationPermissionRequest()249   virtual ~NotificationPermissionRequest() {}
250 
251   nsresult ResolvePromise();
252   nsresult DispatchResolvePromise();
253   nsCOMPtr<nsIPrincipal> mPrincipal;
254   nsCOMPtr<nsPIDOMWindowInner> mWindow;
255   NotificationPermission mPermission;
256   RefPtr<Promise> mPromise;
257   RefPtr<NotificationPermissionCallback> mCallback;
258   nsCOMPtr<nsIContentPermissionRequester> mRequester;
259 };
260 
261 namespace {
262 class ReleaseNotificationControlRunnable final : public MainThreadWorkerControlRunnable
263 {
264   Notification* mNotification;
265 
266 public:
ReleaseNotificationControlRunnable(Notification * aNotification)267   explicit ReleaseNotificationControlRunnable(Notification* aNotification)
268     : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate)
269     , mNotification(aNotification)
270   { }
271 
272   bool
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)273   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
274   {
275     mNotification->ReleaseObject();
276     return true;
277   }
278 };
279 
280 class GetPermissionRunnable final : public WorkerMainThreadRunnable
281 {
282   NotificationPermission mPermission;
283 
284 public:
GetPermissionRunnable(WorkerPrivate * aWorker)285   explicit GetPermissionRunnable(WorkerPrivate* aWorker)
286     : WorkerMainThreadRunnable(aWorker,
287                                NS_LITERAL_CSTRING("Notification :: Get Permission"))
288     , mPermission(NotificationPermission::Denied)
289   { }
290 
291   bool
MainThreadRun()292   MainThreadRun() override
293   {
294     ErrorResult result;
295     mPermission =
296       Notification::GetPermissionInternal(mWorkerPrivate->GetPrincipal(),
297                                           result);
298     return true;
299   }
300 
301   NotificationPermission
GetPermission()302   GetPermission()
303   {
304     return mPermission;
305   }
306 };
307 
308 class FocusWindowRunnable final : public Runnable
309 {
310   nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
311 public:
FocusWindowRunnable(const nsMainThreadPtrHandle<nsPIDOMWindowInner> & aWindow)312   explicit FocusWindowRunnable(const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
313     : mWindow(aWindow)
314   { }
315 
316   NS_IMETHOD
Run()317   Run() override
318   {
319     AssertIsOnMainThread();
320     if (!mWindow->IsCurrentInnerWindow()) {
321       // Window has been closed, this observer is not valid anymore
322       return NS_OK;
323     }
324 
325     nsIDocument* doc = mWindow->GetExtantDoc();
326     if (doc) {
327       // Browser UI may use DOMWebNotificationClicked to focus the tab
328       // from which the event was dispatched.
329       nsContentUtils::DispatchChromeEvent(doc, mWindow->GetOuterWindow(),
330                                           NS_LITERAL_STRING("DOMWebNotificationClicked"),
331                                           true, true);
332     }
333 
334     return NS_OK;
335   }
336 };
337 
338 nsresult
CheckScope(nsIPrincipal * aPrincipal,const nsACString & aScope)339 CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope)
340 {
341   AssertIsOnMainThread();
342   MOZ_ASSERT(aPrincipal);
343 
344   nsCOMPtr<nsIURI> scopeURI;
345   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
346   if (NS_WARN_IF(NS_FAILED(rv))) {
347     return rv;
348   }
349 
350   return aPrincipal->CheckMayLoad(scopeURI, /* report = */ true,
351                                   /* allowIfInheritsPrincipal = */ false);
352 }
353 } // anonymous namespace
354 
355 // Subclass that can be directly dispatched to child workers from the main
356 // thread.
357 class NotificationWorkerRunnable : public MainThreadWorkerRunnable
358 {
359 protected:
NotificationWorkerRunnable(WorkerPrivate * aWorkerPrivate)360   explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate)
361     : MainThreadWorkerRunnable(aWorkerPrivate)
362   {
363   }
364 
365   bool
WorkerRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate)366   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
367   {
368     aWorkerPrivate->AssertIsOnWorkerThread();
369     aWorkerPrivate->ModifyBusyCountFromWorker(true);
370     WorkerRunInternal(aWorkerPrivate);
371     return true;
372   }
373 
374   void
PostRun(JSContext * aCx,WorkerPrivate * aWorkerPrivate,bool aRunResult)375   PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
376           bool aRunResult) override
377   {
378     aWorkerPrivate->ModifyBusyCountFromWorker(false);
379   }
380 
381   virtual void
382   WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0;
383 };
384 
385 // Overrides dispatch and run handlers so we can directly dispatch from main
386 // thread to child workers.
387 class NotificationEventWorkerRunnable final : public NotificationWorkerRunnable
388 {
389   Notification* mNotification;
390   const nsString mEventName;
391 public:
NotificationEventWorkerRunnable(Notification * aNotification,const nsString & aEventName)392   NotificationEventWorkerRunnable(Notification* aNotification,
393                                   const nsString& aEventName)
394     : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
395     , mNotification(aNotification)
396     , mEventName(aEventName)
397   {}
398 
399   void
WorkerRunInternal(WorkerPrivate * aWorkerPrivate)400   WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override
401   {
402     mNotification->DispatchTrustedEvent(mEventName);
403   }
404 };
405 
406 class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
407 {
408   Notification* mNotification;
409 public:
ReleaseNotificationRunnable(Notification * aNotification)410   explicit ReleaseNotificationRunnable(Notification* aNotification)
411     : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
412     , mNotification(aNotification)
413   {}
414 
415   void
WorkerRunInternal(WorkerPrivate * aWorkerPrivate)416   WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override
417   {
418     mNotification->ReleaseObject();
419   }
420 };
421 
422 // Create one whenever you require ownership of the notification. Use with
423 // UniquePtr<>. See Notification.h for details.
424 class NotificationRef final {
425   friend class WorkerNotificationObserver;
426 
427 private:
428   Notification* mNotification;
429   bool mInited;
430 
431   // Only useful for workers.
432   void
Forget()433   Forget()
434   {
435     mNotification = nullptr;
436   }
437 
438 public:
NotificationRef(Notification * aNotification)439   explicit NotificationRef(Notification* aNotification)
440     : mNotification(aNotification)
441   {
442     MOZ_ASSERT(mNotification);
443     if (mNotification->mWorkerPrivate) {
444       mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
445     } else {
446       AssertIsOnMainThread();
447     }
448 
449     mInited = mNotification->AddRefObject();
450   }
451 
452   // This is only required because Gecko runs script in a worker's onclose
453   // handler (non-standard, Bug 790919) where calls to HoldWorker() will
454   // fail. Due to non-standardness and added complications if we decide to
455   // support this, attempts to create a Notification in onclose just throw
456   // exceptions.
457   bool
Initialized()458   Initialized()
459   {
460     return mInited;
461   }
462 
~NotificationRef()463   ~NotificationRef()
464   {
465     if (Initialized() && mNotification) {
466       Notification* notification = mNotification;
467       mNotification = nullptr;
468       if (notification->mWorkerPrivate && NS_IsMainThread()) {
469         // Try to pass ownership back to the worker. If the dispatch succeeds we
470         // are guaranteed this runnable will run, and that it will run after queued
471         // event runnables, so event runnables will have a safe pointer to the
472         // Notification.
473         //
474         // If the dispatch fails, the worker isn't running anymore and the event
475         // runnables have already run or been canceled. We can use a control
476         // runnable to release the reference.
477         RefPtr<ReleaseNotificationRunnable> r =
478           new ReleaseNotificationRunnable(notification);
479 
480         if (!r->Dispatch()) {
481           RefPtr<ReleaseNotificationControlRunnable> r =
482             new ReleaseNotificationControlRunnable(notification);
483           MOZ_ALWAYS_TRUE(r->Dispatch());
484         }
485       } else {
486         notification->AssertIsOnTargetThread();
487         notification->ReleaseObject();
488       }
489     }
490   }
491 
492   // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
493   // a rawptr that the NotificationRef can invalidate?
494   Notification*
GetNotification()495   GetNotification()
496   {
497     MOZ_ASSERT(Initialized());
498     return mNotification;
499   }
500 };
501 
502 class NotificationTask : public Runnable
503 {
504 public:
505   enum NotificationAction {
506     eShow,
507     eClose
508   };
509 
NotificationTask(UniquePtr<NotificationRef> aRef,NotificationAction aAction)510   NotificationTask(UniquePtr<NotificationRef> aRef, NotificationAction aAction)
511     : mNotificationRef(Move(aRef)), mAction(aAction)
512   {}
513 
514   NS_IMETHOD
515   Run() override;
516 protected:
~NotificationTask()517   virtual ~NotificationTask() {}
518 
519   UniquePtr<NotificationRef> mNotificationRef;
520   NotificationAction mAction;
521 };
522 
523 uint32_t Notification::sCount = 0;
524 
NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest,mWindow,mPromise,mCallback)525 NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow, mPromise,
526                                                         mCallback)
527 
528 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest)
529   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
530   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
531   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
532 NS_INTERFACE_MAP_END
533 
534 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationPermissionRequest)
535 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationPermissionRequest)
536 
537 NS_IMETHODIMP
538 NotificationPermissionRequest::Run()
539 {
540   if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
541     mPermission = NotificationPermission::Granted;
542   } else {
543     // File are automatically granted permission.
544     nsCOMPtr<nsIURI> uri;
545     mPrincipal->GetURI(getter_AddRefs(uri));
546 
547     if (uri) {
548       bool isFile;
549       uri->SchemeIs("file", &isFile);
550       if (isFile) {
551         mPermission = NotificationPermission::Granted;
552       }
553     }
554   }
555 
556   // Grant permission if pref'ed on.
557   if (Preferences::GetBool("notification.prompt.testing", false)) {
558     if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
559       mPermission = NotificationPermission::Granted;
560     } else {
561       mPermission = NotificationPermission::Denied;
562     }
563   }
564 
565   if (mPermission != NotificationPermission::Default) {
566     return DispatchResolvePromise();
567   }
568 
569   return nsContentPermissionUtils::AskPermission(this, mWindow);
570 }
571 
572 NS_IMETHODIMP
GetPrincipal(nsIPrincipal ** aRequestingPrincipal)573 NotificationPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal)
574 {
575   NS_ADDREF(*aRequestingPrincipal = mPrincipal);
576   return NS_OK;
577 }
578 
579 NS_IMETHODIMP
GetWindow(mozIDOMWindow ** aRequestingWindow)580 NotificationPermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow)
581 {
582   NS_ADDREF(*aRequestingWindow = mWindow);
583   return NS_OK;
584 }
585 
586 NS_IMETHODIMP
GetElement(nsIDOMElement ** aElement)587 NotificationPermissionRequest::GetElement(nsIDOMElement** aElement)
588 {
589   NS_ENSURE_ARG_POINTER(aElement);
590   *aElement = nullptr;
591   return NS_OK;
592 }
593 
594 NS_IMETHODIMP
Cancel()595 NotificationPermissionRequest::Cancel()
596 {
597   // `Cancel` is called if the user denied permission or dismissed the
598   // permission request. To distinguish between the two, we set the
599   // permission to "default" and query the permission manager in
600   // `ResolvePromise`.
601   mPermission = NotificationPermission::Default;
602   return DispatchResolvePromise();
603 }
604 
605 NS_IMETHODIMP
Allow(JS::HandleValue aChoices)606 NotificationPermissionRequest::Allow(JS::HandleValue aChoices)
607 {
608   MOZ_ASSERT(aChoices.isUndefined());
609 
610   mPermission = NotificationPermission::Granted;
611   return DispatchResolvePromise();
612 }
613 
614 NS_IMETHODIMP
GetRequester(nsIContentPermissionRequester ** aRequester)615 NotificationPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
616 {
617   NS_ENSURE_ARG_POINTER(aRequester);
618 
619   nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
620   requester.forget(aRequester);
621   return NS_OK;
622 }
623 
624 inline nsresult
DispatchResolvePromise()625 NotificationPermissionRequest::DispatchResolvePromise()
626 {
627   return NS_DispatchToMainThread(NewRunnableMethod(this,
628                                                    &NotificationPermissionRequest::ResolvePromise));
629 }
630 
631 nsresult
ResolvePromise()632 NotificationPermissionRequest::ResolvePromise()
633 {
634   nsresult rv = NS_OK;
635   if (mPermission == NotificationPermission::Default) {
636     // This will still be "default" if the user dismissed the doorhanger,
637     // or "denied" otherwise.
638     mPermission = Notification::TestPermission(mPrincipal);
639   }
640   if (mCallback) {
641     ErrorResult error;
642     mCallback->Call(mPermission, error);
643     rv = error.StealNSResult();
644   }
645   Telemetry::Accumulate(
646     Telemetry::WEB_NOTIFICATION_REQUEST_PERMISSION_CALLBACK, !!mCallback);
647   mPromise->MaybeResolve(mPermission);
648   return rv;
649 }
650 
651 NS_IMETHODIMP
GetTypes(nsIArray ** aTypes)652 NotificationPermissionRequest::GetTypes(nsIArray** aTypes)
653 {
654   nsTArray<nsString> emptyOptions;
655   return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
656                                                          NS_LITERAL_CSTRING("unused"),
657                                                          emptyOptions,
658                                                          aTypes);
659 }
660 
NS_IMPL_ISUPPORTS(NotificationTelemetryService,nsIObserver)661 NS_IMPL_ISUPPORTS(NotificationTelemetryService, nsIObserver)
662 
663 NotificationTelemetryService::NotificationTelemetryService()
664   : mDNDRecorded(false)
665 {}
666 
~NotificationTelemetryService()667 NotificationTelemetryService::~NotificationTelemetryService()
668 {
669   Unused << NS_WARN_IF(NS_FAILED(RemovePermissionChangeObserver()));
670 }
671 
672 /* static */ already_AddRefed<NotificationTelemetryService>
GetInstance()673 NotificationTelemetryService::GetInstance()
674 {
675   nsCOMPtr<nsISupports> telemetrySupports =
676     do_GetService(NOTIFICATIONTELEMETRYSERVICE_CONTRACTID);
677   if (!telemetrySupports) {
678     return nullptr;
679   }
680   RefPtr<NotificationTelemetryService> telemetry =
681     static_cast<NotificationTelemetryService*>(telemetrySupports.get());
682   return telemetry.forget();
683 }
684 
685 nsresult
Init()686 NotificationTelemetryService::Init()
687 {
688   nsresult rv = AddPermissionChangeObserver();
689   NS_ENSURE_SUCCESS(rv, rv);
690 
691   RecordPermissions();
692 
693   return NS_OK;
694 }
695 
696 nsresult
RemovePermissionChangeObserver()697 NotificationTelemetryService::RemovePermissionChangeObserver()
698 {
699   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
700   if (!obs) {
701     return NS_ERROR_OUT_OF_MEMORY;
702   }
703   return obs->RemoveObserver(this, "perm-changed");
704 }
705 
706 nsresult
AddPermissionChangeObserver()707 NotificationTelemetryService::AddPermissionChangeObserver()
708 {
709   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
710   if (!obs) {
711     return NS_ERROR_OUT_OF_MEMORY;
712   }
713   return obs->AddObserver(this, "perm-changed", false);
714 }
715 
716 void
RecordPermissions()717 NotificationTelemetryService::RecordPermissions()
718 {
719   if (!Telemetry::CanRecordBase() || !Telemetry::CanRecordExtended()) {
720     return;
721   }
722 
723   nsCOMPtr<nsIPermissionManager> permissionManager =
724     services::GetPermissionManager();
725   if (!permissionManager) {
726     return;
727   }
728 
729   nsCOMPtr<nsISimpleEnumerator> enumerator;
730   nsresult rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator));
731   if (NS_WARN_IF(NS_FAILED(rv))) {
732     return;
733   }
734 
735   for (;;) {
736     bool hasMoreElements;
737     nsresult rv = enumerator->HasMoreElements(&hasMoreElements);
738     if (NS_WARN_IF(NS_FAILED(rv))) {
739       return;
740     }
741     if (!hasMoreElements) {
742       break;
743     }
744     nsCOMPtr<nsISupports> supportsPermission;
745     rv = enumerator->GetNext(getter_AddRefs(supportsPermission));
746     if (NS_WARN_IF(NS_FAILED(rv))) {
747       return;
748     }
749     uint32_t capability;
750     if (!GetNotificationPermission(supportsPermission, &capability)) {
751       continue;
752     }
753     if (capability == nsIPermissionManager::DENY_ACTION) {
754       Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 0);
755     } else if (capability == nsIPermissionManager::ALLOW_ACTION) {
756       Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 1);
757     }
758   }
759 }
760 
761 bool
GetNotificationPermission(nsISupports * aSupports,uint32_t * aCapability)762 NotificationTelemetryService::GetNotificationPermission(nsISupports* aSupports,
763                                                         uint32_t* aCapability)
764 {
765   nsCOMPtr<nsIPermission> permission = do_QueryInterface(aSupports);
766   if (!permission) {
767     return false;
768   }
769   nsAutoCString type;
770   permission->GetType(type);
771   if (!type.Equals("desktop-notification")) {
772     return false;
773   }
774   permission->GetCapability(aCapability);
775   return true;
776 }
777 
778 void
RecordDNDSupported()779 NotificationTelemetryService::RecordDNDSupported()
780 {
781   if (mDNDRecorded) {
782     return;
783   }
784 
785   nsCOMPtr<nsIAlertsService> alertService =
786     do_GetService(NS_ALERTSERVICE_CONTRACTID);
787   if (!alertService) {
788     return;
789   }
790 
791   nsCOMPtr<nsIAlertsDoNotDisturb> alertServiceDND =
792     do_QueryInterface(alertService);
793   if (!alertServiceDND) {
794     return;
795   }
796 
797   mDNDRecorded = true;
798   bool isEnabled;
799   nsresult rv = alertServiceDND->GetManualDoNotDisturb(&isEnabled);
800   if (NS_FAILED(rv)) {
801     return;
802   }
803 
804   Telemetry::Accumulate(
805     Telemetry::ALERTS_SERVICE_DND_SUPPORTED_FLAG, true);
806 }
807 
808 nsresult
RecordSender(nsIPrincipal * aPrincipal)809 NotificationTelemetryService::RecordSender(nsIPrincipal* aPrincipal)
810 {
811   if (!Telemetry::CanRecordBase() || !Telemetry::CanRecordExtended() ||
812       !nsAlertsUtils::IsActionablePrincipal(aPrincipal)) {
813     return NS_OK;
814   }
815   nsAutoString origin;
816   nsresult rv = Notification::GetOrigin(aPrincipal, origin);
817   if (NS_FAILED(rv)) {
818     return rv;
819   }
820   if (!mOrigins.Contains(origin)) {
821     mOrigins.PutEntry(origin);
822     Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_SENDERS, 1);
823   }
824   return NS_OK;
825 }
826 
827 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)828 NotificationTelemetryService::Observe(nsISupports* aSubject,
829                                       const char* aTopic,
830                                       const char16_t* aData)
831 {
832   uint32_t capability;
833   if (strcmp("perm-changed", aTopic) ||
834       !NS_strcmp(u"cleared", aData) ||
835       !GetNotificationPermission(aSubject, &capability)) {
836     return NS_OK;
837   }
838   if (!NS_strcmp(u"deleted", aData)) {
839     if (capability == nsIPermissionManager::DENY_ACTION) {
840       Telemetry::Accumulate(
841         Telemetry::WEB_NOTIFICATION_PERMISSION_REMOVED, 0);
842     } else if (capability == nsIPermissionManager::ALLOW_ACTION) {
843       Telemetry::Accumulate(
844         Telemetry::WEB_NOTIFICATION_PERMISSION_REMOVED, 1);
845     }
846   }
847   return NS_OK;
848 }
849 
850 // Observer that the alert service calls to do common tasks and/or dispatch to the
851 // specific observer for the context e.g. main thread, worker, or service worker.
852 class NotificationObserver final : public nsIObserver
853 {
854 public:
855   nsCOMPtr<nsIObserver> mObserver;
856   nsCOMPtr<nsIPrincipal> mPrincipal;
857   bool mInPrivateBrowsing;
858   NS_DECL_ISUPPORTS
859   NS_DECL_NSIOBSERVER
860 
NotificationObserver(nsIObserver * aObserver,nsIPrincipal * aPrincipal,bool aInPrivateBrowsing)861   NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
862                        bool aInPrivateBrowsing)
863     : mObserver(aObserver), mPrincipal(aPrincipal),
864       mInPrivateBrowsing(aInPrivateBrowsing)
865   {
866     AssertIsOnMainThread();
867     MOZ_ASSERT(mObserver);
868     MOZ_ASSERT(mPrincipal);
869   }
870 
871 protected:
~NotificationObserver()872   virtual ~NotificationObserver()
873   {
874     AssertIsOnMainThread();
875   }
876 
877   nsresult AdjustPushQuota(const char* aTopic);
878 };
879 
880 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
881 
882 class MainThreadNotificationObserver : public nsIObserver
883 {
884 public:
885   UniquePtr<NotificationRef> mNotificationRef;
886   NS_DECL_ISUPPORTS
887   NS_DECL_NSIOBSERVER
888 
MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)889   explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
890     : mNotificationRef(Move(aRef))
891   {
892     AssertIsOnMainThread();
893   }
894 
895 protected:
~MainThreadNotificationObserver()896   virtual ~MainThreadNotificationObserver()
897   {
898     AssertIsOnMainThread();
899   }
900 };
901 
NS_IMPL_ISUPPORTS(MainThreadNotificationObserver,nsIObserver)902 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
903 
904 NS_IMETHODIMP
905 NotificationTask::Run()
906 {
907   AssertIsOnMainThread();
908 
909   // Get a pointer to notification before the notification takes ownership of
910   // the ref (it owns itself temporarily, with ShowInternal() and
911   // CloseInternal() passing on the ownership appropriately.)
912   Notification* notif = mNotificationRef->GetNotification();
913   notif->mTempRef.swap(mNotificationRef);
914   if (mAction == eShow) {
915     notif->ShowInternal();
916   } else if (mAction == eClose) {
917     notif->CloseInternal();
918   } else {
919     MOZ_CRASH("Invalid action");
920   }
921 
922   MOZ_ASSERT(!mNotificationRef);
923   return NS_OK;
924 }
925 
926 bool
RequireInteractionEnabled(JSContext * aCx,JSObject * aOjb)927 Notification::RequireInteractionEnabled(JSContext* aCx, JSObject* aOjb)
928 {
929   if (NS_IsMainThread()) {
930     return Preferences::GetBool("dom.webnotifications.requireinteraction.enabled", false);
931   }
932 
933   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
934   if (!workerPrivate) {
935     return false;
936   }
937 
938   return workerPrivate->DOMWorkerNotificationRIEnabled();
939 }
940 
941 // static
942 bool
PrefEnabled(JSContext * aCx,JSObject * aObj)943 Notification::PrefEnabled(JSContext* aCx, JSObject* aObj)
944 {
945   if (NS_IsMainThread()) {
946     return Preferences::GetBool("dom.webnotifications.enabled", false);
947   }
948 
949   WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
950   if (!workerPrivate) {
951     return false;
952   }
953 
954   if (workerPrivate->IsServiceWorker()) {
955     return workerPrivate->DOMServiceWorkerNotificationEnabled();
956   }
957 
958   return workerPrivate->DOMWorkerNotificationEnabled();
959 }
960 
961 // static
962 bool
IsGetEnabled(JSContext * aCx,JSObject * aObj)963 Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj)
964 {
965   return NS_IsMainThread();
966 }
967 
Notification(nsIGlobalObject * aGlobal,const nsAString & aID,const nsAString & aTitle,const nsAString & aBody,NotificationDirection aDir,const nsAString & aLang,const nsAString & aTag,const nsAString & aIconUrl,bool aRequireInteraction,const NotificationBehavior & aBehavior)968 Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
969                            const nsAString& aTitle, const nsAString& aBody,
970                            NotificationDirection aDir, const nsAString& aLang,
971                            const nsAString& aTag, const nsAString& aIconUrl,
972                            bool aRequireInteraction,
973                            const NotificationBehavior& aBehavior)
974   : DOMEventTargetHelper(),
975     mWorkerPrivate(nullptr), mObserver(nullptr),
976     mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
977     mTag(aTag), mIconUrl(aIconUrl), mRequireInteraction(aRequireInteraction),
978     mBehavior(aBehavior), mData(JS::NullValue()),
979     mIsClosed(false), mIsStored(false), mTaskCount(0)
980 {
981   if (NS_IsMainThread()) {
982     // We can only call this on the main thread because
983     // Event::SetEventType() called down the call chain when dispatching events
984     // using DOMEventTargetHelper::DispatchTrustedEvent() will assume the event
985     // is a main thread event if it has a valid owner. It will then attempt to
986     // fetch the atom for the event name which asserts main thread only.
987     BindToOwner(aGlobal);
988   } else {
989     mWorkerPrivate = GetCurrentThreadWorkerPrivate();
990     MOZ_ASSERT(mWorkerPrivate);
991   }
992 }
993 
994 nsresult
Init()995 Notification::Init()
996 {
997   if (!mWorkerPrivate) {
998     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
999     NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
1000 
1001     nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
1002     NS_ENSURE_SUCCESS(rv, rv);
1003 
1004     rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
1005     NS_ENSURE_SUCCESS(rv, rv);
1006   }
1007 
1008   return NS_OK;
1009 }
1010 
1011 void
SetAlertName()1012 Notification::SetAlertName()
1013 {
1014   AssertIsOnMainThread();
1015   if (!mAlertName.IsEmpty()) {
1016     return;
1017   }
1018 
1019   nsAutoString alertName;
1020   nsresult rv = GetOrigin(GetPrincipal(), alertName);
1021   if (NS_WARN_IF(NS_FAILED(rv))) {
1022     return;
1023   }
1024 
1025   // Get the notification name that is unique per origin + tag/ID.
1026   // The name of the alert is of the form origin#tag/ID.
1027   alertName.Append('#');
1028   if (!mTag.IsEmpty()) {
1029     alertName.AppendLiteral("tag:");
1030     alertName.Append(mTag);
1031   } else {
1032     alertName.AppendLiteral("notag:");
1033     alertName.Append(mID);
1034   }
1035 
1036   mAlertName = alertName;
1037 }
1038 
1039 // May be called on any thread.
1040 // static
1041 already_AddRefed<Notification>
Constructor(const GlobalObject & aGlobal,const nsAString & aTitle,const NotificationOptions & aOptions,ErrorResult & aRv)1042 Notification::Constructor(const GlobalObject& aGlobal,
1043                           const nsAString& aTitle,
1044                           const NotificationOptions& aOptions,
1045                           ErrorResult& aRv)
1046 {
1047   // FIXME(nsm): If the sticky flag is set, throw an error.
1048   RefPtr<ServiceWorkerGlobalScope> scope;
1049   UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
1050   if (scope) {
1051     aRv.ThrowTypeError<MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER>();
1052     return nullptr;
1053   }
1054 
1055   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1056   RefPtr<Notification> notification =
1057     CreateAndShow(aGlobal.Context(), global, aTitle, aOptions,
1058                   EmptyString(), aRv);
1059   if (NS_WARN_IF(aRv.Failed())) {
1060     return nullptr;
1061   }
1062 
1063   // This is be ok since we are on the worker thread where this function will
1064   // run to completion before the Notification has a chance to go away.
1065   return notification.forget();
1066 }
1067 
1068 // static
1069 already_AddRefed<Notification>
ConstructFromFields(nsIGlobalObject * aGlobal,const nsAString & aID,const nsAString & aTitle,const nsAString & aDir,const nsAString & aLang,const nsAString & aBody,const nsAString & aTag,const nsAString & aIcon,const nsAString & aData,const nsAString & aServiceWorkerRegistrationScope,ErrorResult & aRv)1070 Notification::ConstructFromFields(
1071     nsIGlobalObject* aGlobal,
1072     const nsAString& aID,
1073     const nsAString& aTitle,
1074     const nsAString& aDir,
1075     const nsAString& aLang,
1076     const nsAString& aBody,
1077     const nsAString& aTag,
1078     const nsAString& aIcon,
1079     const nsAString& aData,
1080     const nsAString& aServiceWorkerRegistrationScope,
1081     ErrorResult& aRv)
1082 {
1083   MOZ_ASSERT(aGlobal);
1084 
1085   RootedDictionary<NotificationOptions> options(RootingCx());
1086   options.mDir = Notification::StringToDirection(nsString(aDir));
1087   options.mLang = aLang;
1088   options.mBody = aBody;
1089   options.mTag = aTag;
1090   options.mIcon = aIcon;
1091   RefPtr<Notification> notification = CreateInternal(aGlobal, aID, aTitle,
1092                                                      options);
1093 
1094   notification->InitFromBase64(aData, aRv);
1095   if (NS_WARN_IF(aRv.Failed())) {
1096     return nullptr;
1097   }
1098 
1099   notification->SetScope(aServiceWorkerRegistrationScope);
1100 
1101   return notification.forget();
1102 }
1103 
1104 nsresult
PersistNotification()1105 Notification::PersistNotification()
1106 {
1107   AssertIsOnMainThread();
1108   nsresult rv;
1109   nsCOMPtr<nsINotificationStorage> notificationStorage =
1110     do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
1111   if (NS_FAILED(rv)) {
1112     return rv;
1113   }
1114 
1115   nsString origin;
1116   rv = GetOrigin(GetPrincipal(), origin);
1117   if (NS_WARN_IF(NS_FAILED(rv))) {
1118     return rv;
1119   }
1120 
1121   nsString id;
1122   GetID(id);
1123 
1124   nsString alertName;
1125   GetAlertName(alertName);
1126 
1127   nsAutoString behavior;
1128   if (!mBehavior.ToJSON(behavior)) {
1129     return NS_ERROR_FAILURE;
1130   }
1131 
1132   rv = notificationStorage->Put(origin,
1133                                 id,
1134                                 mTitle,
1135                                 DirectionToString(mDir),
1136                                 mLang,
1137                                 mBody,
1138                                 mTag,
1139                                 mIconUrl,
1140                                 alertName,
1141                                 mDataAsBase64,
1142                                 behavior,
1143                                 mScope);
1144 
1145   if (NS_FAILED(rv)) {
1146     return rv;
1147   }
1148 
1149   SetStoredState(true);
1150   return NS_OK;
1151 }
1152 
1153 void
UnpersistNotification()1154 Notification::UnpersistNotification()
1155 {
1156   AssertIsOnMainThread();
1157   if (IsStored()) {
1158     nsCOMPtr<nsINotificationStorage> notificationStorage =
1159       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1160     if (notificationStorage) {
1161       nsString origin;
1162       nsresult rv = GetOrigin(GetPrincipal(), origin);
1163       if (NS_SUCCEEDED(rv)) {
1164         notificationStorage->Delete(origin, mID);
1165       }
1166     }
1167     SetStoredState(false);
1168   }
1169 }
1170 
1171 already_AddRefed<Notification>
CreateInternal(nsIGlobalObject * aGlobal,const nsAString & aID,const nsAString & aTitle,const NotificationOptions & aOptions)1172 Notification::CreateInternal(nsIGlobalObject* aGlobal,
1173                              const nsAString& aID,
1174                              const nsAString& aTitle,
1175                              const NotificationOptions& aOptions)
1176 {
1177   nsresult rv;
1178   nsString id;
1179   if (!aID.IsEmpty()) {
1180     id = aID;
1181   } else {
1182     nsCOMPtr<nsIUUIDGenerator> uuidgen =
1183       do_GetService("@mozilla.org/uuid-generator;1");
1184     NS_ENSURE_TRUE(uuidgen, nullptr);
1185     nsID uuid;
1186     rv = uuidgen->GenerateUUIDInPlace(&uuid);
1187     NS_ENSURE_SUCCESS(rv, nullptr);
1188 
1189     char buffer[NSID_LENGTH];
1190     uuid.ToProvidedString(buffer);
1191     NS_ConvertASCIItoUTF16 convertedID(buffer);
1192     id = convertedID;
1193   }
1194 
1195   RefPtr<Notification> notification = new Notification(aGlobal, id, aTitle,
1196                                                          aOptions.mBody,
1197                                                          aOptions.mDir,
1198                                                          aOptions.mLang,
1199                                                          aOptions.mTag,
1200                                                          aOptions.mIcon,
1201                                                          aOptions.mRequireInteraction,
1202                                                          aOptions.mMozbehavior);
1203   rv = notification->Init();
1204   NS_ENSURE_SUCCESS(rv, nullptr);
1205   return notification.forget();
1206 }
1207 
~Notification()1208 Notification::~Notification()
1209 {
1210   mData.setUndefined();
1211   mozilla::DropJSObjects(this);
1212   AssertIsOnTargetThread();
1213   MOZ_ASSERT(!mWorkerHolder);
1214   MOZ_ASSERT(!mTempRef);
1215 }
1216 
1217 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
1218 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification, DOMEventTargetHelper)
1219   tmp->mData.setUndefined();
1220 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1221 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,DOMEventTargetHelper)1222 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification, DOMEventTargetHelper)
1223 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1224 
1225 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification, DOMEventTargetHelper)
1226   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
1227 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1228 
1229 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
1230 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
1231 
1232 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Notification)
1233   NS_INTERFACE_MAP_ENTRY(nsIObserver)
1234   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
1235 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
1236 
1237 nsIPrincipal*
1238 Notification::GetPrincipal()
1239 {
1240   AssertIsOnMainThread();
1241   if (mWorkerPrivate) {
1242     return mWorkerPrivate->GetPrincipal();
1243   } else {
1244     nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
1245     NS_ENSURE_TRUE(sop, nullptr);
1246     return sop->GetPrincipal();
1247   }
1248 }
1249 
1250 class WorkerNotificationObserver final : public MainThreadNotificationObserver
1251 {
1252 public:
1253   NS_DECL_ISUPPORTS_INHERITED
1254   NS_DECL_NSIOBSERVER
1255 
WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)1256   explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
1257     : MainThreadNotificationObserver(Move(aRef))
1258   {
1259     AssertIsOnMainThread();
1260     MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate);
1261   }
1262 
1263   void
ForgetNotification()1264   ForgetNotification()
1265   {
1266     AssertIsOnMainThread();
1267     mNotificationRef->Forget();
1268   }
1269 
1270 protected:
~WorkerNotificationObserver()1271   virtual ~WorkerNotificationObserver()
1272   {
1273     AssertIsOnMainThread();
1274 
1275     MOZ_ASSERT(mNotificationRef);
1276     Notification* notification = mNotificationRef->GetNotification();
1277     if (notification) {
1278       notification->mObserver = nullptr;
1279     }
1280   }
1281 };
1282 
1283 NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, MainThreadNotificationObserver)
1284 
1285 class ServiceWorkerNotificationObserver final : public nsIObserver
1286 {
1287 public:
1288   NS_DECL_ISUPPORTS
1289   NS_DECL_NSIOBSERVER
1290 
ServiceWorkerNotificationObserver(const nsAString & aScope,nsIPrincipal * aPrincipal,const nsAString & aID,const nsAString & aTitle,const nsAString & aDir,const nsAString & aLang,const nsAString & aBody,const nsAString & aTag,const nsAString & aIcon,const nsAString & aData,const nsAString & aBehavior)1291   ServiceWorkerNotificationObserver(const nsAString& aScope,
1292                                     nsIPrincipal* aPrincipal,
1293                                     const nsAString& aID,
1294                                     const nsAString& aTitle,
1295                                     const nsAString& aDir,
1296                                     const nsAString& aLang,
1297                                     const nsAString& aBody,
1298                                     const nsAString& aTag,
1299                                     const nsAString& aIcon,
1300                                     const nsAString& aData,
1301                                     const nsAString& aBehavior)
1302     : mScope(aScope), mID(aID), mPrincipal(aPrincipal), mTitle(aTitle)
1303     , mDir(aDir), mLang(aLang), mBody(aBody), mTag(aTag), mIcon(aIcon)
1304     , mData(aData), mBehavior(aBehavior)
1305   {
1306     AssertIsOnMainThread();
1307     MOZ_ASSERT(aPrincipal);
1308   }
1309 
1310 private:
~ServiceWorkerNotificationObserver()1311   ~ServiceWorkerNotificationObserver()
1312   {}
1313 
1314   const nsString mScope;
1315   const nsString mID;
1316   nsCOMPtr<nsIPrincipal> mPrincipal;
1317   const nsString mTitle;
1318   const nsString mDir;
1319   const nsString mLang;
1320   const nsString mBody;
1321   const nsString mTag;
1322   const nsString mIcon;
1323   const nsString mData;
1324   const nsString mBehavior;
1325 };
1326 
NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver,nsIObserver)1327 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver)
1328 
1329 // For ServiceWorkers.
1330 bool
1331 Notification::DispatchNotificationClickEvent()
1332 {
1333   MOZ_ASSERT(mWorkerPrivate);
1334   MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
1335   mWorkerPrivate->AssertIsOnWorkerThread();
1336 
1337   NotificationEventInit options;
1338   options.mNotification = this;
1339 
1340   ErrorResult result;
1341   RefPtr<EventTarget> target = mWorkerPrivate->GlobalScope();
1342   RefPtr<NotificationEvent> event =
1343     NotificationEvent::Constructor(target,
1344                                    NS_LITERAL_STRING("notificationclick"),
1345                                    options,
1346                                    result);
1347   if (NS_WARN_IF(result.Failed())) {
1348     return false;
1349   }
1350 
1351   event->SetTrusted(true);
1352   WantsPopupControlCheck popupControlCheck(event);
1353   target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
1354   // We always return false since in case of dispatching on the serviceworker,
1355   // there is no well defined window to focus. The script may use the
1356   // Client.focus() API if it wishes.
1357   return false;
1358 }
1359 
1360 bool
DispatchClickEvent()1361 Notification::DispatchClickEvent()
1362 {
1363   AssertIsOnTargetThread();
1364   RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1365   event->InitEvent(NS_LITERAL_STRING("click"), false, true);
1366   event->SetTrusted(true);
1367   WantsPopupControlCheck popupControlCheck(event);
1368   bool doDefaultAction = true;
1369   DispatchEvent(event, &doDefaultAction);
1370   return doDefaultAction;
1371 }
1372 
1373 // Overrides dispatch and run handlers so we can directly dispatch from main
1374 // thread to child workers.
1375 class NotificationClickWorkerRunnable final : public NotificationWorkerRunnable
1376 {
1377   Notification* mNotification;
1378   // Optional window that gets focused if click event is not
1379   // preventDefault()ed.
1380   nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
1381 public:
NotificationClickWorkerRunnable(Notification * aNotification,const nsMainThreadPtrHandle<nsPIDOMWindowInner> & aWindow)1382   NotificationClickWorkerRunnable(Notification* aNotification,
1383                                   const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
1384     : NotificationWorkerRunnable(aNotification->mWorkerPrivate)
1385     , mNotification(aNotification)
1386     , mWindow(aWindow)
1387   {
1388     MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow);
1389   }
1390 
1391   void
WorkerRunInternal(WorkerPrivate * aWorkerPrivate)1392   WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override
1393   {
1394     bool doDefaultAction = mNotification->DispatchClickEvent();
1395     MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction);
1396     if (doDefaultAction) {
1397       RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
1398       NS_DispatchToMainThread(r);
1399     }
1400   }
1401 };
1402 
1403 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1404 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1405                               const char16_t* aData)
1406 {
1407   AssertIsOnMainThread();
1408 
1409   if (!strcmp("alertdisablecallback", aTopic)) {
1410     Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 1);
1411     if (XRE_IsParentProcess()) {
1412       return Notification::RemovePermission(mPrincipal);
1413     }
1414     // Permissions can't be removed from the content process. Send a message
1415     // to the parent; `ContentParent::RecvDisableNotifications` will call
1416     // `RemovePermission`.
1417     ContentChild::GetSingleton()->SendDisableNotifications(
1418       IPC::Principal(mPrincipal));
1419     return NS_OK;
1420   } else if (!strcmp("alertclickcallback", aTopic)) {
1421     Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_CLICKED, 1);
1422   } else if (!strcmp("alertsettingscallback", aTopic)) {
1423     Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 2);
1424     if (XRE_IsParentProcess()) {
1425       return Notification::OpenSettings(mPrincipal);
1426     }
1427     // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1428     // parent process.
1429     ContentChild::GetSingleton()->SendOpenNotificationSettings(
1430       IPC::Principal(mPrincipal));
1431     return NS_OK;
1432   } else if (!strcmp("alertshow", aTopic) ||
1433              !strcmp("alertfinished", aTopic)) {
1434     RefPtr<NotificationTelemetryService> telemetry =
1435       NotificationTelemetryService::GetInstance();
1436     if (telemetry) {
1437       // Record whether "do not disturb" is supported after the first
1438       // notification, to account for falling back to XUL alerts.
1439       telemetry->RecordDNDSupported();
1440       if (!mInPrivateBrowsing) {
1441         // Ignore senders in private windows.
1442         Unused << NS_WARN_IF(NS_FAILED(telemetry->RecordSender(mPrincipal)));
1443       }
1444     }
1445     Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
1446 
1447     if (!strcmp("alertshow", aTopic)) {
1448       // Record notifications actually shown (e.g. don't count if DND is on).
1449       Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_SHOWN, 1);
1450     }
1451   }
1452 
1453   return mObserver->Observe(aSubject, aTopic, aData);
1454 }
1455 
1456 nsresult
AdjustPushQuota(const char * aTopic)1457 NotificationObserver::AdjustPushQuota(const char* aTopic)
1458 {
1459   nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
1460     do_GetService("@mozilla.org/push/Service;1");
1461   if (!pushQuotaManager) {
1462     return NS_ERROR_FAILURE;
1463   }
1464 
1465   nsAutoCString origin;
1466   nsresult rv = mPrincipal->GetOrigin(origin);
1467   if (NS_FAILED(rv)) {
1468     return rv;
1469   }
1470 
1471   if (!strcmp("alertshow", aTopic)) {
1472     return pushQuotaManager->NotificationForOriginShown(origin.get());
1473   }
1474   return pushQuotaManager->NotificationForOriginClosed(origin.get());
1475 }
1476 
1477 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1478 MainThreadNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1479                                         const char16_t* aData)
1480 {
1481   AssertIsOnMainThread();
1482   MOZ_ASSERT(mNotificationRef);
1483   Notification* notification = mNotificationRef->GetNotification();
1484   MOZ_ASSERT(notification);
1485   if (!strcmp("alertclickcallback", aTopic)) {
1486     nsCOMPtr<nsPIDOMWindowInner> window = notification->GetOwner();
1487     if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1488       // Window has been closed, this observer is not valid anymore
1489       return NS_ERROR_FAILURE;
1490     }
1491 
1492     bool doDefaultAction = notification->DispatchClickEvent();
1493     if (doDefaultAction) {
1494       nsIDocument* doc = window ? window->GetExtantDoc() : nullptr;
1495       if (doc) {
1496         // Browser UI may use DOMWebNotificationClicked to focus the tab
1497         // from which the event was dispatched.
1498         nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(),
1499                                             NS_LITERAL_STRING("DOMWebNotificationClicked"),
1500                                             true, true);
1501       }
1502     }
1503   } else if (!strcmp("alertfinished", aTopic)) {
1504     notification->UnpersistNotification();
1505     notification->mIsClosed = true;
1506     notification->DispatchTrustedEvent(NS_LITERAL_STRING("close"));
1507   } else if (!strcmp("alertshow", aTopic)) {
1508     notification->DispatchTrustedEvent(NS_LITERAL_STRING("show"));
1509   }
1510   return NS_OK;
1511 }
1512 
1513 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1514 WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1515                                     const char16_t* aData)
1516 {
1517   AssertIsOnMainThread();
1518   MOZ_ASSERT(mNotificationRef);
1519   // For an explanation of why it is OK to pass this rawptr to the event
1520   // runnables, see the Notification class comment.
1521   Notification* notification = mNotificationRef->GetNotification();
1522   // We can't assert notification here since the feature could've unset it.
1523   if (NS_WARN_IF(!notification)) {
1524     return NS_ERROR_FAILURE;
1525   }
1526 
1527   MOZ_ASSERT(notification->mWorkerPrivate);
1528 
1529   RefPtr<WorkerRunnable> r;
1530   if (!strcmp("alertclickcallback", aTopic)) {
1531     nsPIDOMWindowInner* window = nullptr;
1532     if (!notification->mWorkerPrivate->IsServiceWorker()) {
1533       WorkerPrivate* top = notification->mWorkerPrivate;
1534       while (top->GetParent()) {
1535         top = top->GetParent();
1536       }
1537 
1538       window = top->GetWindow();
1539       if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1540         // Window has been closed, this observer is not valid anymore
1541         return NS_ERROR_FAILURE;
1542       }
1543     }
1544 
1545     // Instead of bothering with adding features and other worker lifecycle
1546     // management, we simply hold strongrefs to the window and document.
1547     nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
1548       new nsMainThreadPtrHolder<nsPIDOMWindowInner>(window));
1549 
1550     r = new NotificationClickWorkerRunnable(notification, windowHandle);
1551   } else if (!strcmp("alertfinished", aTopic)) {
1552     notification->UnpersistNotification();
1553     notification->mIsClosed = true;
1554     r = new NotificationEventWorkerRunnable(notification,
1555                                             NS_LITERAL_STRING("close"));
1556   } else if (!strcmp("alertshow", aTopic)) {
1557     r = new NotificationEventWorkerRunnable(notification,
1558                                             NS_LITERAL_STRING("show"));
1559   }
1560 
1561   MOZ_ASSERT(r);
1562   if (!r->Dispatch()) {
1563     NS_WARNING("Could not dispatch event to worker notification");
1564   }
1565   return NS_OK;
1566 }
1567 
1568 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)1569 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
1570                                            const char* aTopic,
1571                                            const char16_t* aData)
1572 {
1573   AssertIsOnMainThread();
1574 
1575   nsAutoCString originSuffix;
1576   nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
1577   if (NS_WARN_IF(NS_FAILED(rv))) {
1578     return rv;
1579   }
1580 
1581   nsCOMPtr<nsIServiceWorkerManager> swm =
1582     mozilla::services::GetServiceWorkerManager();
1583   if (NS_WARN_IF(!swm)) {
1584     return NS_ERROR_FAILURE;
1585   }
1586 
1587   if (!strcmp("alertclickcallback", aTopic)) {
1588     rv = swm->SendNotificationClickEvent(originSuffix,
1589                                          NS_ConvertUTF16toUTF8(mScope),
1590                                          mID,
1591                                          mTitle,
1592                                          mDir,
1593                                          mLang,
1594                                          mBody,
1595                                          mTag,
1596                                          mIcon,
1597                                          mData,
1598                                          mBehavior);
1599     Unused << NS_WARN_IF(NS_FAILED(rv));
1600     return NS_OK;
1601   }
1602 
1603   if (!strcmp("alertfinished", aTopic)) {
1604     nsString origin;
1605     nsresult rv = Notification::GetOrigin(mPrincipal, origin);
1606     if (NS_WARN_IF(NS_FAILED(rv))) {
1607       return rv;
1608     }
1609 
1610     // Remove closed or dismissed persistent notifications.
1611     nsCOMPtr<nsINotificationStorage> notificationStorage =
1612       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1613     if (notificationStorage) {
1614       notificationStorage->Delete(origin, mID);
1615     }
1616 
1617     rv = swm->SendNotificationCloseEvent(originSuffix,
1618                                          NS_ConvertUTF16toUTF8(mScope),
1619                                          mID,
1620                                          mTitle,
1621                                          mDir,
1622                                          mLang,
1623                                          mBody,
1624                                          mTag,
1625                                          mIcon,
1626                                          mData,
1627                                          mBehavior);
1628     Unused << NS_WARN_IF(NS_FAILED(rv));
1629     return NS_OK;
1630   }
1631 
1632   return NS_OK;
1633 }
1634 
1635 bool
IsInPrivateBrowsing()1636 Notification::IsInPrivateBrowsing()
1637 {
1638   AssertIsOnMainThread();
1639 
1640   nsIDocument* doc = nullptr;
1641 
1642   if (mWorkerPrivate) {
1643     doc = mWorkerPrivate->GetDocument();
1644   } else if (GetOwner()) {
1645     doc = GetOwner()->GetExtantDoc();
1646   }
1647 
1648   if (doc) {
1649     nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
1650     return loadContext && loadContext->UsePrivateBrowsing();
1651   }
1652 
1653   if (mWorkerPrivate) {
1654     // Not all workers may have a document, but with Bug 1107516 fixed, they
1655     // should all have a loadcontext.
1656     nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1657     nsCOMPtr<nsILoadContext> loadContext;
1658     NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext),
1659                                   getter_AddRefs(loadContext));
1660     return loadContext && loadContext->UsePrivateBrowsing();
1661   }
1662 
1663   //XXXnsm Should this default to true?
1664   return false;
1665 }
1666 
1667 namespace {
1668   struct StringWriteFunc : public JSONWriteFunc
1669   {
1670     nsAString& mBuffer; // This struct must not outlive this buffer
StringWriteFuncmozilla::dom::__anoneb2ff8230211::StringWriteFunc1671     explicit StringWriteFunc(nsAString& buffer) : mBuffer(buffer) {}
1672 
Writemozilla::dom::__anoneb2ff8230211::StringWriteFunc1673     void Write(const char* aStr)
1674     {
1675       mBuffer.Append(NS_ConvertUTF8toUTF16(aStr));
1676     }
1677   };
1678 }
1679 
1680 void
ShowInternal()1681 Notification::ShowInternal()
1682 {
1683   AssertIsOnMainThread();
1684   MOZ_ASSERT(mTempRef, "Notification should take ownership of itself before"
1685                        "calling ShowInternal!");
1686   // A notification can only have one observer and one call to ShowInternal.
1687   MOZ_ASSERT(!mObserver);
1688 
1689   // Transfer ownership to local scope so we can either release it at the end
1690   // of this function or transfer it to the observer.
1691   UniquePtr<NotificationRef> ownership;
1692   mozilla::Swap(ownership, mTempRef);
1693   MOZ_ASSERT(ownership->GetNotification() == this);
1694 
1695   nsresult rv = PersistNotification();
1696   if (NS_FAILED(rv)) {
1697     NS_WARNING("Could not persist Notification");
1698   }
1699 
1700   nsCOMPtr<nsIAlertsService> alertService =
1701     do_GetService(NS_ALERTSERVICE_CONTRACTID);
1702 
1703   ErrorResult result;
1704   NotificationPermission permission = NotificationPermission::Denied;
1705   if (mWorkerPrivate) {
1706     permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
1707   } else {
1708     permission = GetPermissionInternal(GetOwner(), result);
1709   }
1710   // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1711   MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
1712   result.SuppressException();
1713   if (permission != NotificationPermission::Granted || !alertService) {
1714     if (mWorkerPrivate) {
1715       RefPtr<NotificationEventWorkerRunnable> r =
1716         new NotificationEventWorkerRunnable(this,
1717                                             NS_LITERAL_STRING("error"));
1718       if (!r->Dispatch()) {
1719         NS_WARNING("Could not dispatch event to worker notification");
1720       }
1721     } else {
1722       DispatchTrustedEvent(NS_LITERAL_STRING("error"));
1723     }
1724     return;
1725   }
1726 
1727   nsAutoString iconUrl;
1728   nsAutoString soundUrl;
1729   ResolveIconAndSoundURL(iconUrl, soundUrl);
1730 
1731   bool isPersistent = false;
1732   nsCOMPtr<nsIObserver> observer;
1733   if (mScope.IsEmpty()) {
1734     // Ownership passed to observer.
1735     if (mWorkerPrivate) {
1736       // Scope better be set on ServiceWorker initiated requests.
1737       MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
1738       // Keep a pointer so that the feature can tell the observer not to release
1739       // the notification.
1740       mObserver = new WorkerNotificationObserver(Move(ownership));
1741       observer = mObserver;
1742     } else {
1743       observer = new MainThreadNotificationObserver(Move(ownership));
1744     }
1745   } else {
1746     isPersistent = true;
1747     // This observer does not care about the Notification. It will be released
1748     // at the end of this function.
1749     //
1750     // The observer is wholly owned by the NotificationObserver passed to the alert service.
1751     nsAutoString behavior;
1752     if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
1753       behavior.Truncate();
1754     }
1755     observer = new ServiceWorkerNotificationObserver(mScope,
1756                                                      GetPrincipal(),
1757                                                      mID,
1758                                                      mTitle,
1759                                                      DirectionToString(mDir),
1760                                                      mLang,
1761                                                      mBody,
1762                                                      mTag,
1763                                                      iconUrl,
1764                                                      mDataAsBase64,
1765                                                      behavior);
1766   }
1767   MOZ_ASSERT(observer);
1768   nsCOMPtr<nsIObserver> alertObserver = new NotificationObserver(observer,
1769                                                                  GetPrincipal(),
1770                                                                  IsInPrivateBrowsing());
1771 
1772 
1773   // In the case of IPC, the parent process uses the cookie to map to
1774   // nsIObserver. Thus the cookie must be unique to differentiate observers.
1775   nsString uniqueCookie = NS_LITERAL_STRING("notification:");
1776   uniqueCookie.AppendInt(sCount++);
1777   bool inPrivateBrowsing = IsInPrivateBrowsing();
1778 
1779   bool requireInteraction = mRequireInteraction;
1780   if (!Preferences::GetBool("dom.webnotifications.requireinteraction.enabled", false)) {
1781     requireInteraction = false;
1782   }
1783 
1784   nsAutoString alertName;
1785   GetAlertName(alertName);
1786   nsCOMPtr<nsIAlertNotification> alert =
1787     do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
1788   NS_ENSURE_TRUE_VOID(alert);
1789   nsIPrincipal* principal = GetPrincipal();
1790   rv = alert->Init(alertName, iconUrl, mTitle, mBody,
1791                    true,
1792                    uniqueCookie,
1793                    DirectionToString(mDir),
1794                    mLang,
1795                    mDataAsBase64,
1796                    GetPrincipal(),
1797                    inPrivateBrowsing,
1798                    requireInteraction);
1799   NS_ENSURE_SUCCESS_VOID(rv);
1800 
1801   if (isPersistent) {
1802     nsAutoString persistentData;
1803 
1804     JSONWriter w(MakeUnique<StringWriteFunc>(persistentData));
1805     w.Start();
1806 
1807     nsAutoString origin;
1808     Notification::GetOrigin(principal, origin);
1809     w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin).get());
1810 
1811     w.StringProperty("id", NS_ConvertUTF16toUTF8(mID).get());
1812 
1813     nsAutoCString originSuffix;
1814     principal->GetOriginSuffix(originSuffix);
1815     w.StringProperty("originSuffix", originSuffix.get());
1816 
1817     w.End();
1818 
1819     alertService->ShowPersistentNotification(persistentData, alert, alertObserver);
1820   } else {
1821     alertService->ShowAlert(alert, alertObserver);
1822   }
1823 }
1824 
1825 /* static */ bool
RequestPermissionEnabledForScope(JSContext * aCx,JSObject *)1826 Notification::RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */)
1827 {
1828   // requestPermission() is not allowed on workers. The calling page should ask
1829   // for permission on the worker's behalf. This is to prevent 'which window
1830   // should show the browser pop-up'. See discussion:
1831   // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1832   return NS_IsMainThread();
1833 }
1834 
1835 already_AddRefed<Promise>
RequestPermission(const GlobalObject & aGlobal,const Optional<OwningNonNull<NotificationPermissionCallback>> & aCallback,ErrorResult & aRv)1836 Notification::RequestPermission(const GlobalObject& aGlobal,
1837                                 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
1838                                 ErrorResult& aRv)
1839 {
1840   // Get principal from global to make permission request for notifications.
1841   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
1842   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports());
1843   if (!sop) {
1844     aRv.Throw(NS_ERROR_UNEXPECTED);
1845     return nullptr;
1846   }
1847   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1848 
1849   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
1850   RefPtr<Promise> promise = Promise::Create(global, aRv);
1851   if (aRv.Failed()) {
1852     return nullptr;
1853   }
1854   NotificationPermissionCallback* permissionCallback = nullptr;
1855   if (aCallback.WasPassed()) {
1856     permissionCallback = &aCallback.Value();
1857   }
1858   nsCOMPtr<nsIRunnable> request =
1859     new NotificationPermissionRequest(principal, window, promise, permissionCallback);
1860 
1861   NS_DispatchToMainThread(request);
1862   return promise.forget();
1863 }
1864 
1865 // static
1866 NotificationPermission
GetPermission(const GlobalObject & aGlobal,ErrorResult & aRv)1867 Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv)
1868 {
1869   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1870   return GetPermission(global, aRv);
1871 }
1872 
1873 // static
1874 NotificationPermission
GetPermission(nsIGlobalObject * aGlobal,ErrorResult & aRv)1875 Notification::GetPermission(nsIGlobalObject* aGlobal, ErrorResult& aRv)
1876 {
1877   if (NS_IsMainThread()) {
1878     return GetPermissionInternal(aGlobal, aRv);
1879   } else {
1880     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1881     MOZ_ASSERT(worker);
1882     RefPtr<GetPermissionRunnable> r =
1883       new GetPermissionRunnable(worker);
1884     r->Dispatch(aRv);
1885     if (aRv.Failed()) {
1886       return NotificationPermission::Denied;
1887     }
1888 
1889     return r->GetPermission();
1890   }
1891 }
1892 
1893 /* static */ NotificationPermission
GetPermissionInternal(nsISupports * aGlobal,ErrorResult & aRv)1894 Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
1895 {
1896   // Get principal from global to check permission for notifications.
1897   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
1898   if (!sop) {
1899     aRv.Throw(NS_ERROR_UNEXPECTED);
1900     return NotificationPermission::Denied;
1901   }
1902 
1903   nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1904   return GetPermissionInternal(principal, aRv);
1905 }
1906 
1907 /* static */ NotificationPermission
GetPermissionInternal(nsIPrincipal * aPrincipal,ErrorResult & aRv)1908 Notification::GetPermissionInternal(nsIPrincipal* aPrincipal,
1909                                     ErrorResult& aRv)
1910 {
1911   AssertIsOnMainThread();
1912   MOZ_ASSERT(aPrincipal);
1913 
1914   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
1915     return NotificationPermission::Granted;
1916   } else {
1917     // Allow files to show notifications by default.
1918     nsCOMPtr<nsIURI> uri;
1919     aPrincipal->GetURI(getter_AddRefs(uri));
1920     if (uri) {
1921       bool isFile;
1922       uri->SchemeIs("file", &isFile);
1923       if (isFile) {
1924         return NotificationPermission::Granted;
1925       }
1926     }
1927   }
1928 
1929   // We also allow notifications is they are pref'ed on.
1930   if (Preferences::GetBool("notification.prompt.testing", false)) {
1931     if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1932       return NotificationPermission::Granted;
1933     } else {
1934       return NotificationPermission::Denied;
1935     }
1936   }
1937 
1938   return TestPermission(aPrincipal);
1939 }
1940 
1941 /* static */ NotificationPermission
TestPermission(nsIPrincipal * aPrincipal)1942 Notification::TestPermission(nsIPrincipal* aPrincipal)
1943 {
1944   AssertIsOnMainThread();
1945 
1946   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
1947 
1948   nsCOMPtr<nsIPermissionManager> permissionManager =
1949     services::GetPermissionManager();
1950   if (!permissionManager) {
1951     return NotificationPermission::Default;
1952   }
1953 
1954   permissionManager->TestExactPermissionFromPrincipal(aPrincipal,
1955                                                       "desktop-notification",
1956                                                       &permission);
1957 
1958   // Convert the result to one of the enum types.
1959   switch (permission) {
1960   case nsIPermissionManager::ALLOW_ACTION:
1961     return NotificationPermission::Granted;
1962   case nsIPermissionManager::DENY_ACTION:
1963     return NotificationPermission::Denied;
1964   default:
1965     return NotificationPermission::Default;
1966   }
1967 }
1968 
1969 nsresult
ResolveIconAndSoundURL(nsString & iconUrl,nsString & soundUrl)1970 Notification::ResolveIconAndSoundURL(nsString& iconUrl, nsString& soundUrl)
1971 {
1972   AssertIsOnMainThread();
1973   nsresult rv = NS_OK;
1974 
1975   nsCOMPtr<nsIURI> baseUri;
1976 
1977   // XXXnsm If I understand correctly, the character encoding for resolving
1978   // URIs in new specs is dictated by the URL spec, which states that unless
1979   // the URL parser is passed an override encoding, the charset to be used is
1980   // UTF-8. The new Notification icon/sound specification just says to use the
1981   // Fetch API, where the Request constructor defers to URL parsing specifying
1982   // the API base URL and no override encoding. So we've to use UTF-8 on
1983   // workers, but for backwards compat keeping it document charset on main
1984   // thread.
1985   const char* charset = "UTF-8";
1986 
1987   if (mWorkerPrivate) {
1988     baseUri = mWorkerPrivate->GetBaseURI();
1989   } else {
1990     nsIDocument* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1991     if (doc) {
1992       baseUri = doc->GetBaseURI();
1993       charset = doc->GetDocumentCharacterSet().get();
1994     } else {
1995       NS_WARNING("No document found for main thread notification!");
1996       return NS_ERROR_FAILURE;
1997     }
1998   }
1999 
2000   if (baseUri) {
2001     if (mIconUrl.Length() > 0) {
2002       nsCOMPtr<nsIURI> srcUri;
2003       rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, charset, baseUri);
2004       if (NS_SUCCEEDED(rv)) {
2005         nsAutoCString src;
2006         srcUri->GetSpec(src);
2007         iconUrl = NS_ConvertUTF8toUTF16(src);
2008       }
2009     }
2010     if (mBehavior.mSoundFile.Length() > 0) {
2011       nsCOMPtr<nsIURI> srcUri;
2012       rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, charset, baseUri);
2013       if (NS_SUCCEEDED(rv)) {
2014         nsAutoCString src;
2015         srcUri->GetSpec(src);
2016         soundUrl = NS_ConvertUTF8toUTF16(src);
2017       }
2018     }
2019   }
2020 
2021   return rv;
2022 }
2023 
2024 already_AddRefed<Promise>
Get(nsPIDOMWindowInner * aWindow,const GetNotificationOptions & aFilter,const nsAString & aScope,ErrorResult & aRv)2025 Notification::Get(nsPIDOMWindowInner* aWindow,
2026                   const GetNotificationOptions& aFilter,
2027                   const nsAString& aScope,
2028                   ErrorResult& aRv)
2029 {
2030   MOZ_ASSERT(aWindow);
2031 
2032   nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
2033   if (!doc) {
2034     aRv.Throw(NS_ERROR_UNEXPECTED);
2035     return nullptr;
2036   }
2037 
2038   nsString origin;
2039   aRv = GetOrigin(doc->NodePrincipal(), origin);
2040   if (aRv.Failed()) {
2041     return nullptr;
2042   }
2043 
2044   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aWindow);
2045   RefPtr<Promise> promise = Promise::Create(global, aRv);
2046   if (aRv.Failed()) {
2047     return nullptr;
2048   }
2049 
2050   nsCOMPtr<nsINotificationStorageCallback> callback =
2051     new NotificationStorageCallback(global, aScope, promise);
2052 
2053   RefPtr<NotificationGetRunnable> r =
2054     new NotificationGetRunnable(origin, aFilter.mTag, callback);
2055 
2056   aRv = NS_DispatchToMainThread(r);
2057   if (NS_WARN_IF(aRv.Failed())) {
2058     return nullptr;
2059   }
2060 
2061   return promise.forget();
2062 }
2063 
2064 already_AddRefed<Promise>
Get(const GlobalObject & aGlobal,const GetNotificationOptions & aFilter,ErrorResult & aRv)2065 Notification::Get(const GlobalObject& aGlobal,
2066                   const GetNotificationOptions& aFilter,
2067                   ErrorResult& aRv)
2068 {
2069   AssertIsOnMainThread();
2070   nsCOMPtr<nsIGlobalObject> global =
2071     do_QueryInterface(aGlobal.GetAsSupports());
2072   MOZ_ASSERT(global);
2073   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
2074 
2075   return Get(window, aFilter, EmptyString(), aRv);
2076 }
2077 
2078 class WorkerGetResultRunnable final : public NotificationWorkerRunnable
2079 {
2080   RefPtr<PromiseWorkerProxy> mPromiseProxy;
2081   const nsTArray<NotificationStrings> mStrings;
2082 public:
WorkerGetResultRunnable(WorkerPrivate * aWorkerPrivate,PromiseWorkerProxy * aPromiseProxy,const nsTArray<NotificationStrings> && aStrings)2083   WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
2084                           PromiseWorkerProxy* aPromiseProxy,
2085                           const nsTArray<NotificationStrings>&& aStrings)
2086     : NotificationWorkerRunnable(aWorkerPrivate)
2087     , mPromiseProxy(aPromiseProxy)
2088     , mStrings(Move(aStrings))
2089   {
2090   }
2091 
2092   void
WorkerRunInternal(WorkerPrivate * aWorkerPrivate)2093   WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override
2094   {
2095     RefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise();
2096 
2097     ErrorResult result;
2098     AutoTArray<RefPtr<Notification>, 5> notifications;
2099     for (uint32_t i = 0; i < mStrings.Length(); ++i) {
2100       RefPtr<Notification> n =
2101         Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(),
2102                                           mStrings[i].mID,
2103                                           mStrings[i].mTitle,
2104                                           mStrings[i].mDir,
2105                                           mStrings[i].mLang,
2106                                           mStrings[i].mBody,
2107                                           mStrings[i].mTag,
2108                                           mStrings[i].mIcon,
2109                                           mStrings[i].mData,
2110                                           /* mStrings[i].mBehavior, not
2111                                            * supported */
2112                                           mStrings[i].mServiceWorkerRegistrationScope,
2113                                           result);
2114 
2115       n->SetStoredState(true);
2116       Unused << NS_WARN_IF(result.Failed());
2117       if (!result.Failed()) {
2118         notifications.AppendElement(n.forget());
2119       }
2120     }
2121 
2122     workerPromise->MaybeResolve(notifications);
2123     mPromiseProxy->CleanUp();
2124   }
2125 };
2126 
2127 class WorkerGetCallback final : public ScopeCheckingGetCallback
2128 {
2129   RefPtr<PromiseWorkerProxy> mPromiseProxy;
2130 public:
2131   NS_DECL_ISUPPORTS
2132 
WorkerGetCallback(PromiseWorkerProxy * aProxy,const nsAString & aScope)2133   WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
2134     : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy)
2135   {
2136     AssertIsOnMainThread();
2137     MOZ_ASSERT(aProxy);
2138   }
2139 
Done()2140   NS_IMETHOD Done() final
2141   {
2142     AssertIsOnMainThread();
2143     MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
2144 
2145     RefPtr<PromiseWorkerProxy> proxy = mPromiseProxy.forget();
2146     MutexAutoLock lock(proxy->Lock());
2147     if (proxy->CleanedUp()) {
2148       return NS_OK;
2149     }
2150 
2151     RefPtr<WorkerGetResultRunnable> r =
2152       new WorkerGetResultRunnable(proxy->GetWorkerPrivate(),
2153                                   proxy,
2154                                   Move(mStrings));
2155 
2156     r->Dispatch();
2157     return NS_OK;
2158   }
2159 
2160 private:
~WorkerGetCallback()2161   ~WorkerGetCallback()
2162   {}
2163 };
2164 
2165 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
2166 
2167 class WorkerGetRunnable final : public Runnable
2168 {
2169   RefPtr<PromiseWorkerProxy> mPromiseProxy;
2170   const nsString mTag;
2171   const nsString mScope;
2172 public:
WorkerGetRunnable(PromiseWorkerProxy * aProxy,const nsAString & aTag,const nsAString & aScope)2173   WorkerGetRunnable(PromiseWorkerProxy* aProxy,
2174                     const nsAString& aTag,
2175                     const nsAString& aScope)
2176     : mPromiseProxy(aProxy), mTag(aTag), mScope(aScope)
2177   {
2178     MOZ_ASSERT(mPromiseProxy);
2179   }
2180 
2181   NS_IMETHOD
Run()2182   Run() override
2183   {
2184     AssertIsOnMainThread();
2185     nsCOMPtr<nsINotificationStorageCallback> callback =
2186       new WorkerGetCallback(mPromiseProxy, mScope);
2187 
2188     nsresult rv;
2189     nsCOMPtr<nsINotificationStorage> notificationStorage =
2190       do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
2191     if (NS_WARN_IF(NS_FAILED(rv))) {
2192       callback->Done();
2193       return rv;
2194     }
2195 
2196     MutexAutoLock lock(mPromiseProxy->Lock());
2197     if (mPromiseProxy->CleanedUp()) {
2198       return NS_OK;
2199     }
2200 
2201     nsString origin;
2202     rv =
2203       Notification::GetOrigin(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(),
2204                               origin);
2205     if (NS_WARN_IF(NS_FAILED(rv))) {
2206       callback->Done();
2207       return rv;
2208     }
2209 
2210     rv = notificationStorage->Get(origin, mTag, callback);
2211     if (NS_WARN_IF(NS_FAILED(rv))) {
2212       callback->Done();
2213       return rv;
2214     }
2215 
2216     return NS_OK;
2217   }
2218 private:
~WorkerGetRunnable()2219   ~WorkerGetRunnable()
2220   {}
2221 };
2222 
2223 already_AddRefed<Promise>
WorkerGet(WorkerPrivate * aWorkerPrivate,const GetNotificationOptions & aFilter,const nsAString & aScope,ErrorResult & aRv)2224 Notification::WorkerGet(WorkerPrivate* aWorkerPrivate,
2225                         const GetNotificationOptions& aFilter,
2226                         const nsAString& aScope,
2227                         ErrorResult& aRv)
2228 {
2229   MOZ_ASSERT(aWorkerPrivate);
2230   aWorkerPrivate->AssertIsOnWorkerThread();
2231   RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
2232   if (NS_WARN_IF(aRv.Failed())) {
2233     return nullptr;
2234   }
2235 
2236   RefPtr<PromiseWorkerProxy> proxy =
2237     PromiseWorkerProxy::Create(aWorkerPrivate, p);
2238   if (!proxy) {
2239     aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2240     return nullptr;
2241   }
2242 
2243   RefPtr<WorkerGetRunnable> r =
2244     new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
2245   // Since this is called from script via
2246   // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
2247   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
2248   return p.forget();
2249 }
2250 
2251 JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)2252 Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
2253 {
2254   return mozilla::dom::NotificationBinding::Wrap(aCx, this, aGivenProto);
2255 }
2256 
2257 void
Close()2258 Notification::Close()
2259 {
2260   AssertIsOnTargetThread();
2261   auto ref = MakeUnique<NotificationRef>(this);
2262   if (!ref->Initialized()) {
2263     return;
2264   }
2265 
2266   nsCOMPtr<nsIRunnable> closeNotificationTask =
2267     new NotificationTask(Move(ref), NotificationTask::eClose);
2268   nsresult rv = NS_DispatchToMainThread(closeNotificationTask);
2269 
2270   if (NS_FAILED(rv)) {
2271     DispatchTrustedEvent(NS_LITERAL_STRING("error"));
2272     // If dispatch fails, NotificationTask will release the ref when it goes
2273     // out of scope at the end of this function.
2274   }
2275 }
2276 
2277 void
CloseInternal()2278 Notification::CloseInternal()
2279 {
2280   AssertIsOnMainThread();
2281   // Transfer ownership (if any) to local scope so we can release it at the end
2282   // of this function. This is relevant when the call is from
2283   // NotificationTask::Run().
2284   UniquePtr<NotificationRef> ownership;
2285   mozilla::Swap(ownership, mTempRef);
2286 
2287   SetAlertName();
2288   UnpersistNotification();
2289   if (!mIsClosed) {
2290     nsCOMPtr<nsIAlertsService> alertService =
2291       do_GetService(NS_ALERTSERVICE_CONTRACTID);
2292     if (alertService) {
2293       nsAutoString alertName;
2294       GetAlertName(alertName);
2295       alertService->CloseAlert(alertName, GetPrincipal());
2296     }
2297   }
2298 }
2299 
2300 nsresult
GetOrigin(nsIPrincipal * aPrincipal,nsString & aOrigin)2301 Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin)
2302 {
2303   if (!aPrincipal) {
2304     return NS_ERROR_FAILURE;
2305   }
2306 
2307   nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, aOrigin);
2308   NS_ENSURE_SUCCESS(rv, rv);
2309 
2310   return NS_OK;
2311 }
2312 
2313 bool
RequireInteraction() const2314 Notification::RequireInteraction() const
2315 {
2316   return mRequireInteraction;
2317 }
2318 
2319 void
GetData(JSContext * aCx,JS::MutableHandle<JS::Value> aRetval)2320 Notification::GetData(JSContext* aCx,
2321                       JS::MutableHandle<JS::Value> aRetval)
2322 {
2323   if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
2324     nsresult rv;
2325     RefPtr<nsStructuredCloneContainer> container =
2326       new nsStructuredCloneContainer();
2327     rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
2328     if (NS_WARN_IF(NS_FAILED(rv))) {
2329       aRetval.setNull();
2330       return;
2331     }
2332 
2333     JS::Rooted<JS::Value> data(aCx);
2334     rv = container->DeserializeToJsval(aCx, &data);
2335     if (NS_WARN_IF(NS_FAILED(rv))) {
2336       aRetval.setNull();
2337       return;
2338     }
2339 
2340     if (data.isGCThing()) {
2341       mozilla::HoldJSObjects(this);
2342     }
2343     mData = data;
2344   }
2345   if (mData.isNull()) {
2346     aRetval.setNull();
2347     return;
2348   }
2349 
2350   aRetval.set(mData);
2351 }
2352 
2353 void
InitFromJSVal(JSContext * aCx,JS::Handle<JS::Value> aData,ErrorResult & aRv)2354 Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
2355                             ErrorResult& aRv)
2356 {
2357   if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
2358     return;
2359   }
2360   RefPtr<nsStructuredCloneContainer> dataObjectContainer =
2361     new nsStructuredCloneContainer();
2362   aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
2363   if (NS_WARN_IF(aRv.Failed())) {
2364     return;
2365   }
2366 
2367   dataObjectContainer->GetDataAsBase64(mDataAsBase64);
2368 }
2369 
InitFromBase64(const nsAString & aData,ErrorResult & aRv)2370 void Notification::InitFromBase64(const nsAString& aData, ErrorResult& aRv)
2371 {
2372   if (!mDataAsBase64.IsEmpty() || aData.IsEmpty()) {
2373     return;
2374   }
2375 
2376   // To and fro to ensure it is valid base64.
2377   RefPtr<nsStructuredCloneContainer> container =
2378     new nsStructuredCloneContainer();
2379   aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION);
2380   if (NS_WARN_IF(aRv.Failed())) {
2381     return;
2382   }
2383 
2384   container->GetDataAsBase64(mDataAsBase64);
2385 }
2386 
2387 bool
AddRefObject()2388 Notification::AddRefObject()
2389 {
2390   AssertIsOnTargetThread();
2391   MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerHolder, mTaskCount == 0);
2392   MOZ_ASSERT_IF(mWorkerPrivate && mWorkerHolder, mTaskCount > 0);
2393   if (mWorkerPrivate && !mWorkerHolder) {
2394     if (!RegisterWorkerHolder()) {
2395       return false;
2396     }
2397   }
2398   AddRef();
2399   ++mTaskCount;
2400   return true;
2401 }
2402 
2403 void
ReleaseObject()2404 Notification::ReleaseObject()
2405 {
2406   AssertIsOnTargetThread();
2407   MOZ_ASSERT(mTaskCount > 0);
2408   MOZ_ASSERT_IF(mWorkerPrivate, mWorkerHolder);
2409 
2410   --mTaskCount;
2411   if (mWorkerPrivate && mTaskCount == 0) {
2412     UnregisterWorkerHolder();
2413   }
2414   Release();
2415 }
2416 
NotificationWorkerHolder(Notification * aNotification)2417 NotificationWorkerHolder::NotificationWorkerHolder(Notification* aNotification)
2418   : mNotification(aNotification)
2419 {
2420   MOZ_ASSERT(mNotification->mWorkerPrivate);
2421   mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
2422 }
2423 
2424 /*
2425  * Called from the worker, runs on main thread, blocks worker.
2426  *
2427  * We can freely access mNotification here because the feature supplied it and
2428  * the Notification owns the feature.
2429  */
2430 class CloseNotificationRunnable final
2431   : public WorkerMainThreadRunnable
2432 {
2433   Notification* mNotification;
2434   bool mHadObserver;
2435 
2436   public:
CloseNotificationRunnable(Notification * aNotification)2437   explicit CloseNotificationRunnable(Notification* aNotification)
2438     : WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
2439                                NS_LITERAL_CSTRING("Notification :: Close Notification"))
2440     , mNotification(aNotification)
2441     , mHadObserver(false)
2442   {}
2443 
2444   bool
MainThreadRun()2445   MainThreadRun() override
2446   {
2447     if (mNotification->mObserver) {
2448       // The Notify() take's responsibility of releasing the Notification.
2449       mNotification->mObserver->ForgetNotification();
2450       mNotification->mObserver = nullptr;
2451       mHadObserver = true;
2452     }
2453     mNotification->CloseInternal();
2454     return true;
2455   }
2456 
2457   bool
HadObserver()2458   HadObserver()
2459   {
2460     return mHadObserver;
2461   }
2462 };
2463 
2464 bool
Notify(Status aStatus)2465 NotificationWorkerHolder::Notify(Status aStatus)
2466 {
2467   if (aStatus >= Canceling) {
2468     // CloseNotificationRunnable blocks the worker by pushing a sync event loop
2469     // on the stack. Meanwhile, WorkerControlRunnables dispatched to the worker
2470     // can still continue running. One of these is
2471     // ReleaseNotificationControlRunnable that releases the notification,
2472     // invalidating the notification and this feature. We hold this reference to
2473     // keep the notification valid until we are done with it.
2474     //
2475     // An example of when the control runnable could get dispatched to the
2476     // worker is if a Notification is created and the worker is immediately
2477     // closed, but there is no permission to show it so that the main thread
2478     // immediately drops the NotificationRef. In this case, this function blocks
2479     // on the main thread, but the main thread dispatches the control runnable,
2480     // invalidating mNotification.
2481     RefPtr<Notification> kungFuDeathGrip = mNotification;
2482 
2483     // Dispatched to main thread, blocks on closing the Notification.
2484     RefPtr<CloseNotificationRunnable> r =
2485       new CloseNotificationRunnable(kungFuDeathGrip);
2486     ErrorResult rv;
2487     r->Dispatch(rv);
2488     // XXXbz I'm told throwing and returning false from here is pointless (and
2489     // also that doing sync stuff from here is really weird), so I guess we just
2490     // suppress the exception on rv, if any.
2491     rv.SuppressException();
2492 
2493     // Only call ReleaseObject() to match the observer's NotificationRef
2494     // ownership (since CloseNotificationRunnable asked the observer to drop the
2495     // reference to the notification).
2496     if (r->HadObserver()) {
2497       kungFuDeathGrip->ReleaseObject();
2498     }
2499 
2500     // From this point we cannot touch properties of this feature because
2501     // ReleaseObject() may have led to the notification going away and the
2502     // notification owns this feature!
2503   }
2504   return true;
2505 }
2506 
2507 bool
RegisterWorkerHolder()2508 Notification::RegisterWorkerHolder()
2509 {
2510   MOZ_ASSERT(mWorkerPrivate);
2511   mWorkerPrivate->AssertIsOnWorkerThread();
2512   MOZ_ASSERT(!mWorkerHolder);
2513   mWorkerHolder = MakeUnique<NotificationWorkerHolder>(this);
2514   if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) {
2515     return false;
2516   }
2517 
2518   return true;
2519 }
2520 
2521 void
UnregisterWorkerHolder()2522 Notification::UnregisterWorkerHolder()
2523 {
2524   MOZ_ASSERT(mWorkerPrivate);
2525   mWorkerPrivate->AssertIsOnWorkerThread();
2526   MOZ_ASSERT(mWorkerHolder);
2527   mWorkerHolder = nullptr;
2528 }
2529 
2530 /*
2531  * Checks:
2532  * 1) Is aWorker allowed to show a notification for scope?
2533  * 2) Is aWorker an active worker?
2534  *
2535  * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2536  */
2537 class CheckLoadRunnable final : public WorkerMainThreadRunnable
2538 {
2539   nsresult mRv;
2540   nsCString mScope;
2541 
2542 public:
CheckLoadRunnable(WorkerPrivate * aWorker,const nsACString & aScope)2543   explicit CheckLoadRunnable(WorkerPrivate* aWorker, const nsACString& aScope)
2544     : WorkerMainThreadRunnable(aWorker,
2545                                NS_LITERAL_CSTRING("Notification :: Check Load"))
2546     , mRv(NS_ERROR_DOM_SECURITY_ERR)
2547     , mScope(aScope)
2548   { }
2549 
2550   bool
MainThreadRun()2551   MainThreadRun() override
2552   {
2553     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
2554     mRv = CheckScope(principal, mScope);
2555 
2556     if (NS_FAILED(mRv)) {
2557       return true;
2558     }
2559 
2560     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
2561     if (!swm) {
2562       // browser shutdown began
2563       mRv = NS_ERROR_FAILURE;
2564       return true;
2565     }
2566 
2567     RefPtr<ServiceWorkerRegistrationInfo> registration =
2568       swm->GetRegistration(principal, mScope);
2569 
2570     // This is coming from a ServiceWorkerRegistration.
2571     MOZ_ASSERT(registration);
2572 
2573     if (!registration->GetActive() ||
2574         registration->GetActive()->ID() != mWorkerPrivate->ServiceWorkerID()) {
2575       mRv = NS_ERROR_NOT_AVAILABLE;
2576     }
2577 
2578     return true;
2579   }
2580 
2581   nsresult
Result()2582   Result()
2583   {
2584     return mRv;
2585   }
2586 
2587 };
2588 
2589 /* static */
2590 already_AddRefed<Promise>
ShowPersistentNotification(JSContext * aCx,nsIGlobalObject * aGlobal,const nsAString & aScope,const nsAString & aTitle,const NotificationOptions & aOptions,ErrorResult & aRv)2591 Notification::ShowPersistentNotification(JSContext* aCx,
2592                                          nsIGlobalObject *aGlobal,
2593                                          const nsAString& aScope,
2594                                          const nsAString& aTitle,
2595                                          const NotificationOptions& aOptions,
2596                                          ErrorResult& aRv)
2597 {
2598   MOZ_ASSERT(aGlobal);
2599 
2600   // Validate scope.
2601   // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2602   // thread. On calls from content, we can be sure the scope is valid since
2603   // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2604   // debug only? The problem is that there would be different semantics in
2605   // debug and non-debug builds in such a case.
2606   if (NS_IsMainThread()) {
2607     nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
2608     if (NS_WARN_IF(!sop)) {
2609       aRv.Throw(NS_ERROR_UNEXPECTED);
2610       return nullptr;
2611     }
2612 
2613     nsIPrincipal* principal = sop->GetPrincipal();
2614     if (NS_WARN_IF(!principal)) {
2615       aRv.Throw(NS_ERROR_UNEXPECTED);
2616       return nullptr;
2617     }
2618 
2619     aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope));
2620     if (NS_WARN_IF(aRv.Failed())) {
2621       aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2622       return nullptr;
2623     }
2624   } else {
2625     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
2626     MOZ_ASSERT(worker);
2627     worker->AssertIsOnWorkerThread();
2628     RefPtr<CheckLoadRunnable> loadChecker =
2629       new CheckLoadRunnable(worker, NS_ConvertUTF16toUTF8(aScope));
2630     loadChecker->Dispatch(aRv);
2631     if (aRv.Failed()) {
2632       return nullptr;
2633     }
2634 
2635     if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
2636       if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
2637         aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(aScope);
2638       } else {
2639         aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2640       }
2641       return nullptr;
2642     }
2643   }
2644 
2645 
2646   RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
2647   if (NS_WARN_IF(aRv.Failed())) {
2648     return nullptr;
2649   }
2650 
2651   // We check permission here rather than pass the Promise to NotificationTask
2652   // which leads to uglier code.
2653   NotificationPermission permission = GetPermission(aGlobal, aRv);
2654 
2655   // "If permission for notification's origin is not "granted", reject promise with a TypeError exception, and terminate these substeps."
2656   if (NS_WARN_IF(aRv.Failed()) || permission == NotificationPermission::Denied) {
2657     ErrorResult result;
2658     result.ThrowTypeError<MSG_NOTIFICATION_PERMISSION_DENIED>();
2659     p->MaybeReject(result);
2660     return p.forget();
2661   }
2662 
2663   // "Otherwise, resolve promise with undefined."
2664   // The Notification may still not be shown due to other errors, but the spec
2665   // is not concerned with those.
2666   p->MaybeResolveWithUndefined();
2667 
2668   RefPtr<Notification> notification =
2669     CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
2670   if (NS_WARN_IF(aRv.Failed())) {
2671     return nullptr;
2672   }
2673 
2674   return p.forget();
2675 }
2676 
2677 /* static */ already_AddRefed<Notification>
CreateAndShow(JSContext * aCx,nsIGlobalObject * aGlobal,const nsAString & aTitle,const NotificationOptions & aOptions,const nsAString & aScope,ErrorResult & aRv)2678 Notification::CreateAndShow(JSContext* aCx,
2679                             nsIGlobalObject* aGlobal,
2680                             const nsAString& aTitle,
2681                             const NotificationOptions& aOptions,
2682                             const nsAString& aScope,
2683                             ErrorResult& aRv)
2684 {
2685   MOZ_ASSERT(aGlobal);
2686 
2687   RefPtr<Notification> notification = CreateInternal(aGlobal, EmptyString(),
2688                                                      aTitle, aOptions);
2689 
2690   // Make a structured clone of the aOptions.mData object
2691   JS::Rooted<JS::Value> data(aCx, aOptions.mData);
2692   notification->InitFromJSVal(aCx, data, aRv);
2693   if (NS_WARN_IF(aRv.Failed())) {
2694     return nullptr;
2695   }
2696 
2697   notification->SetScope(aScope);
2698 
2699   auto ref = MakeUnique<NotificationRef>(notification);
2700   if (NS_WARN_IF(!ref->Initialized())) {
2701     aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2702     return nullptr;
2703   }
2704 
2705   // Queue a task to show the notification.
2706   nsCOMPtr<nsIRunnable> showNotificationTask =
2707     new NotificationTask(Move(ref), NotificationTask::eShow);
2708   nsresult rv = NS_DispatchToMainThread(showNotificationTask);
2709   if (NS_WARN_IF(NS_FAILED(rv))) {
2710     notification->DispatchTrustedEvent(NS_LITERAL_STRING("error"));
2711   }
2712 
2713   return notification.forget();
2714 }
2715 
2716 /* static */ nsresult
RemovePermission(nsIPrincipal * aPrincipal)2717 Notification::RemovePermission(nsIPrincipal* aPrincipal)
2718 {
2719   MOZ_ASSERT(XRE_IsParentProcess());
2720   nsCOMPtr<nsIPermissionManager> permissionManager =
2721     mozilla::services::GetPermissionManager();
2722   if (!permissionManager) {
2723     return NS_ERROR_FAILURE;
2724   }
2725   permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification");
2726   return NS_OK;
2727 }
2728 
2729 /* static */ nsresult
OpenSettings(nsIPrincipal * aPrincipal)2730 Notification::OpenSettings(nsIPrincipal* aPrincipal)
2731 {
2732   MOZ_ASSERT(XRE_IsParentProcess());
2733   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2734   if (!obs) {
2735     return NS_ERROR_FAILURE;
2736   }
2737   // Notify other observers so they can show settings UI.
2738   obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
2739   return NS_OK;
2740 }
2741 
2742 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)2743 Notification::Observe(nsISupports* aSubject, const char* aTopic,
2744                       const char16_t* aData)
2745 {
2746   AssertIsOnMainThread();
2747 
2748   if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
2749       !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
2750 
2751     nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
2752     if (SameCOMIdentity(aSubject, window)) {
2753       nsCOMPtr<nsIObserverService> obs =
2754         mozilla::services::GetObserverService();
2755       if (obs) {
2756         obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
2757         obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
2758       }
2759 
2760       CloseInternal();
2761     }
2762   }
2763 
2764   return NS_OK;
2765 }
2766 
2767 } // namespace dom
2768 } // namespace mozilla
2769 
2770