Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / notification / Notification.cpp
blobfb5b0ea9a375aed566734f8b6722fe2de84076d0
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/. */
7 #include "mozilla/dom/Notification.h"
9 #include <utility>
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/Components.h"
13 #include "mozilla/Encoding.h"
14 #include "mozilla/EventStateManager.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/JSONStringWriteFuncs.h"
17 #include "mozilla/OwningNonNull.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/StaticPrefs_dom.h"
20 #include "mozilla/Unused.h"
21 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
22 #include "mozilla/dom/BindingUtils.h"
23 #include "mozilla/dom/ContentChild.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/Promise.h"
26 #include "mozilla/dom/PromiseWorkerProxy.h"
27 #include "mozilla/dom/QMResult.h"
28 #include "mozilla/dom/RootedDictionary.h"
29 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
30 #include "mozilla/dom/ServiceWorkerUtils.h"
31 #include "mozilla/dom/WorkerRunnable.h"
32 #include "mozilla/dom/WorkerScope.h"
33 #include "mozilla/dom/quota/ResultExtensions.h"
34 #include "Navigator.h"
35 #include "nsComponentManagerUtils.h"
36 #include "nsContentPermissionHelper.h"
37 #include "nsContentUtils.h"
38 #include "nsFocusManager.h"
39 #include "nsIAlertsService.h"
40 #include "nsIContentPermissionPrompt.h"
41 #include "nsILoadContext.h"
42 #include "nsINotificationStorage.h"
43 #include "nsIPermission.h"
44 #include "nsIPermissionManager.h"
45 #include "nsIPushService.h"
46 #include "nsIScriptError.h"
47 #include "nsIServiceWorkerManager.h"
48 #include "nsIUUIDGenerator.h"
49 #include "nsNetUtil.h"
50 #include "nsProxyRelease.h"
51 #include "nsServiceManagerUtils.h"
52 #include "nsStructuredCloneContainer.h"
53 #include "nsThreadUtils.h"
54 #include "nsXULAppAPI.h"
56 namespace mozilla::dom {
58 struct NotificationStrings {
59 const nsString mID;
60 const nsString mTitle;
61 const nsString mDir;
62 const nsString mLang;
63 const nsString mBody;
64 const nsString mTag;
65 const nsString mIcon;
66 const nsString mData;
67 const nsString mBehavior;
68 const nsString mServiceWorkerRegistrationScope;
71 class ScopeCheckingGetCallback : public nsINotificationStorageCallback {
72 const nsString mScope;
74 public:
75 explicit ScopeCheckingGetCallback(const nsAString& aScope) : mScope(aScope) {}
77 NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle,
78 const nsAString& aDir, const nsAString& aLang,
79 const nsAString& aBody, const nsAString& aTag,
80 const nsAString& aIcon, const nsAString& aData,
81 const nsAString& aBehavior,
82 const nsAString& aServiceWorkerRegistrationScope) final {
83 AssertIsOnMainThread();
84 MOZ_ASSERT(!aID.IsEmpty());
86 // Skip scopes that don't match when called from getNotifications().
87 if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) {
88 return NS_OK;
91 NotificationStrings strings = {
92 nsString(aID), nsString(aTitle),
93 nsString(aDir), nsString(aLang),
94 nsString(aBody), nsString(aTag),
95 nsString(aIcon), nsString(aData),
96 nsString(aBehavior), nsString(aServiceWorkerRegistrationScope),
99 mStrings.AppendElement(std::move(strings));
100 return NS_OK;
103 NS_IMETHOD Done() override = 0;
105 protected:
106 virtual ~ScopeCheckingGetCallback() = default;
108 nsTArray<NotificationStrings> mStrings;
111 class NotificationStorageCallback final : public ScopeCheckingGetCallback {
112 public:
113 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
114 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
116 NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
117 Promise* aPromise)
118 : ScopeCheckingGetCallback(aScope), mWindow(aWindow), mPromise(aPromise) {
119 AssertIsOnMainThread();
120 MOZ_ASSERT(aWindow);
121 MOZ_ASSERT(aPromise);
124 NS_IMETHOD Done() final {
125 AutoTArray<RefPtr<Notification>, 5> notifications;
127 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
128 auto result = Notification::ConstructFromFields(
129 mWindow, mStrings[i].mID, mStrings[i].mTitle, mStrings[i].mDir,
130 mStrings[i].mLang, mStrings[i].mBody, mStrings[i].mTag,
131 mStrings[i].mIcon, mStrings[i].mData,
132 /* mStrings[i].mBehavior, not
133 * supported */
134 mStrings[i].mServiceWorkerRegistrationScope);
135 if (result.isErr()) {
136 continue;
138 RefPtr<Notification> n = result.unwrap();
139 n->SetStoredState(true);
140 notifications.AppendElement(n.forget());
143 mPromise->MaybeResolve(notifications);
144 return NS_OK;
147 private:
148 virtual ~NotificationStorageCallback() = default;
150 nsCOMPtr<nsIGlobalObject> mWindow;
151 RefPtr<Promise> mPromise;
152 const nsString mScope;
155 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
156 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
157 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
159 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
160 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
161 NS_INTERFACE_MAP_ENTRY(nsISupports)
162 NS_INTERFACE_MAP_END
164 class NotificationGetRunnable final : public Runnable {
165 const nsString mOrigin;
166 const nsString mTag;
167 nsCOMPtr<nsINotificationStorageCallback> mCallback;
169 public:
170 NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
171 nsINotificationStorageCallback* aCallback)
172 : Runnable("NotificationGetRunnable"),
173 mOrigin(aOrigin),
174 mTag(aTag),
175 mCallback(aCallback) {}
177 NS_IMETHOD
178 Run() override {
179 nsresult rv;
180 nsCOMPtr<nsINotificationStorage> notificationStorage =
181 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
182 if (NS_WARN_IF(NS_FAILED(rv))) {
183 return rv;
186 rv = notificationStorage->Get(mOrigin, mTag, mCallback);
187 // XXXnsm Is it guaranteed mCallback will be called in case of failure?
188 Unused << NS_WARN_IF(NS_FAILED(rv));
189 return rv;
193 class NotificationPermissionRequest : public ContentPermissionRequestBase,
194 public nsIRunnable,
195 public nsINamed {
196 public:
197 NS_DECL_NSIRUNNABLE
198 NS_DECL_ISUPPORTS_INHERITED
199 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
200 ContentPermissionRequestBase)
202 // nsIContentPermissionRequest
203 NS_IMETHOD Cancel(void) override;
204 NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;
206 NotificationPermissionRequest(nsIPrincipal* aPrincipal,
207 nsPIDOMWindowInner* aWindow, Promise* aPromise,
208 NotificationPermissionCallback* aCallback)
209 : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
210 "desktop-notification"_ns),
211 mPermission(NotificationPermission::Default),
212 mPromise(aPromise),
213 mCallback(aCallback) {
214 MOZ_ASSERT(aPromise);
217 NS_IMETHOD GetName(nsACString& aName) override {
218 aName.AssignLiteral("NotificationPermissionRequest");
219 return NS_OK;
222 protected:
223 ~NotificationPermissionRequest() = default;
225 MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
226 nsresult DispatchResolvePromise();
227 NotificationPermission mPermission;
228 RefPtr<Promise> mPromise;
229 RefPtr<NotificationPermissionCallback> mCallback;
232 namespace {
233 class ReleaseNotificationControlRunnable final
234 : public MainThreadWorkerControlRunnable {
235 Notification* mNotification;
237 public:
238 explicit ReleaseNotificationControlRunnable(Notification* aNotification)
239 : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate),
240 mNotification(aNotification) {}
242 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
243 mNotification->ReleaseObject();
244 return true;
248 class GetPermissionRunnable final : public WorkerMainThreadRunnable {
249 NotificationPermission mPermission;
251 public:
252 explicit GetPermissionRunnable(WorkerPrivate* aWorker)
253 : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns),
254 mPermission(NotificationPermission::Denied) {}
256 bool MainThreadRun() override {
257 ErrorResult result;
258 mPermission = Notification::GetPermissionInternal(
259 mWorkerPrivate->GetPrincipal(), result);
260 return true;
263 NotificationPermission GetPermission() { return mPermission; }
266 class FocusWindowRunnable final : public Runnable {
267 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
269 public:
270 explicit FocusWindowRunnable(
271 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
272 : Runnable("FocusWindowRunnable"), mWindow(aWindow) {}
274 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
275 // bug 1535398.
276 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
277 AssertIsOnMainThread();
278 if (!mWindow->IsCurrentInnerWindow()) {
279 // Window has been closed, this observer is not valid anymore
280 return NS_OK;
283 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mWindow->GetOuterWindow();
284 nsFocusManager::FocusWindow(outerWindow, CallerType::System);
285 return NS_OK;
289 nsresult CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope,
290 uint64_t aWindowID) {
291 AssertIsOnMainThread();
292 MOZ_ASSERT(aPrincipal);
294 nsCOMPtr<nsIURI> scopeURI;
295 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
296 if (NS_WARN_IF(NS_FAILED(rv))) {
297 return rv;
300 return aPrincipal->CheckMayLoadWithReporting(
301 scopeURI,
302 /* allowIfInheritsPrincipal = */ false, aWindowID);
304 } // anonymous namespace
306 // Subclass that can be directly dispatched to child workers from the main
307 // thread.
308 class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
309 protected:
310 explicit NotificationWorkerRunnable(
311 WorkerPrivate* aWorkerPrivate,
312 const char* aName = "NotificationWorkerRunnable")
313 : MainThreadWorkerRunnable(aWorkerPrivate, aName) {}
315 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
316 aWorkerPrivate->AssertIsOnWorkerThread();
317 // WorkerScope might start dying at the moment. And WorkerRunInternal()
318 // should not be executed once WorkerScope is dying, since
319 // WorkerRunInternal() might access resources which already been freed
320 // during WorkerRef::Notify().
321 if (aWorkerPrivate->GlobalScope() &&
322 !aWorkerPrivate->GlobalScope()->IsDying()) {
323 WorkerRunInternal(aWorkerPrivate);
325 return true;
328 virtual void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0;
331 // Overrides dispatch and run handlers so we can directly dispatch from main
332 // thread to child workers.
333 class NotificationEventWorkerRunnable final
334 : public NotificationWorkerRunnable {
335 Notification* mNotification;
336 const nsString mEventName;
338 public:
339 NotificationEventWorkerRunnable(Notification* aNotification,
340 const nsString& aEventName)
341 : NotificationWorkerRunnable(aNotification->mWorkerPrivate,
342 "NotificationEventWorkerRunnable"),
343 mNotification(aNotification),
344 mEventName(aEventName) {}
346 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
347 mNotification->DispatchTrustedEvent(mEventName);
351 class ReleaseNotificationRunnable final : public NotificationWorkerRunnable {
352 Notification* mNotification;
354 public:
355 explicit ReleaseNotificationRunnable(Notification* aNotification)
356 : NotificationWorkerRunnable(aNotification->mWorkerPrivate,
357 "ReleaseNotificationRunnable"),
358 mNotification(aNotification) {}
360 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
361 aWorkerPrivate->AssertIsOnWorkerThread();
362 // ReleaseNotificationRunnable is only used in StrongWorkerRef's shutdown
363 // callback. At the moment, it is supposed to executing
364 // mNotification->ReleaseObject() safely even though the corresponding
365 // WorkerScope::IsDying() is true. It is unlike other
366 // NotificationWorkerRunnable.
367 WorkerRunInternal(aWorkerPrivate);
368 return true;
371 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
372 mNotification->ReleaseObject();
375 nsresult Cancel() override {
376 mNotification->ReleaseObject();
377 return NS_OK;
381 // Create one whenever you require ownership of the notification. Use with
382 // UniquePtr<>. See Notification.h for details.
383 class NotificationRef final {
384 friend class WorkerNotificationObserver;
386 private:
387 Notification* mNotification;
388 bool mInited;
390 // Only useful for workers.
391 void Forget() { mNotification = nullptr; }
393 public:
394 explicit NotificationRef(Notification* aNotification)
395 : mNotification(aNotification) {
396 MOZ_ASSERT(mNotification);
397 if (mNotification->mWorkerPrivate) {
398 mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
399 } else {
400 AssertIsOnMainThread();
403 mInited = mNotification->AddRefObject();
406 // This is only required because Gecko runs script in a worker's onclose
407 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
408 // fail. Due to non-standardness and added complications if we decide to
409 // support this, attempts to create a Notification in onclose just throw
410 // exceptions.
411 bool Initialized() { return mInited; }
413 ~NotificationRef() {
414 if (Initialized() && mNotification) {
415 Notification* notification = mNotification;
416 mNotification = nullptr;
417 if (notification->mWorkerPrivate && NS_IsMainThread()) {
418 // Try to pass ownership back to the worker. If the dispatch succeeds we
419 // are guaranteed this runnable will run, and that it will run after
420 // queued event runnables, so event runnables will have a safe pointer
421 // to the Notification.
423 // If the dispatch fails, the worker isn't running anymore and the event
424 // runnables have already run or been canceled. We can use a control
425 // runnable to release the reference.
426 RefPtr<ReleaseNotificationRunnable> r =
427 new ReleaseNotificationRunnable(notification);
429 if (!r->Dispatch()) {
430 RefPtr<ReleaseNotificationControlRunnable> r =
431 new ReleaseNotificationControlRunnable(notification);
432 MOZ_ALWAYS_TRUE(r->Dispatch());
434 } else {
435 notification->AssertIsOnTargetThread();
436 notification->ReleaseObject();
441 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
442 // a rawptr that the NotificationRef can invalidate?
443 Notification* GetNotification() {
444 MOZ_ASSERT(Initialized());
445 return mNotification;
449 class NotificationTask : public Runnable {
450 public:
451 enum NotificationAction { eShow, eClose };
453 NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef,
454 NotificationAction aAction)
455 : Runnable(aName), mNotificationRef(std::move(aRef)), mAction(aAction) {}
457 NS_IMETHOD
458 Run() override;
460 protected:
461 virtual ~NotificationTask() = default;
463 UniquePtr<NotificationRef> mNotificationRef;
464 NotificationAction mAction;
467 uint32_t Notification::sCount = 0;
469 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
470 ContentPermissionRequestBase, mCallback)
471 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
472 ContentPermissionRequestBase)
473 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
474 ContentPermissionRequestBase)
476 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
477 NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
478 nsINamed)
480 NS_IMETHODIMP
481 NotificationPermissionRequest::Run() {
482 bool isSystem = mPrincipal->IsSystemPrincipal();
483 bool blocked = false;
484 if (isSystem) {
485 mPermission = NotificationPermission::Granted;
486 } else if (
487 mPrincipal->GetPrivateBrowsingId() != 0 &&
488 !StaticPrefs::
489 dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
490 mPermission = NotificationPermission::Denied;
491 blocked = true;
492 } else {
493 // File are automatically granted permission.
495 if (mPrincipal->SchemeIs("file")) {
496 mPermission = NotificationPermission::Granted;
497 } else if (!mWindow->IsSecureContext()) {
498 mPermission = NotificationPermission::Denied;
499 blocked = true;
500 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
501 if (doc) {
502 nsContentUtils::ReportToConsole(
503 nsIScriptError::errorFlag, "DOM"_ns, doc,
504 nsContentUtils::eDOM_PROPERTIES,
505 "NotificationsInsecureRequestIsForbidden");
510 // We can't call ShowPrompt() directly here since our logic for determining
511 // whether to display a prompt depends on the checks above as well as the
512 // result of CheckPromptPrefs(). So we have to manually check the prompt
513 // prefs and decide what to do based on that.
514 PromptResult pr = CheckPromptPrefs();
515 switch (pr) {
516 case PromptResult::Granted:
517 mPermission = NotificationPermission::Granted;
518 break;
519 case PromptResult::Denied:
520 mPermission = NotificationPermission::Denied;
521 break;
522 default:
523 // ignore
524 break;
527 if (!mHasValidTransientUserGestureActivation &&
528 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
529 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
530 if (doc) {
531 doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
535 // Check this after checking the prompt prefs to make sure this pref overrides
536 // those. We rely on this for testing purposes.
537 if (!isSystem && !blocked &&
538 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
539 !mPrincipal->Subsumes(mTopLevelPrincipal)) {
540 mPermission = NotificationPermission::Denied;
541 blocked = true;
542 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
543 if (doc) {
544 nsContentUtils::ReportToConsole(
545 nsIScriptError::errorFlag, "DOM"_ns, doc,
546 nsContentUtils::eDOM_PROPERTIES,
547 "NotificationsCrossOriginIframeRequestIsForbidden");
551 if (mPermission != NotificationPermission::Default) {
552 return DispatchResolvePromise();
555 return nsContentPermissionUtils::AskPermission(this, mWindow);
558 NS_IMETHODIMP
559 NotificationPermissionRequest::Cancel() {
560 // `Cancel` is called if the user denied permission or dismissed the
561 // permission request. To distinguish between the two, we set the
562 // permission to "default" and query the permission manager in
563 // `ResolvePromise`.
564 mPermission = NotificationPermission::Default;
565 return DispatchResolvePromise();
568 NS_IMETHODIMP
569 NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
570 MOZ_ASSERT(aChoices.isUndefined());
572 mPermission = NotificationPermission::Granted;
573 return DispatchResolvePromise();
576 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
577 nsCOMPtr<nsIRunnable> resolver =
578 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
579 this, &NotificationPermissionRequest::ResolvePromise);
580 return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget());
583 nsresult NotificationPermissionRequest::ResolvePromise() {
584 nsresult rv = NS_OK;
585 // This will still be "default" if the user dismissed the doorhanger,
586 // or "denied" otherwise.
587 if (mPermission == NotificationPermission::Default) {
588 // When the front-end has decided to deny the permission request
589 // automatically and we are not handling user input, then log a
590 // warning in the current document that this happened because
591 // Notifications require a user gesture.
592 if (!mHasValidTransientUserGestureActivation &&
593 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
594 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
595 if (doc) {
596 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
597 doc, nsContentUtils::eDOM_PROPERTIES,
598 "NotificationsRequireUserGesture");
602 mPermission = Notification::TestPermission(mPrincipal);
604 if (mCallback) {
605 ErrorResult error;
606 RefPtr<NotificationPermissionCallback> callback(mCallback);
607 callback->Call(mPermission, error);
608 rv = error.StealNSResult();
610 mPromise->MaybeResolve(mPermission);
611 return rv;
614 // Observer that the alert service calls to do common tasks and/or dispatch to
615 // the specific observer for the context e.g. main thread, worker, or service
616 // worker.
617 class NotificationObserver final : public nsIObserver {
618 public:
619 nsCOMPtr<nsIObserver> mObserver;
620 nsCOMPtr<nsIPrincipal> mPrincipal;
621 bool mInPrivateBrowsing;
622 NS_DECL_ISUPPORTS
623 NS_DECL_NSIOBSERVER
625 NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
626 bool aInPrivateBrowsing)
627 : mObserver(aObserver),
628 mPrincipal(aPrincipal),
629 mInPrivateBrowsing(aInPrivateBrowsing) {
630 AssertIsOnMainThread();
631 MOZ_ASSERT(mObserver);
632 MOZ_ASSERT(mPrincipal);
635 protected:
636 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
638 nsresult AdjustPushQuota(const char* aTopic);
641 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
643 class MainThreadNotificationObserver : public nsIObserver {
644 public:
645 UniquePtr<NotificationRef> mNotificationRef;
646 NS_DECL_ISUPPORTS
647 NS_DECL_NSIOBSERVER
649 explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
650 : mNotificationRef(std::move(aRef)) {
651 AssertIsOnMainThread();
654 protected:
655 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
658 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
660 NS_IMETHODIMP
661 NotificationTask::Run() {
662 AssertIsOnMainThread();
664 // Get a pointer to notification before the notification takes ownership of
665 // the ref (it owns itself temporarily, with ShowInternal() and
666 // CloseInternal() passing on the ownership appropriately.)
667 Notification* notif = mNotificationRef->GetNotification();
668 notif->mTempRef.swap(mNotificationRef);
669 if (mAction == eShow) {
670 notif->ShowInternal();
671 } else if (mAction == eClose) {
672 notif->CloseInternal();
673 } else {
674 MOZ_CRASH("Invalid action");
677 MOZ_ASSERT(!mNotificationRef);
678 return NS_OK;
681 // static
682 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
683 return StaticPrefs::dom_webnotifications_enabled();
686 Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
687 const nsAString& aTitle, const nsAString& aBody,
688 NotificationDirection aDir, const nsAString& aLang,
689 const nsAString& aTag, const nsAString& aIconUrl,
690 bool aRequireInteraction, bool aSilent,
691 nsTArray<uint32_t>&& aVibrate,
692 const NotificationBehavior& aBehavior)
693 : DOMEventTargetHelper(aGlobal),
694 mWorkerPrivate(nullptr),
695 mObserver(nullptr),
696 mID(aID),
697 mTitle(aTitle),
698 mBody(aBody),
699 mDir(aDir),
700 mLang(aLang),
701 mTag(aTag),
702 mIconUrl(aIconUrl),
703 mRequireInteraction(aRequireInteraction),
704 mSilent(aSilent),
705 mVibrate(std::move(aVibrate)),
706 mBehavior(aBehavior),
707 mData(JS::NullValue()),
708 mIsClosed(false),
709 mIsStored(false),
710 mTaskCount(0) {
711 if (!NS_IsMainThread()) {
712 mWorkerPrivate = GetCurrentThreadWorkerPrivate();
713 MOZ_ASSERT(mWorkerPrivate);
717 nsresult Notification::MaybeObserveWindowFrozenOrDestroyed() {
718 // NOTE: Non-persistent notifications can also be opened from workers, but we
719 // don't care and nobody else cares. And it's not clear whether we even should
720 // do this for window at all, see
721 // https://github.com/whatwg/notifications/issues/204.
722 // TODO: Somehow extend GlobalTeardownObserver to deal with FROZEN_TOPIC?
723 if (!mWorkerPrivate) {
724 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
725 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
727 nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
728 NS_ENSURE_SUCCESS(rv, rv);
730 rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
731 NS_ENSURE_SUCCESS(rv, rv);
734 return NS_OK;
737 void Notification::SetAlertName() {
738 AssertIsOnMainThread();
739 if (!mAlertName.IsEmpty()) {
740 return;
743 nsAutoString alertName;
744 nsresult rv = GetOrigin(GetPrincipal(), alertName);
745 if (NS_WARN_IF(NS_FAILED(rv))) {
746 return;
749 // Get the notification name that is unique per origin + tag/ID.
750 // The name of the alert is of the form origin#tag/ID.
751 alertName.Append('#');
752 if (!mTag.IsEmpty()) {
753 alertName.AppendLiteral("tag:");
754 alertName.Append(mTag);
755 } else {
756 alertName.AppendLiteral("notag:");
757 alertName.Append(mID);
760 mAlertName = alertName;
763 // May be called on any thread.
764 // static
765 already_AddRefed<Notification> Notification::Constructor(
766 const GlobalObject& aGlobal, const nsAString& aTitle,
767 const NotificationOptions& aOptions, ErrorResult& aRv) {
768 // FIXME(nsm): If the sticky flag is set, throw an error.
769 RefPtr<ServiceWorkerGlobalScope> scope;
770 UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
771 if (scope) {
772 aRv.ThrowTypeError(
773 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
774 "Use registration.showNotification() instead.");
775 return nullptr;
778 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
779 RefPtr<Notification> notification =
780 CreateAndShow(aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
781 if (NS_WARN_IF(aRv.Failed())) {
782 return nullptr;
784 if (NS_WARN_IF(
785 NS_FAILED(notification->MaybeObserveWindowFrozenOrDestroyed()))) {
786 return nullptr;
789 // This is be ok since we are on the worker thread where this function will
790 // run to completion before the Notification has a chance to go away.
791 return notification.forget();
794 // static
795 Result<already_AddRefed<Notification>, QMResult>
796 Notification::ConstructFromFields(
797 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
798 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
799 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
800 const nsAString& aServiceWorkerRegistrationScope) {
801 MOZ_ASSERT(aGlobal);
803 RootedDictionary<NotificationOptions> options(RootingCx());
804 options.mDir = Notification::StringToDirection(nsString(aDir));
805 options.mLang = aLang;
806 options.mBody = aBody;
807 options.mTag = aTag;
808 options.mIcon = aIcon;
809 IgnoredErrorResult rv;
810 RefPtr<Notification> notification =
811 CreateInternal(aGlobal, aID, aTitle, options, rv);
812 if (NS_WARN_IF(rv.Failed())) {
813 return Err(ToQMResult(NS_ERROR_FAILURE));
816 QM_TRY(notification->InitFromBase64(aData));
818 notification->SetScope(aServiceWorkerRegistrationScope);
820 return notification.forget();
823 nsresult Notification::PersistNotification() {
824 AssertIsOnMainThread();
825 nsresult rv;
826 nsCOMPtr<nsINotificationStorage> notificationStorage =
827 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
828 if (NS_FAILED(rv)) {
829 return rv;
832 nsString origin;
833 rv = GetOrigin(GetPrincipal(), origin);
834 if (NS_WARN_IF(NS_FAILED(rv))) {
835 return rv;
838 nsString id;
839 GetID(id);
841 nsString alertName;
842 GetAlertName(alertName);
844 nsAutoString behavior;
845 if (!mBehavior.ToJSON(behavior)) {
846 return NS_ERROR_FAILURE;
849 rv = notificationStorage->Put(origin, id, mTitle, DirectionToString(mDir),
850 mLang, mBody, mTag, mIconUrl, alertName,
851 mDataAsBase64, behavior, mScope);
853 if (NS_FAILED(rv)) {
854 return rv;
857 SetStoredState(true);
858 return NS_OK;
861 void Notification::UnpersistNotification() {
862 AssertIsOnMainThread();
863 if (IsStored()) {
864 nsCOMPtr<nsINotificationStorage> notificationStorage =
865 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
866 if (notificationStorage) {
867 nsString origin;
868 nsresult rv = GetOrigin(GetPrincipal(), origin);
869 if (NS_SUCCEEDED(rv)) {
870 notificationStorage->Delete(origin, mID);
873 SetStoredState(false);
877 already_AddRefed<Notification> Notification::CreateInternal(
878 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
879 const NotificationOptions& aOptions, ErrorResult& aRv) {
880 nsresult rv;
881 nsString id;
882 if (!aID.IsEmpty()) {
883 id = aID;
884 } else {
885 nsCOMPtr<nsIUUIDGenerator> uuidgen =
886 do_GetService("@mozilla.org/uuid-generator;1");
887 NS_ENSURE_TRUE(uuidgen, nullptr);
888 nsID uuid;
889 rv = uuidgen->GenerateUUIDInPlace(&uuid);
890 NS_ENSURE_SUCCESS(rv, nullptr);
892 char buffer[NSID_LENGTH];
893 uuid.ToProvidedString(buffer);
894 NS_ConvertASCIItoUTF16 convertedID(buffer);
895 id = convertedID;
898 bool silent = false;
899 if (StaticPrefs::dom_webnotifications_silent_enabled()) {
900 silent = aOptions.mSilent;
903 nsTArray<uint32_t> vibrate;
904 if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
905 aOptions.mVibrate.WasPassed()) {
906 if (silent) {
907 aRv.ThrowTypeError(
908 "Silent notifications must not specify vibration patterns.");
909 return nullptr;
912 const OwningUnsignedLongOrUnsignedLongSequence& value =
913 aOptions.mVibrate.Value();
914 if (value.IsUnsignedLong()) {
915 AutoTArray<uint32_t, 1> array;
916 array.AppendElement(value.GetAsUnsignedLong());
917 vibrate = SanitizeVibratePattern(array);
918 } else {
919 vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence());
923 RefPtr<Notification> notification = new Notification(
924 aGlobal, id, aTitle, aOptions.mBody, aOptions.mDir, aOptions.mLang,
925 aOptions.mTag, aOptions.mIcon, aOptions.mRequireInteraction, silent,
926 std::move(vibrate), aOptions.mMozbehavior);
927 return notification.forget();
930 Notification::~Notification() {
931 mozilla::DropJSObjects(this);
932 AssertIsOnTargetThread();
933 MOZ_ASSERT(!mWorkerRef);
934 MOZ_ASSERT(!mTempRef);
937 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
938 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
939 DOMEventTargetHelper)
940 tmp->mData.setUndefined();
941 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
942 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
944 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
945 DOMEventTargetHelper)
946 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
948 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
949 DOMEventTargetHelper)
950 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
951 NS_IMPL_CYCLE_COLLECTION_TRACE_END
953 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
954 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
956 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
957 NS_INTERFACE_MAP_ENTRY(nsIObserver)
958 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
959 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
961 nsIPrincipal* Notification::GetPrincipal() {
962 AssertIsOnMainThread();
963 if (mWorkerPrivate) {
964 return mWorkerPrivate->GetPrincipal();
965 } else {
966 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
967 NS_ENSURE_TRUE(sop, nullptr);
968 return sop->GetPrincipal();
972 class WorkerNotificationObserver final : public MainThreadNotificationObserver {
973 public:
974 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver,
975 MainThreadNotificationObserver)
976 NS_DECL_NSIOBSERVER
978 explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
979 : MainThreadNotificationObserver(std::move(aRef)) {
980 AssertIsOnMainThread();
981 MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate);
984 void ForgetNotification() {
985 AssertIsOnMainThread();
986 mNotificationRef->Forget();
989 protected:
990 virtual ~WorkerNotificationObserver() {
991 AssertIsOnMainThread();
993 MOZ_ASSERT(mNotificationRef);
994 Notification* notification = mNotificationRef->GetNotification();
995 if (notification) {
996 notification->mObserver = nullptr;
1001 class ServiceWorkerNotificationObserver final : public nsIObserver {
1002 public:
1003 NS_DECL_ISUPPORTS
1004 NS_DECL_NSIOBSERVER
1006 ServiceWorkerNotificationObserver(
1007 const nsAString& aScope, nsIPrincipal* aPrincipal, const nsAString& aID,
1008 const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang,
1009 const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon,
1010 const nsAString& aData, const nsAString& aBehavior)
1011 : mScope(aScope),
1012 mID(aID),
1013 mPrincipal(aPrincipal),
1014 mTitle(aTitle),
1015 mDir(aDir),
1016 mLang(aLang),
1017 mBody(aBody),
1018 mTag(aTag),
1019 mIcon(aIcon),
1020 mData(aData),
1021 mBehavior(aBehavior) {
1022 AssertIsOnMainThread();
1023 MOZ_ASSERT(aPrincipal);
1026 private:
1027 ~ServiceWorkerNotificationObserver() = default;
1029 const nsString mScope;
1030 const nsString mID;
1031 nsCOMPtr<nsIPrincipal> mPrincipal;
1032 const nsString mTitle;
1033 const nsString mDir;
1034 const nsString mLang;
1035 const nsString mBody;
1036 const nsString mTag;
1037 const nsString mIcon;
1038 const nsString mData;
1039 const nsString mBehavior;
1042 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver)
1044 bool Notification::DispatchClickEvent() {
1045 AssertIsOnTargetThread();
1046 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1047 event->InitEvent(u"click"_ns, false, true);
1048 event->SetTrusted(true);
1049 WantsPopupControlCheck popupControlCheck(event);
1050 return DispatchEvent(*event, CallerType::System, IgnoreErrors());
1053 // Overrides dispatch and run handlers so we can directly dispatch from main
1054 // thread to child workers.
1055 class NotificationClickWorkerRunnable final
1056 : public NotificationWorkerRunnable {
1057 Notification* mNotification;
1058 // Optional window that gets focused if click event is not
1059 // preventDefault()ed.
1060 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
1062 public:
1063 NotificationClickWorkerRunnable(
1064 Notification* aNotification,
1065 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
1066 : NotificationWorkerRunnable(aNotification->mWorkerPrivate,
1067 "NotificationClickWorkerRunnable"),
1068 mNotification(aNotification),
1069 mWindow(aWindow) {
1070 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow);
1073 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1074 bool doDefaultAction = mNotification->DispatchClickEvent();
1075 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction);
1076 if (doDefaultAction) {
1077 RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
1078 mWorkerPrivate->DispatchToMainThread(r.forget());
1083 NS_IMETHODIMP
1084 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1085 const char16_t* aData) {
1086 AssertIsOnMainThread();
1088 if (!strcmp("alertdisablecallback", aTopic)) {
1089 if (XRE_IsParentProcess()) {
1090 return Notification::RemovePermission(mPrincipal);
1092 // Permissions can't be removed from the content process. Send a message
1093 // to the parent; `ContentParent::RecvDisableNotifications` will call
1094 // `RemovePermission`.
1095 ContentChild::GetSingleton()->SendDisableNotifications(mPrincipal);
1096 return NS_OK;
1097 } else if (!strcmp("alertsettingscallback", aTopic)) {
1098 if (XRE_IsParentProcess()) {
1099 return Notification::OpenSettings(mPrincipal);
1101 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1102 // parent process.
1103 ContentChild::GetSingleton()->SendOpenNotificationSettings(mPrincipal);
1104 return NS_OK;
1105 } else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) {
1106 Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
1109 return mObserver->Observe(aSubject, aTopic, aData);
1112 nsresult NotificationObserver::AdjustPushQuota(const char* aTopic) {
1113 nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
1114 do_GetService("@mozilla.org/push/Service;1");
1115 if (!pushQuotaManager) {
1116 return NS_ERROR_FAILURE;
1119 nsAutoCString origin;
1120 nsresult rv = mPrincipal->GetOrigin(origin);
1121 if (NS_FAILED(rv)) {
1122 return rv;
1125 if (!strcmp("alertshow", aTopic)) {
1126 return pushQuotaManager->NotificationForOriginShown(origin.get());
1128 return pushQuotaManager->NotificationForOriginClosed(origin.get());
1131 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
1132 // bug 1539845.
1133 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1134 MainThreadNotificationObserver::Observe(nsISupports* aSubject,
1135 const char* aTopic,
1136 const char16_t* aData) {
1137 AssertIsOnMainThread();
1138 MOZ_ASSERT(mNotificationRef);
1139 Notification* notification = mNotificationRef->GetNotification();
1140 MOZ_ASSERT(notification);
1141 if (!strcmp("alertclickcallback", aTopic)) {
1142 nsCOMPtr<nsPIDOMWindowInner> window = notification->GetOwner();
1143 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1144 // Window has been closed, this observer is not valid anymore
1145 return NS_ERROR_FAILURE;
1148 bool doDefaultAction = notification->DispatchClickEvent();
1149 if (doDefaultAction) {
1150 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->GetOuterWindow();
1151 nsFocusManager::FocusWindow(outerWindow, CallerType::System);
1153 } else if (!strcmp("alertfinished", aTopic)) {
1154 notification->UnpersistNotification();
1155 notification->mIsClosed = true;
1156 notification->DispatchTrustedEvent(u"close"_ns);
1157 } else if (!strcmp("alertshow", aTopic)) {
1158 notification->DispatchTrustedEvent(u"show"_ns);
1160 return NS_OK;
1163 NS_IMETHODIMP
1164 WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1165 const char16_t* aData) {
1166 AssertIsOnMainThread();
1167 MOZ_ASSERT(mNotificationRef);
1168 // For an explanation of why it is OK to pass this rawptr to the event
1169 // runnables, see the Notification class comment.
1170 Notification* notification = mNotificationRef->GetNotification();
1171 // We can't assert notification here since the feature could've unset it.
1172 if (NS_WARN_IF(!notification)) {
1173 return NS_ERROR_FAILURE;
1176 MOZ_ASSERT(notification->mWorkerPrivate);
1178 RefPtr<WorkerRunnable> r;
1179 if (!strcmp("alertclickcallback", aTopic)) {
1180 nsPIDOMWindowInner* window = nullptr;
1181 if (!notification->mWorkerPrivate->IsServiceWorker()) {
1182 WorkerPrivate* top = notification->mWorkerPrivate;
1183 while (top->GetParent()) {
1184 top = top->GetParent();
1187 window = top->GetWindow();
1188 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1189 // Window has been closed, this observer is not valid anymore
1190 return NS_ERROR_FAILURE;
1194 // Instead of bothering with adding features and other worker lifecycle
1195 // management, we simply hold strongrefs to the window and document.
1196 nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
1197 new nsMainThreadPtrHolder<nsPIDOMWindowInner>(
1198 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window));
1200 r = new NotificationClickWorkerRunnable(notification, windowHandle);
1201 } else if (!strcmp("alertfinished", aTopic)) {
1202 notification->UnpersistNotification();
1203 notification->mIsClosed = true;
1204 r = new NotificationEventWorkerRunnable(notification, u"close"_ns);
1205 } else if (!strcmp("alertshow", aTopic)) {
1206 r = new NotificationEventWorkerRunnable(notification, u"show"_ns);
1209 MOZ_ASSERT(r);
1210 if (!r->Dispatch()) {
1211 NS_WARNING("Could not dispatch event to worker notification");
1213 return NS_OK;
1216 NS_IMETHODIMP
1217 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
1218 const char* aTopic,
1219 const char16_t* aData) {
1220 AssertIsOnMainThread();
1222 nsAutoCString originSuffix;
1223 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
1224 if (NS_WARN_IF(NS_FAILED(rv))) {
1225 return rv;
1228 if (!strcmp("alertclickcallback", aTopic)) {
1229 if (XRE_IsParentProcess()) {
1230 nsCOMPtr<nsIServiceWorkerManager> swm =
1231 mozilla::components::ServiceWorkerManager::Service();
1232 if (NS_WARN_IF(!swm)) {
1233 return NS_ERROR_FAILURE;
1236 rv = swm->SendNotificationClickEvent(
1237 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1238 mBody, mTag, mIcon, mData, mBehavior);
1239 Unused << NS_WARN_IF(NS_FAILED(rv));
1240 } else {
1241 auto* cc = ContentChild::GetSingleton();
1242 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1243 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1244 mData, mBehavior);
1245 Unused << cc->SendNotificationEvent(u"click"_ns, data);
1247 return NS_OK;
1250 if (!strcmp("alertfinished", aTopic)) {
1251 nsString origin;
1252 nsresult rv = Notification::GetOrigin(mPrincipal, origin);
1253 if (NS_WARN_IF(NS_FAILED(rv))) {
1254 return rv;
1257 // Remove closed or dismissed persistent notifications.
1258 nsCOMPtr<nsINotificationStorage> notificationStorage =
1259 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1260 if (notificationStorage) {
1261 notificationStorage->Delete(origin, mID);
1264 if (XRE_IsParentProcess()) {
1265 nsCOMPtr<nsIServiceWorkerManager> swm =
1266 mozilla::components::ServiceWorkerManager::Service();
1267 if (NS_WARN_IF(!swm)) {
1268 return NS_ERROR_FAILURE;
1271 rv = swm->SendNotificationCloseEvent(
1272 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1273 mBody, mTag, mIcon, mData, mBehavior);
1274 Unused << NS_WARN_IF(NS_FAILED(rv));
1275 } else {
1276 auto* cc = ContentChild::GetSingleton();
1277 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1278 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1279 mData, mBehavior);
1280 Unused << cc->SendNotificationEvent(u"close"_ns, data);
1282 return NS_OK;
1285 return NS_OK;
1288 bool Notification::IsInPrivateBrowsing() {
1289 AssertIsOnMainThread();
1291 Document* doc = nullptr;
1293 if (mWorkerPrivate) {
1294 doc = mWorkerPrivate->GetDocument();
1295 } else if (GetOwner()) {
1296 doc = GetOwner()->GetExtantDoc();
1299 if (doc) {
1300 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
1301 return loadContext && loadContext->UsePrivateBrowsing();
1304 if (mWorkerPrivate) {
1305 // Not all workers may have a document, but with Bug 1107516 fixed, they
1306 // should all have a loadcontext.
1307 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1308 nsCOMPtr<nsILoadContext> loadContext;
1309 NS_QueryNotificationCallbacks(nullptr, loadGroup,
1310 NS_GET_IID(nsILoadContext),
1311 getter_AddRefs(loadContext));
1312 return loadContext && loadContext->UsePrivateBrowsing();
1315 // XXXnsm Should this default to true?
1316 return false;
1319 void Notification::ShowInternal() {
1320 AssertIsOnMainThread();
1321 MOZ_ASSERT(mTempRef,
1322 "Notification should take ownership of itself before"
1323 "calling ShowInternal!");
1324 // A notification can only have one observer and one call to ShowInternal.
1325 MOZ_ASSERT(!mObserver);
1327 // Transfer ownership to local scope so we can either release it at the end
1328 // of this function or transfer it to the observer.
1329 UniquePtr<NotificationRef> ownership;
1330 std::swap(ownership, mTempRef);
1331 MOZ_ASSERT(ownership->GetNotification() == this);
1333 nsresult rv = PersistNotification();
1334 if (NS_FAILED(rv)) {
1335 NS_WARNING("Could not persist Notification");
1338 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1340 ErrorResult result;
1341 NotificationPermission permission = NotificationPermission::Denied;
1342 if (mWorkerPrivate) {
1343 permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
1344 } else {
1345 permission = GetPermissionInternal(GetOwner(), result);
1347 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1348 MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
1349 result.SuppressException();
1350 if (permission != NotificationPermission::Granted || !alertService) {
1351 if (mWorkerPrivate) {
1352 RefPtr<NotificationEventWorkerRunnable> r =
1353 new NotificationEventWorkerRunnable(this, u"error"_ns);
1354 if (!r->Dispatch()) {
1355 NS_WARNING("Could not dispatch event to worker notification");
1357 } else {
1358 DispatchTrustedEvent(u"error"_ns);
1360 return;
1363 nsAutoString iconUrl;
1364 nsAutoString soundUrl;
1365 ResolveIconAndSoundURL(iconUrl, soundUrl);
1367 bool isPersistent = false;
1368 nsCOMPtr<nsIObserver> observer;
1369 if (mScope.IsEmpty()) {
1370 // Ownership passed to observer.
1371 if (mWorkerPrivate) {
1372 // Scope better be set on ServiceWorker initiated requests.
1373 MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
1374 // Keep a pointer so that the feature can tell the observer not to release
1375 // the notification.
1376 mObserver = new WorkerNotificationObserver(std::move(ownership));
1377 observer = mObserver;
1378 } else {
1379 observer = new MainThreadNotificationObserver(std::move(ownership));
1381 } else {
1382 isPersistent = true;
1383 // This observer does not care about the Notification. It will be released
1384 // at the end of this function.
1386 // The observer is wholly owned by the NotificationObserver passed to the
1387 // alert service.
1388 nsAutoString behavior;
1389 if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
1390 behavior.Truncate();
1392 observer = new ServiceWorkerNotificationObserver(
1393 mScope, GetPrincipal(), mID, mTitle, DirectionToString(mDir), mLang,
1394 mBody, mTag, iconUrl, mDataAsBase64, behavior);
1396 MOZ_ASSERT(observer);
1397 nsCOMPtr<nsIObserver> alertObserver =
1398 new NotificationObserver(observer, GetPrincipal(), IsInPrivateBrowsing());
1400 // In the case of IPC, the parent process uses the cookie to map to
1401 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1402 nsString uniqueCookie = u"notification:"_ns;
1403 uniqueCookie.AppendInt(sCount++);
1404 bool inPrivateBrowsing = IsInPrivateBrowsing();
1406 bool requireInteraction = mRequireInteraction;
1407 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1408 requireInteraction = false;
1411 nsAutoString alertName;
1412 GetAlertName(alertName);
1413 nsCOMPtr<nsIAlertNotification> alert =
1414 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
1415 NS_ENSURE_TRUE_VOID(alert);
1416 nsIPrincipal* principal = GetPrincipal();
1417 rv =
1418 alert->Init(alertName, iconUrl, mTitle, mBody, true, uniqueCookie,
1419 DirectionToString(mDir), mLang, mDataAsBase64, GetPrincipal(),
1420 inPrivateBrowsing, requireInteraction, mSilent, mVibrate);
1421 NS_ENSURE_SUCCESS_VOID(rv);
1423 if (isPersistent) {
1424 JSONStringWriteFunc<nsAutoCString> persistentData;
1425 JSONWriter w(persistentData);
1426 w.Start();
1428 nsAutoString origin;
1429 Notification::GetOrigin(principal, origin);
1430 w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin));
1432 w.StringProperty("id", NS_ConvertUTF16toUTF8(mID));
1434 nsAutoCString originSuffix;
1435 principal->GetOriginSuffix(originSuffix);
1436 w.StringProperty("originSuffix", originSuffix);
1438 w.End();
1440 alertService->ShowPersistentNotification(
1441 NS_ConvertUTF8toUTF16(persistentData.StringCRef()), alert,
1442 alertObserver);
1443 } else {
1444 alertService->ShowAlert(alert, alertObserver);
1448 /* static */
1449 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
1450 JSObject* /* unused */) {
1451 // requestPermission() is not allowed on workers. The calling page should ask
1452 // for permission on the worker's behalf. This is to prevent 'which window
1453 // should show the browser pop-up'. See discussion:
1454 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1455 return NS_IsMainThread();
1458 // static
1459 already_AddRefed<Promise> Notification::RequestPermission(
1460 const GlobalObject& aGlobal,
1461 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
1462 ErrorResult& aRv) {
1463 AssertIsOnMainThread();
1465 // Get principal from global to make permission request for notifications.
1466 nsCOMPtr<nsPIDOMWindowInner> window =
1467 do_QueryInterface(aGlobal.GetAsSupports());
1468 nsCOMPtr<nsIScriptObjectPrincipal> sop =
1469 do_QueryInterface(aGlobal.GetAsSupports());
1470 if (!sop || !window) {
1471 aRv.Throw(NS_ERROR_UNEXPECTED);
1472 return nullptr;
1475 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1476 if (!principal) {
1477 aRv.Throw(NS_ERROR_UNEXPECTED);
1478 return nullptr;
1481 RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
1482 if (aRv.Failed()) {
1483 return nullptr;
1485 NotificationPermissionCallback* permissionCallback = nullptr;
1486 if (aCallback.WasPassed()) {
1487 permissionCallback = &aCallback.Value();
1489 nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
1490 principal, window, promise, permissionCallback);
1492 window->AsGlobal()->Dispatch(request.forget());
1494 return promise.forget();
1497 // static
1498 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
1499 ErrorResult& aRv) {
1500 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1501 return GetPermission(global, aRv);
1504 // static
1505 NotificationPermission Notification::GetPermission(nsIGlobalObject* aGlobal,
1506 ErrorResult& aRv) {
1507 if (NS_IsMainThread()) {
1508 return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aRv);
1509 } else {
1510 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1511 MOZ_ASSERT(worker);
1512 RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(worker);
1513 r->Dispatch(Canceling, aRv);
1514 if (aRv.Failed()) {
1515 return NotificationPermission::Denied;
1518 return r->GetPermission();
1522 /* static */
1523 NotificationPermission Notification::GetPermissionInternal(
1524 nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
1525 // Get principal from global to check permission for notifications.
1526 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
1527 if (!sop) {
1528 aRv.Throw(NS_ERROR_UNEXPECTED);
1529 return NotificationPermission::Denied;
1532 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1533 if (!principal) {
1534 aRv.Throw(NS_ERROR_UNEXPECTED);
1535 return NotificationPermission::Denied;
1538 if (principal->GetPrivateBrowsingId() != 0 &&
1539 !StaticPrefs::
1540 dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
1541 return NotificationPermission::Denied;
1543 // Disallow showing notification if our origin is not the same origin as the
1544 // toplevel one, see https://github.com/whatwg/notifications/issues/177.
1545 if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) {
1546 nsCOMPtr<nsIScriptObjectPrincipal> topSop =
1547 do_QueryInterface(aWindow->GetBrowsingContext()->Top()->GetDOMWindow());
1548 nsIPrincipal* topPrincipal = topSop ? topSop->GetPrincipal() : nullptr;
1549 if (!topPrincipal || !principal->Subsumes(topPrincipal)) {
1550 return NotificationPermission::Denied;
1554 return GetPermissionInternal(principal, aRv);
1557 /* static */
1558 NotificationPermission Notification::GetPermissionInternal(
1559 nsIPrincipal* aPrincipal, ErrorResult& aRv) {
1560 AssertIsOnMainThread();
1561 MOZ_ASSERT(aPrincipal);
1563 if (aPrincipal->IsSystemPrincipal()) {
1564 return NotificationPermission::Granted;
1565 } else {
1566 // Allow files to show notifications by default.
1567 if (aPrincipal->SchemeIs("file")) {
1568 return NotificationPermission::Granted;
1572 // We also allow notifications is they are pref'ed on.
1573 if (Preferences::GetBool("notification.prompt.testing", false)) {
1574 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1575 return NotificationPermission::Granted;
1576 } else {
1577 return NotificationPermission::Denied;
1581 return TestPermission(aPrincipal);
1584 /* static */
1585 NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) {
1586 AssertIsOnMainThread();
1588 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
1590 nsCOMPtr<nsIPermissionManager> permissionManager =
1591 components::PermissionManager::Service();
1592 if (!permissionManager) {
1593 return NotificationPermission::Default;
1596 permissionManager->TestExactPermissionFromPrincipal(
1597 aPrincipal, "desktop-notification"_ns, &permission);
1599 // Convert the result to one of the enum types.
1600 switch (permission) {
1601 case nsIPermissionManager::ALLOW_ACTION:
1602 return NotificationPermission::Granted;
1603 case nsIPermissionManager::DENY_ACTION:
1604 return NotificationPermission::Denied;
1605 default:
1606 return NotificationPermission::Default;
1610 nsresult Notification::ResolveIconAndSoundURL(nsString& iconUrl,
1611 nsString& soundUrl) {
1612 AssertIsOnMainThread();
1613 nsresult rv = NS_OK;
1615 nsIURI* baseUri = nullptr;
1617 // XXXnsm If I understand correctly, the character encoding for resolving
1618 // URIs in new specs is dictated by the URL spec, which states that unless
1619 // the URL parser is passed an override encoding, the charset to be used is
1620 // UTF-8. The new Notification icon/sound specification just says to use the
1621 // Fetch API, where the Request constructor defers to URL parsing specifying
1622 // the API base URL and no override encoding. So we've to use UTF-8 on
1623 // workers, but for backwards compat keeping it document charset on main
1624 // thread.
1625 auto encoding = UTF_8_ENCODING;
1627 if (mWorkerPrivate) {
1628 baseUri = mWorkerPrivate->GetBaseURI();
1629 } else {
1630 Document* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1631 if (doc) {
1632 baseUri = doc->GetBaseURI();
1633 encoding = doc->GetDocumentCharacterSet();
1634 } else {
1635 NS_WARNING("No document found for main thread notification!");
1636 return NS_ERROR_FAILURE;
1640 if (baseUri) {
1641 if (mIconUrl.Length() > 0) {
1642 nsCOMPtr<nsIURI> srcUri;
1643 rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, encoding, baseUri);
1644 if (NS_SUCCEEDED(rv)) {
1645 nsAutoCString src;
1646 srcUri->GetSpec(src);
1647 CopyUTF8toUTF16(src, iconUrl);
1650 if (mBehavior.mSoundFile.Length() > 0) {
1651 nsCOMPtr<nsIURI> srcUri;
1652 rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding,
1653 baseUri);
1654 if (NS_SUCCEEDED(rv)) {
1655 nsAutoCString src;
1656 srcUri->GetSpec(src);
1657 CopyUTF8toUTF16(src, soundUrl);
1662 return rv;
1665 already_AddRefed<Promise> Notification::Get(
1666 nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter,
1667 const nsAString& aScope, ErrorResult& aRv) {
1668 AssertIsOnMainThread();
1669 MOZ_ASSERT(aWindow);
1671 nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
1672 if (!doc) {
1673 aRv.Throw(NS_ERROR_UNEXPECTED);
1674 return nullptr;
1677 nsString origin;
1678 aRv = GetOrigin(doc->NodePrincipal(), origin);
1679 if (aRv.Failed()) {
1680 return nullptr;
1683 RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
1684 if (aRv.Failed()) {
1685 return nullptr;
1688 nsCOMPtr<nsINotificationStorageCallback> callback =
1689 new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
1691 RefPtr<NotificationGetRunnable> r =
1692 new NotificationGetRunnable(origin, aFilter.mTag, callback);
1694 aRv = aWindow->AsGlobal()->Dispatch(r.forget());
1695 if (NS_WARN_IF(aRv.Failed())) {
1696 return nullptr;
1699 return promise.forget();
1702 class WorkerGetResultRunnable final : public NotificationWorkerRunnable {
1703 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1704 const nsTArray<NotificationStrings> mStrings;
1706 public:
1707 WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
1708 PromiseWorkerProxy* aPromiseProxy,
1709 nsTArray<NotificationStrings>&& aStrings)
1710 : NotificationWorkerRunnable(aWorkerPrivate, "WorkerGetResultRunnable"),
1711 mPromiseProxy(aPromiseProxy),
1712 mStrings(std::move(aStrings)) {}
1714 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1715 RefPtr<Promise> workerPromise = mPromiseProxy->GetWorkerPromise();
1716 // Once Worker had already started shutdown, workerPromise would be nullptr
1717 if (!workerPromise) {
1718 return;
1721 AutoTArray<RefPtr<Notification>, 5> notifications;
1722 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
1723 auto result = Notification::ConstructFromFields(
1724 aWorkerPrivate->GlobalScope(), mStrings[i].mID, mStrings[i].mTitle,
1725 mStrings[i].mDir, mStrings[i].mLang, mStrings[i].mBody,
1726 mStrings[i].mTag, mStrings[i].mIcon, mStrings[i].mData,
1727 /* mStrings[i].mBehavior, not
1728 * supported */
1729 mStrings[i].mServiceWorkerRegistrationScope);
1730 if (result.isErr()) {
1731 continue;
1733 RefPtr<Notification> n = result.unwrap();
1734 n->SetStoredState(true);
1735 notifications.AppendElement(n.forget());
1738 workerPromise->MaybeResolve(notifications);
1739 mPromiseProxy->CleanUp();
1743 class WorkerGetCallback final : public ScopeCheckingGetCallback {
1744 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1746 public:
1747 NS_DECL_ISUPPORTS
1749 WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
1750 : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) {
1751 AssertIsOnMainThread();
1752 MOZ_ASSERT(aProxy);
1755 NS_IMETHOD Done() final {
1756 AssertIsOnMainThread();
1757 MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
1759 RefPtr<PromiseWorkerProxy> proxy = std::move(mPromiseProxy);
1760 MutexAutoLock lock(proxy->Lock());
1761 if (proxy->CleanedUp()) {
1762 return NS_OK;
1765 RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
1766 proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
1768 r->Dispatch();
1769 return NS_OK;
1772 private:
1773 ~WorkerGetCallback() = default;
1776 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
1778 class WorkerGetRunnable final : public Runnable {
1779 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1780 const nsString mTag;
1781 const nsString mScope;
1783 public:
1784 WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag,
1785 const nsAString& aScope)
1786 : Runnable("WorkerGetRunnable"),
1787 mPromiseProxy(aProxy),
1788 mTag(aTag),
1789 mScope(aScope) {
1790 MOZ_ASSERT(mPromiseProxy);
1793 NS_IMETHOD
1794 Run() override {
1795 AssertIsOnMainThread();
1796 nsCOMPtr<nsINotificationStorageCallback> callback =
1797 new WorkerGetCallback(mPromiseProxy, mScope);
1799 nsresult rv;
1800 nsCOMPtr<nsINotificationStorage> notificationStorage =
1801 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
1802 if (NS_WARN_IF(NS_FAILED(rv))) {
1803 callback->Done();
1804 return rv;
1807 MutexAutoLock lock(mPromiseProxy->Lock());
1808 if (mPromiseProxy->CleanedUp()) {
1809 return NS_OK;
1812 nsString origin;
1813 rv = Notification::GetOrigin(
1814 mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
1815 if (NS_WARN_IF(NS_FAILED(rv))) {
1816 callback->Done();
1817 return rv;
1820 rv = notificationStorage->Get(origin, mTag, callback);
1821 if (NS_WARN_IF(NS_FAILED(rv))) {
1822 callback->Done();
1823 return rv;
1826 return NS_OK;
1829 private:
1830 ~WorkerGetRunnable() = default;
1833 // static
1834 already_AddRefed<Promise> Notification::WorkerGet(
1835 WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
1836 const nsAString& aScope, ErrorResult& aRv) {
1837 MOZ_ASSERT(aWorkerPrivate);
1838 aWorkerPrivate->AssertIsOnWorkerThread();
1839 RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
1840 if (NS_WARN_IF(aRv.Failed())) {
1841 return nullptr;
1844 RefPtr<PromiseWorkerProxy> proxy =
1845 PromiseWorkerProxy::Create(aWorkerPrivate, p);
1846 if (!proxy) {
1847 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
1848 return nullptr;
1851 RefPtr<WorkerGetRunnable> r =
1852 new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
1853 // Since this is called from script via
1854 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1855 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
1856 return p.forget();
1859 JSObject* Notification::WrapObject(JSContext* aCx,
1860 JS::Handle<JSObject*> aGivenProto) {
1861 return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
1864 void Notification::Close() {
1865 AssertIsOnTargetThread();
1866 auto ref = MakeUnique<NotificationRef>(this);
1867 if (!ref->Initialized()) {
1868 return;
1871 nsCOMPtr<nsIRunnable> closeNotificationTask = new NotificationTask(
1872 "Notification::Close", std::move(ref), NotificationTask::eClose);
1873 nsresult rv = DispatchToMainThread(closeNotificationTask.forget());
1875 if (NS_FAILED(rv)) {
1876 DispatchTrustedEvent(u"error"_ns);
1877 // If dispatch fails, NotificationTask will release the ref when it goes
1878 // out of scope at the end of this function.
1882 void Notification::CloseInternal(bool aContextClosed) {
1883 AssertIsOnMainThread();
1884 // Transfer ownership (if any) to local scope so we can release it at the end
1885 // of this function. This is relevant when the call is from
1886 // NotificationTask::Run().
1887 UniquePtr<NotificationRef> ownership;
1888 std::swap(ownership, mTempRef);
1890 SetAlertName();
1891 UnpersistNotification();
1892 if (!mIsClosed) {
1893 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1894 if (alertService) {
1895 nsAutoString alertName;
1896 GetAlertName(alertName);
1897 alertService->CloseAlert(alertName, aContextClosed);
1902 nsresult Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) {
1903 if (!aPrincipal) {
1904 return NS_ERROR_FAILURE;
1907 nsresult rv =
1908 nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, aOrigin);
1909 NS_ENSURE_SUCCESS(rv, rv);
1911 return NS_OK;
1914 bool Notification::RequireInteraction() const { return mRequireInteraction; }
1916 bool Notification::Silent() const { return mSilent; }
1918 void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
1919 aRetval = mVibrate.Clone();
1922 void Notification::GetData(JSContext* aCx,
1923 JS::MutableHandle<JS::Value> aRetval) {
1924 if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
1925 nsresult rv;
1926 RefPtr<nsStructuredCloneContainer> container =
1927 new nsStructuredCloneContainer();
1928 rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
1929 if (NS_WARN_IF(NS_FAILED(rv))) {
1930 aRetval.setNull();
1931 return;
1934 JS::Rooted<JS::Value> data(aCx);
1935 rv = container->DeserializeToJsval(aCx, &data);
1936 if (NS_WARN_IF(NS_FAILED(rv))) {
1937 aRetval.setNull();
1938 return;
1941 if (data.isGCThing()) {
1942 mozilla::HoldJSObjects(this);
1944 mData = data;
1946 if (mData.isNull()) {
1947 aRetval.setNull();
1948 return;
1951 aRetval.set(mData);
1954 void Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
1955 ErrorResult& aRv) {
1956 if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
1957 return;
1959 RefPtr<nsStructuredCloneContainer> dataObjectContainer =
1960 new nsStructuredCloneContainer();
1961 aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
1962 if (NS_WARN_IF(aRv.Failed())) {
1963 return;
1966 aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64);
1967 if (NS_WARN_IF(aRv.Failed())) {
1968 return;
1972 Result<Ok, QMResult> Notification::InitFromBase64(const nsAString& aData) {
1973 MOZ_ASSERT(mDataAsBase64.IsEmpty());
1974 if (aData.IsEmpty()) {
1975 // No data; skipping
1976 return Ok();
1979 // To and fro to ensure it is valid base64.
1980 RefPtr<nsStructuredCloneContainer> container =
1981 new nsStructuredCloneContainer();
1982 QM_TRY(QM_TO_RESULT(
1983 container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION)));
1984 QM_TRY(QM_TO_RESULT(container->GetDataAsBase64(mDataAsBase64)));
1986 return Ok();
1989 bool Notification::AddRefObject() {
1990 AssertIsOnTargetThread();
1991 MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerRef, mTaskCount == 0);
1992 MOZ_ASSERT_IF(mWorkerPrivate && mWorkerRef, mTaskCount > 0);
1993 if (mWorkerPrivate && !mWorkerRef) {
1994 if (!CreateWorkerRef()) {
1995 return false;
1998 AddRef();
1999 ++mTaskCount;
2000 return true;
2003 void Notification::ReleaseObject() {
2004 AssertIsOnTargetThread();
2005 MOZ_ASSERT(mTaskCount > 0);
2006 MOZ_ASSERT_IF(mWorkerPrivate, mWorkerRef);
2008 --mTaskCount;
2009 if (mWorkerPrivate && mTaskCount == 0) {
2010 MOZ_ASSERT(mWorkerRef);
2011 mWorkerRef = nullptr;
2013 Release();
2017 * Called from the worker, runs on main thread, blocks worker.
2019 * We can freely access mNotification here because the feature supplied it and
2020 * the Notification owns the feature.
2022 class CloseNotificationRunnable final : public WorkerMainThreadRunnable {
2023 Notification* mNotification;
2024 bool mHadObserver;
2026 public:
2027 explicit CloseNotificationRunnable(Notification* aNotification)
2028 : WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
2029 "Notification :: Close Notification"_ns),
2030 mNotification(aNotification),
2031 mHadObserver(false) {}
2033 bool MainThreadRun() override {
2034 if (mNotification->mObserver) {
2035 // The Notify() take's responsibility of releasing the Notification.
2036 mNotification->mObserver->ForgetNotification();
2037 mNotification->mObserver = nullptr;
2038 mHadObserver = true;
2040 mNotification->CloseInternal();
2041 return true;
2044 bool HadObserver() { return mHadObserver; }
2047 bool Notification::CreateWorkerRef() {
2048 MOZ_ASSERT(mWorkerPrivate);
2049 mWorkerPrivate->AssertIsOnWorkerThread();
2050 MOZ_ASSERT(!mWorkerRef);
2052 RefPtr<Notification> self = this;
2053 mWorkerRef =
2054 StrongWorkerRef::Create(mWorkerPrivate, "Notification", [self]() {
2055 // CloseNotificationRunnable blocks the worker by pushing a sync event
2056 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2057 // the worker can still continue running. One of these is
2058 // ReleaseNotificationControlRunnable that releases the notification,
2059 // invalidating the notification and this feature. We hold this
2060 // reference to keep the notification valid until we are done with it.
2062 // An example of when the control runnable could get dispatched to the
2063 // worker is if a Notification is created and the worker is immediately
2064 // closed, but there is no permission to show it so that the main thread
2065 // immediately drops the NotificationRef. In this case, this function
2066 // blocks on the main thread, but the main thread dispatches the control
2067 // runnable, invalidating mNotification.
2069 // Dispatched to main thread, blocks on closing the Notification.
2070 RefPtr<CloseNotificationRunnable> r =
2071 new CloseNotificationRunnable(self);
2072 ErrorResult rv;
2073 r->Dispatch(Killing, rv);
2074 // XXXbz I'm told throwing and returning false from here is pointless
2075 // (and also that doing sync stuff from here is really weird), so I
2076 // guess we just suppress the exception on rv, if any.
2077 rv.SuppressException();
2079 // Only call ReleaseObject() to match the observer's NotificationRef
2080 // ownership (since CloseNotificationRunnable asked the observer to drop
2081 // the reference to the notification).
2082 if (r->HadObserver()) {
2083 self->ReleaseObject();
2086 // From this point we cannot touch properties of this feature because
2087 // ReleaseObject() may have led to the notification going away and the
2088 // notification owns this feature!
2091 if (NS_WARN_IF(!mWorkerRef)) {
2092 return false;
2095 return true;
2099 * Checks:
2100 * 1) Is aWorker allowed to show a notification for scope?
2101 * 2) Is aWorker an active worker?
2103 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2105 class CheckLoadRunnable final : public WorkerMainThreadRunnable {
2106 nsresult mRv;
2107 nsCString mScope;
2108 ServiceWorkerRegistrationDescriptor mDescriptor;
2110 public:
2111 explicit CheckLoadRunnable(
2112 WorkerPrivate* aWorker, const nsACString& aScope,
2113 const ServiceWorkerRegistrationDescriptor& aDescriptor)
2114 : WorkerMainThreadRunnable(aWorker, "Notification :: Check Load"_ns),
2115 mRv(NS_ERROR_DOM_SECURITY_ERR),
2116 mScope(aScope),
2117 mDescriptor(aDescriptor) {}
2119 bool MainThreadRun() override {
2120 nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
2121 mRv = CheckScope(principal, mScope, mWorkerPrivate->WindowID());
2123 if (NS_FAILED(mRv)) {
2124 return true;
2127 auto activeWorker = mDescriptor.GetActive();
2129 if (!activeWorker ||
2130 activeWorker.ref().Id() != mWorkerPrivate->ServiceWorkerID()) {
2131 mRv = NS_ERROR_NOT_AVAILABLE;
2134 return true;
2137 nsresult Result() { return mRv; }
2140 // Step 2, 5, 6 of
2141 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification
2142 /* static */
2143 already_AddRefed<Promise> Notification::ShowPersistentNotification(
2144 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
2145 const nsAString& aTitle, const NotificationOptions& aOptions,
2146 const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
2147 MOZ_ASSERT(aGlobal);
2149 // Validate scope.
2150 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2151 // thread. On calls from content, we can be sure the scope is valid since
2152 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2153 // debug only? The problem is that there would be different semantics in
2154 // debug and non-debug builds in such a case.
2155 if (NS_IsMainThread()) {
2156 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
2157 if (NS_WARN_IF(!sop)) {
2158 aRv.Throw(NS_ERROR_UNEXPECTED);
2159 return nullptr;
2162 nsIPrincipal* principal = sop->GetPrincipal();
2163 if (NS_WARN_IF(!principal)) {
2164 aRv.Throw(NS_ERROR_UNEXPECTED);
2165 return nullptr;
2168 uint64_t windowID = 0;
2169 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
2170 if (win) {
2171 windowID = win->WindowID();
2174 aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope), windowID);
2175 if (NS_WARN_IF(aRv.Failed())) {
2176 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2177 return nullptr;
2179 } else {
2180 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
2181 MOZ_ASSERT(worker);
2182 worker->AssertIsOnWorkerThread();
2184 RefPtr<CheckLoadRunnable> loadChecker = new CheckLoadRunnable(
2185 worker, NS_ConvertUTF16toUTF8(aScope), aDescriptor);
2186 loadChecker->Dispatch(Canceling, aRv);
2187 if (aRv.Failed()) {
2188 return nullptr;
2191 if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
2192 if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
2193 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
2194 } else {
2195 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2197 return nullptr;
2201 // Step 2: Let promise be a new promise in this’s relevant Realm.
2202 RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
2203 if (NS_WARN_IF(aRv.Failed())) {
2204 return nullptr;
2207 // We check permission here rather than pass the Promise to NotificationTask
2208 // which leads to uglier code.
2209 // XXX: GetPermission is a synchronous blocking function on workers.
2210 NotificationPermission permission = GetPermission(aGlobal, aRv);
2212 // Step 6.1: If the result of getting the notifications permission state is
2213 // not "granted", then queue a global task on the DOM manipulation task source
2214 // given global to reject promise with a TypeError, and abort these steps.
2215 if (NS_WARN_IF(aRv.Failed()) ||
2216 permission != NotificationPermission::Granted) {
2217 p->MaybeRejectWithTypeError("Permission to show Notification denied.");
2218 return p.forget();
2221 // "Otherwise, resolve promise with undefined."
2222 // The Notification may still not be shown due to other errors, but the spec
2223 // is not concerned with those.
2224 p->MaybeResolveWithUndefined();
2226 // Step 5: Let notification be the result of creating a notification given
2227 // title, options, this’s relevant settings object, and
2228 // serviceWorkerRegistration. If this threw an exception, then reject promise
2229 // with that exception and return promise.
2231 // XXX: This should happen before the permission check per the spec, as this
2232 // can throw errors too. This should be split into create and show.
2233 RefPtr<Notification> notification =
2234 CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
2235 if (NS_WARN_IF(aRv.Failed())) {
2236 return nullptr;
2239 return p.forget();
2242 /* static */
2243 already_AddRefed<Notification> Notification::CreateAndShow(
2244 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
2245 const NotificationOptions& aOptions, const nsAString& aScope,
2246 ErrorResult& aRv) {
2247 MOZ_ASSERT(aGlobal);
2249 RefPtr<Notification> notification =
2250 CreateInternal(aGlobal, u""_ns, aTitle, aOptions, aRv);
2251 if (aRv.Failed()) {
2252 return nullptr;
2255 // Make a structured clone of the aOptions.mData object
2256 JS::Rooted<JS::Value> data(aCx, aOptions.mData);
2257 notification->InitFromJSVal(aCx, data, aRv);
2258 if (NS_WARN_IF(aRv.Failed())) {
2259 return nullptr;
2262 notification->SetScope(aScope);
2264 auto ref = MakeUnique<NotificationRef>(notification);
2265 if (NS_WARN_IF(!ref->Initialized())) {
2266 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2267 return nullptr;
2270 // Queue a task to show the notification.
2271 nsCOMPtr<nsIRunnable> showNotificationTask = new NotificationTask(
2272 "Notification::CreateAndShow", std::move(ref), NotificationTask::eShow);
2274 nsresult rv =
2275 notification->DispatchToMainThread(showNotificationTask.forget());
2277 if (NS_WARN_IF(NS_FAILED(rv))) {
2278 notification->DispatchTrustedEvent(u"error"_ns);
2281 return notification.forget();
2284 /* static */
2285 nsresult Notification::RemovePermission(nsIPrincipal* aPrincipal) {
2286 MOZ_ASSERT(XRE_IsParentProcess());
2287 nsCOMPtr<nsIPermissionManager> permissionManager =
2288 mozilla::components::PermissionManager::Service();
2289 if (!permissionManager) {
2290 return NS_ERROR_FAILURE;
2292 permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"_ns);
2293 return NS_OK;
2296 /* static */
2297 nsresult Notification::OpenSettings(nsIPrincipal* aPrincipal) {
2298 MOZ_ASSERT(XRE_IsParentProcess());
2299 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2300 if (!obs) {
2301 return NS_ERROR_FAILURE;
2303 // Notify other observers so they can show settings UI.
2304 obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
2305 return NS_OK;
2308 NS_IMETHODIMP
2309 Notification::Observe(nsISupports* aSubject, const char* aTopic,
2310 const char16_t* aData) {
2311 AssertIsOnMainThread();
2313 if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
2314 !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
2315 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
2316 if (SameCOMIdentity(aSubject, window)) {
2317 nsCOMPtr<nsIObserverService> obs =
2318 mozilla::services::GetObserverService();
2319 if (obs) {
2320 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
2321 obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
2324 CloseInternal(true);
2328 return NS_OK;
2331 nsresult Notification::DispatchToMainThread(
2332 already_AddRefed<nsIRunnable>&& aRunnable) {
2333 if (mWorkerPrivate) {
2334 return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable));
2336 AssertIsOnMainThread();
2337 return NS_DispatchToCurrentThread(std::move(aRunnable));
2340 } // namespace mozilla::dom