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