Bug 1685822 [wpt PR 27117] - [Import Maps] Add tests for rejecting multiple import...
[gecko.git] / dom / notification / Notification.cpp
blob9ac49ad445c4fa2f4c73e98ccdef65c1faae377f
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/JSONWriter.h"
17 #include "mozilla/OwningNonNull.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/Services.h"
20 #include "mozilla/StaticPrefs_dom.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
24 #include "mozilla/dom/BindingUtils.h"
25 #include "mozilla/dom/ContentChild.h"
26 #include "mozilla/dom/Document.h"
27 #include "mozilla/dom/NotificationEvent.h"
28 #include "mozilla/dom/PermissionMessageUtils.h"
29 #include "mozilla/dom/Promise.h"
30 #include "mozilla/dom/PromiseWorkerProxy.h"
31 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
32 #include "mozilla/dom/ServiceWorkerManager.h"
33 #include "mozilla/dom/ServiceWorkerUtils.h"
34 #include "mozilla/dom/WorkerPrivate.h"
35 #include "mozilla/dom/WorkerRunnable.h"
36 #include "mozilla/dom/WorkerScope.h"
37 #include "nsAlertsUtils.h"
38 #include "nsCRTGlue.h"
39 #include "nsComponentManagerUtils.h"
40 #include "nsContentPermissionHelper.h"
41 #include "nsContentUtils.h"
42 #include "nsDOMJSUtils.h"
43 #include "nsFocusManager.h"
44 #include "nsGlobalWindow.h"
45 #include "nsIAlertsService.h"
46 #include "nsIContentPermissionPrompt.h"
47 #include "nsILoadContext.h"
48 #include "nsINotificationStorage.h"
49 #include "nsIPermission.h"
50 #include "nsIPermissionManager.h"
51 #include "nsIPushService.h"
52 #include "nsIScriptError.h"
53 #include "nsIServiceWorkerManager.h"
54 #include "nsISimpleEnumerator.h"
55 #include "nsIUUIDGenerator.h"
56 #include "nsNetUtil.h"
57 #include "nsProxyRelease.h"
58 #include "nsServiceManagerUtils.h"
59 #include "nsStructuredCloneContainer.h"
60 #include "nsThreadUtils.h"
61 #include "nsXULAppAPI.h"
63 namespace mozilla::dom {
65 struct NotificationStrings {
66 const nsString mID;
67 const nsString mTitle;
68 const nsString mDir;
69 const nsString mLang;
70 const nsString mBody;
71 const nsString mTag;
72 const nsString mIcon;
73 const nsString mData;
74 const nsString mBehavior;
75 const nsString mServiceWorkerRegistrationScope;
78 class ScopeCheckingGetCallback : public nsINotificationStorageCallback {
79 const nsString mScope;
81 public:
82 explicit ScopeCheckingGetCallback(const nsAString& aScope) : mScope(aScope) {}
84 NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle,
85 const nsAString& aDir, const nsAString& aLang,
86 const nsAString& aBody, const nsAString& aTag,
87 const nsAString& aIcon, const nsAString& aData,
88 const nsAString& aBehavior,
89 const nsAString& aServiceWorkerRegistrationScope) final {
90 AssertIsOnMainThread();
91 MOZ_ASSERT(!aID.IsEmpty());
93 // Skip scopes that don't match when called from getNotifications().
94 if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) {
95 return NS_OK;
98 NotificationStrings strings = {
99 nsString(aID), nsString(aTitle),
100 nsString(aDir), nsString(aLang),
101 nsString(aBody), nsString(aTag),
102 nsString(aIcon), nsString(aData),
103 nsString(aBehavior), nsString(aServiceWorkerRegistrationScope),
106 mStrings.AppendElement(std::move(strings));
107 return NS_OK;
110 NS_IMETHOD Done() override = 0;
112 protected:
113 virtual ~ScopeCheckingGetCallback() = default;
115 nsTArray<NotificationStrings> mStrings;
118 class NotificationStorageCallback final : public ScopeCheckingGetCallback {
119 public:
120 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
121 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
123 NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
124 Promise* aPromise)
125 : ScopeCheckingGetCallback(aScope), mWindow(aWindow), mPromise(aPromise) {
126 AssertIsOnMainThread();
127 MOZ_ASSERT(aWindow);
128 MOZ_ASSERT(aPromise);
131 NS_IMETHOD Done() final {
132 ErrorResult result;
133 AutoTArray<RefPtr<Notification>, 5> notifications;
135 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
136 RefPtr<Notification> n = Notification::ConstructFromFields(
137 mWindow, mStrings[i].mID, mStrings[i].mTitle, mStrings[i].mDir,
138 mStrings[i].mLang, mStrings[i].mBody, mStrings[i].mTag,
139 mStrings[i].mIcon, mStrings[i].mData,
140 /* mStrings[i].mBehavior, not
141 * supported */
142 mStrings[i].mServiceWorkerRegistrationScope, result);
144 n->SetStoredState(true);
145 Unused << NS_WARN_IF(result.Failed());
146 if (!result.Failed()) {
147 notifications.AppendElement(n.forget());
151 mPromise->MaybeResolve(notifications);
152 return NS_OK;
155 private:
156 virtual ~NotificationStorageCallback() = default;
158 nsCOMPtr<nsIGlobalObject> mWindow;
159 RefPtr<Promise> mPromise;
160 const nsString mScope;
163 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
164 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
165 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
167 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
168 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
169 NS_INTERFACE_MAP_ENTRY(nsISupports)
170 NS_INTERFACE_MAP_END
172 class NotificationGetRunnable final : public Runnable {
173 const nsString mOrigin;
174 const nsString mTag;
175 nsCOMPtr<nsINotificationStorageCallback> mCallback;
177 public:
178 NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
179 nsINotificationStorageCallback* aCallback)
180 : Runnable("NotificationGetRunnable"),
181 mOrigin(aOrigin),
182 mTag(aTag),
183 mCallback(aCallback) {}
185 NS_IMETHOD
186 Run() override {
187 nsresult rv;
188 nsCOMPtr<nsINotificationStorage> notificationStorage =
189 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
190 if (NS_WARN_IF(NS_FAILED(rv))) {
191 return rv;
194 rv = notificationStorage->Get(mOrigin, mTag, mCallback);
195 // XXXnsm Is it guaranteed mCallback will be called in case of failure?
196 Unused << NS_WARN_IF(NS_FAILED(rv));
197 return rv;
201 class NotificationPermissionRequest : public ContentPermissionRequestBase,
202 public nsIRunnable,
203 public nsINamed {
204 public:
205 NS_DECL_NSIRUNNABLE
206 NS_DECL_ISUPPORTS_INHERITED
207 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
208 ContentPermissionRequestBase)
210 // nsIContentPermissionRequest
211 NS_IMETHOD Cancel(void) override;
212 NS_IMETHOD Allow(JS::HandleValue choices) override;
214 NotificationPermissionRequest(nsIPrincipal* aPrincipal,
215 nsPIDOMWindowInner* aWindow, Promise* aPromise,
216 NotificationPermissionCallback* aCallback)
217 : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
218 "desktop-notification"_ns),
219 mPermission(NotificationPermission::Default),
220 mPromise(aPromise),
221 mCallback(aCallback) {
222 MOZ_ASSERT(aPromise);
225 NS_IMETHOD GetName(nsACString& aName) override {
226 aName.AssignLiteral("NotificationPermissionRequest");
227 return NS_OK;
230 protected:
231 ~NotificationPermissionRequest() = default;
233 MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
234 nsresult DispatchResolvePromise();
235 NotificationPermission mPermission;
236 RefPtr<Promise> mPromise;
237 RefPtr<NotificationPermissionCallback> mCallback;
240 namespace {
241 class ReleaseNotificationControlRunnable final
242 : public MainThreadWorkerControlRunnable {
243 Notification* mNotification;
245 public:
246 explicit ReleaseNotificationControlRunnable(Notification* aNotification)
247 : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate),
248 mNotification(aNotification) {}
250 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
251 mNotification->ReleaseObject();
252 return true;
256 class GetPermissionRunnable final : public WorkerMainThreadRunnable {
257 NotificationPermission mPermission;
259 public:
260 explicit GetPermissionRunnable(WorkerPrivate* aWorker)
261 : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns),
262 mPermission(NotificationPermission::Denied) {}
264 bool MainThreadRun() override {
265 ErrorResult result;
266 mPermission = Notification::GetPermissionInternal(
267 mWorkerPrivate->GetPrincipal(), result);
268 return true;
271 NotificationPermission GetPermission() { return mPermission; }
274 class FocusWindowRunnable final : public Runnable {
275 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
277 public:
278 explicit FocusWindowRunnable(
279 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
280 : Runnable("FocusWindowRunnable"), mWindow(aWindow) {}
282 NS_IMETHOD
283 Run() override {
284 AssertIsOnMainThread();
285 if (!mWindow->IsCurrentInnerWindow()) {
286 // Window has been closed, this observer is not valid anymore
287 return NS_OK;
290 nsFocusManager::FocusWindow(mWindow->GetOuterWindow(), CallerType::System);
291 return NS_OK;
295 nsresult CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope,
296 uint64_t aWindowID) {
297 AssertIsOnMainThread();
298 MOZ_ASSERT(aPrincipal);
300 nsCOMPtr<nsIURI> scopeURI;
301 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
302 if (NS_WARN_IF(NS_FAILED(rv))) {
303 return rv;
306 return aPrincipal->CheckMayLoadWithReporting(
307 scopeURI,
308 /* allowIfInheritsPrincipal = */ false, aWindowID);
310 } // anonymous namespace
312 // Subclass that can be directly dispatched to child workers from the main
313 // thread.
314 class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
315 protected:
316 explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate)
317 : MainThreadWorkerRunnable(aWorkerPrivate) {}
319 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
320 aWorkerPrivate->AssertIsOnWorkerThread();
321 aWorkerPrivate->ModifyBusyCountFromWorker(true);
322 WorkerRunInternal(aWorkerPrivate);
323 return true;
326 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
327 bool aRunResult) override {
328 aWorkerPrivate->ModifyBusyCountFromWorker(false);
331 virtual void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0;
334 // Overrides dispatch and run handlers so we can directly dispatch from main
335 // thread to child workers.
336 class NotificationEventWorkerRunnable final
337 : public NotificationWorkerRunnable {
338 Notification* mNotification;
339 const nsString mEventName;
341 public:
342 NotificationEventWorkerRunnable(Notification* aNotification,
343 const nsString& aEventName)
344 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
345 mNotification(aNotification),
346 mEventName(aEventName) {}
348 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
349 mNotification->DispatchTrustedEvent(mEventName);
353 class ReleaseNotificationRunnable final : public NotificationWorkerRunnable {
354 Notification* mNotification;
356 public:
357 explicit ReleaseNotificationRunnable(Notification* aNotification)
358 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
359 mNotification(aNotification) {}
361 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
362 mNotification->ReleaseObject();
365 nsresult Cancel() override {
366 mNotification->ReleaseObject();
367 return NotificationWorkerRunnable::Cancel();
371 // Create one whenever you require ownership of the notification. Use with
372 // UniquePtr<>. See Notification.h for details.
373 class NotificationRef final {
374 friend class WorkerNotificationObserver;
376 private:
377 Notification* mNotification;
378 bool mInited;
380 // Only useful for workers.
381 void Forget() { mNotification = nullptr; }
383 public:
384 explicit NotificationRef(Notification* aNotification)
385 : mNotification(aNotification) {
386 MOZ_ASSERT(mNotification);
387 if (mNotification->mWorkerPrivate) {
388 mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
389 } else {
390 AssertIsOnMainThread();
393 mInited = mNotification->AddRefObject();
396 // This is only required because Gecko runs script in a worker's onclose
397 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
398 // fail. Due to non-standardness and added complications if we decide to
399 // support this, attempts to create a Notification in onclose just throw
400 // exceptions.
401 bool Initialized() { return mInited; }
403 ~NotificationRef() {
404 if (Initialized() && mNotification) {
405 Notification* notification = mNotification;
406 mNotification = nullptr;
407 if (notification->mWorkerPrivate && NS_IsMainThread()) {
408 // Try to pass ownership back to the worker. If the dispatch succeeds we
409 // are guaranteed this runnable will run, and that it will run after
410 // queued event runnables, so event runnables will have a safe pointer
411 // to the Notification.
413 // If the dispatch fails, the worker isn't running anymore and the event
414 // runnables have already run or been canceled. We can use a control
415 // runnable to release the reference.
416 RefPtr<ReleaseNotificationRunnable> r =
417 new ReleaseNotificationRunnable(notification);
419 if (!r->Dispatch()) {
420 RefPtr<ReleaseNotificationControlRunnable> r =
421 new ReleaseNotificationControlRunnable(notification);
422 MOZ_ALWAYS_TRUE(r->Dispatch());
424 } else {
425 notification->AssertIsOnTargetThread();
426 notification->ReleaseObject();
431 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
432 // a rawptr that the NotificationRef can invalidate?
433 Notification* GetNotification() {
434 MOZ_ASSERT(Initialized());
435 return mNotification;
439 class NotificationTask : public Runnable {
440 public:
441 enum NotificationAction { eShow, eClose };
443 NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef,
444 NotificationAction aAction)
445 : Runnable(aName), mNotificationRef(std::move(aRef)), mAction(aAction) {}
447 NS_IMETHOD
448 Run() override;
450 protected:
451 virtual ~NotificationTask() = default;
453 UniquePtr<NotificationRef> mNotificationRef;
454 NotificationAction mAction;
457 uint32_t Notification::sCount = 0;
459 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
460 ContentPermissionRequestBase, mCallback)
461 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
462 ContentPermissionRequestBase)
463 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
464 ContentPermissionRequestBase)
466 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
467 NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
468 nsINamed)
470 NS_IMETHODIMP
471 NotificationPermissionRequest::Run() {
472 bool isSystem = mPrincipal->IsSystemPrincipal();
473 bool blocked = false;
474 if (isSystem) {
475 mPermission = NotificationPermission::Granted;
476 } else {
477 // File are automatically granted permission.
479 if (mPrincipal->SchemeIs("file")) {
480 mPermission = NotificationPermission::Granted;
481 } else if (!StaticPrefs::dom_webnotifications_allowinsecure() &&
482 !mWindow->IsSecureContext()) {
483 mPermission = NotificationPermission::Denied;
484 blocked = true;
485 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
486 if (doc) {
487 nsContentUtils::ReportToConsole(
488 nsIScriptError::errorFlag, "DOM"_ns, doc,
489 nsContentUtils::eDOM_PROPERTIES,
490 "NotificationsInsecureRequestIsForbidden");
495 // We can't call ShowPrompt() directly here since our logic for determining
496 // whether to display a prompt depends on the checks above as well as the
497 // result of CheckPromptPrefs(). So we have to manually check the prompt
498 // prefs and decide what to do based on that.
499 PromptResult pr = CheckPromptPrefs();
500 switch (pr) {
501 case PromptResult::Granted:
502 mPermission = NotificationPermission::Granted;
503 break;
504 case PromptResult::Denied:
505 mPermission = NotificationPermission::Denied;
506 break;
507 default:
508 // ignore
509 break;
512 if (!mIsHandlingUserInput &&
513 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
514 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
515 if (doc) {
516 doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
520 // Check this after checking the prompt prefs to make sure this pref overrides
521 // those. We rely on this for testing purposes.
522 if (!isSystem && !blocked &&
523 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
524 !mPrincipal->Subsumes(mTopLevelPrincipal)) {
525 mPermission = NotificationPermission::Denied;
526 blocked = true;
527 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
528 if (doc) {
529 nsContentUtils::ReportToConsole(
530 nsIScriptError::errorFlag, "DOM"_ns, doc,
531 nsContentUtils::eDOM_PROPERTIES,
532 "NotificationsCrossOriginIframeRequestIsForbidden");
536 if (mPermission != NotificationPermission::Default) {
537 return DispatchResolvePromise();
540 return nsContentPermissionUtils::AskPermission(this, mWindow);
543 NS_IMETHODIMP
544 NotificationPermissionRequest::Cancel() {
545 // `Cancel` is called if the user denied permission or dismissed the
546 // permission request. To distinguish between the two, we set the
547 // permission to "default" and query the permission manager in
548 // `ResolvePromise`.
549 mPermission = NotificationPermission::Default;
550 return DispatchResolvePromise();
553 NS_IMETHODIMP
554 NotificationPermissionRequest::Allow(JS::HandleValue aChoices) {
555 MOZ_ASSERT(aChoices.isUndefined());
557 mPermission = NotificationPermission::Granted;
558 return DispatchResolvePromise();
561 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
562 nsCOMPtr<nsIRunnable> resolver =
563 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
564 this, &NotificationPermissionRequest::ResolvePromise);
565 if (nsIEventTarget* target = mWindow->EventTargetFor(TaskCategory::Other)) {
566 return target->Dispatch(resolver.forget(), nsIEventTarget::DISPATCH_NORMAL);
568 return NS_ERROR_FAILURE;
571 nsresult NotificationPermissionRequest::ResolvePromise() {
572 nsresult rv = NS_OK;
573 // This will still be "default" if the user dismissed the doorhanger,
574 // or "denied" otherwise.
575 if (mPermission == NotificationPermission::Default) {
576 // When the front-end has decided to deny the permission request
577 // automatically and we are not handling user input, then log a
578 // warning in the current document that this happened because
579 // Notifications require a user gesture.
580 if (!mIsHandlingUserInput &&
581 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
582 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
583 if (doc) {
584 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
585 doc, nsContentUtils::eDOM_PROPERTIES,
586 "NotificationsRequireUserGesture");
590 mPermission = Notification::TestPermission(mPrincipal);
592 if (mCallback) {
593 ErrorResult error;
594 RefPtr<NotificationPermissionCallback> callback(mCallback);
595 callback->Call(mPermission, error);
596 rv = error.StealNSResult();
598 mPromise->MaybeResolve(mPermission);
599 return rv;
602 // Observer that the alert service calls to do common tasks and/or dispatch to
603 // the specific observer for the context e.g. main thread, worker, or service
604 // worker.
605 class NotificationObserver final : public nsIObserver {
606 public:
607 nsCOMPtr<nsIObserver> mObserver;
608 nsCOMPtr<nsIPrincipal> mPrincipal;
609 bool mInPrivateBrowsing;
610 NS_DECL_ISUPPORTS
611 NS_DECL_NSIOBSERVER
613 NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
614 bool aInPrivateBrowsing)
615 : mObserver(aObserver),
616 mPrincipal(aPrincipal),
617 mInPrivateBrowsing(aInPrivateBrowsing) {
618 AssertIsOnMainThread();
619 MOZ_ASSERT(mObserver);
620 MOZ_ASSERT(mPrincipal);
623 protected:
624 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
626 nsresult AdjustPushQuota(const char* aTopic);
629 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
631 class MainThreadNotificationObserver : public nsIObserver {
632 public:
633 UniquePtr<NotificationRef> mNotificationRef;
634 NS_DECL_ISUPPORTS
635 NS_DECL_NSIOBSERVER
637 explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
638 : mNotificationRef(std::move(aRef)) {
639 AssertIsOnMainThread();
642 protected:
643 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
646 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
648 NS_IMETHODIMP
649 NotificationTask::Run() {
650 AssertIsOnMainThread();
652 // Get a pointer to notification before the notification takes ownership of
653 // the ref (it owns itself temporarily, with ShowInternal() and
654 // CloseInternal() passing on the ownership appropriately.)
655 Notification* notif = mNotificationRef->GetNotification();
656 notif->mTempRef.swap(mNotificationRef);
657 if (mAction == eShow) {
658 notif->ShowInternal();
659 } else if (mAction == eClose) {
660 notif->CloseInternal();
661 } else {
662 MOZ_CRASH("Invalid action");
665 MOZ_ASSERT(!mNotificationRef);
666 return NS_OK;
669 // static
670 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
671 if (!NS_IsMainThread()) {
672 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
673 if (!workerPrivate) {
674 return false;
677 if (workerPrivate->IsServiceWorker()) {
678 return StaticPrefs::dom_webnotifications_serviceworker_enabled();
682 return StaticPrefs::dom_webnotifications_enabled();
685 // static
686 bool Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj) {
687 return NS_IsMainThread();
690 Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
691 const nsAString& aTitle, const nsAString& aBody,
692 NotificationDirection aDir, const nsAString& aLang,
693 const nsAString& aTag, const nsAString& aIconUrl,
694 bool aRequireInteraction,
695 const NotificationBehavior& aBehavior)
696 : DOMEventTargetHelper(aGlobal),
697 mWorkerPrivate(nullptr),
698 mObserver(nullptr),
699 mID(aID),
700 mTitle(aTitle),
701 mBody(aBody),
702 mDir(aDir),
703 mLang(aLang),
704 mTag(aTag),
705 mIconUrl(aIconUrl),
706 mRequireInteraction(aRequireInteraction),
707 mBehavior(aBehavior),
708 mData(JS::NullValue()),
709 mIsClosed(false),
710 mIsStored(false),
711 mTaskCount(0) {
712 if (!NS_IsMainThread()) {
713 mWorkerPrivate = GetCurrentThreadWorkerPrivate();
714 MOZ_ASSERT(mWorkerPrivate);
718 nsresult Notification::Init() {
719 if (!mWorkerPrivate) {
720 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
721 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
723 nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
724 NS_ENSURE_SUCCESS(rv, rv);
726 rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
727 NS_ENSURE_SUCCESS(rv, rv);
730 return NS_OK;
733 void Notification::SetAlertName() {
734 AssertIsOnMainThread();
735 if (!mAlertName.IsEmpty()) {
736 return;
739 nsAutoString alertName;
740 nsresult rv = GetOrigin(GetPrincipal(), alertName);
741 if (NS_WARN_IF(NS_FAILED(rv))) {
742 return;
745 // Get the notification name that is unique per origin + tag/ID.
746 // The name of the alert is of the form origin#tag/ID.
747 alertName.Append('#');
748 if (!mTag.IsEmpty()) {
749 alertName.AppendLiteral("tag:");
750 alertName.Append(mTag);
751 } else {
752 alertName.AppendLiteral("notag:");
753 alertName.Append(mID);
756 mAlertName = alertName;
759 // May be called on any thread.
760 // static
761 already_AddRefed<Notification> Notification::Constructor(
762 const GlobalObject& aGlobal, const nsAString& aTitle,
763 const NotificationOptions& aOptions, ErrorResult& aRv) {
764 // FIXME(nsm): If the sticky flag is set, throw an error.
765 RefPtr<ServiceWorkerGlobalScope> scope;
766 UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
767 if (scope) {
768 aRv.ThrowTypeError(
769 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
770 "Use registration.showNotification() instead.");
771 return nullptr;
774 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
775 RefPtr<Notification> notification =
776 CreateAndShow(aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
777 if (NS_WARN_IF(aRv.Failed())) {
778 return nullptr;
781 // This is be ok since we are on the worker thread where this function will
782 // run to completion before the Notification has a chance to go away.
783 return notification.forget();
786 // static
787 already_AddRefed<Notification> Notification::ConstructFromFields(
788 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
789 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
790 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
791 const nsAString& aServiceWorkerRegistrationScope, ErrorResult& aRv) {
792 MOZ_ASSERT(aGlobal);
794 RootedDictionary<NotificationOptions> options(RootingCx());
795 options.mDir = Notification::StringToDirection(nsString(aDir));
796 options.mLang = aLang;
797 options.mBody = aBody;
798 options.mTag = aTag;
799 options.mIcon = aIcon;
800 RefPtr<Notification> notification =
801 CreateInternal(aGlobal, aID, aTitle, options);
803 notification->InitFromBase64(aData, aRv);
804 if (NS_WARN_IF(aRv.Failed())) {
805 return nullptr;
808 notification->SetScope(aServiceWorkerRegistrationScope);
810 return notification.forget();
813 nsresult Notification::PersistNotification() {
814 AssertIsOnMainThread();
815 nsresult rv;
816 nsCOMPtr<nsINotificationStorage> notificationStorage =
817 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
818 if (NS_FAILED(rv)) {
819 return rv;
822 nsString origin;
823 rv = GetOrigin(GetPrincipal(), origin);
824 if (NS_WARN_IF(NS_FAILED(rv))) {
825 return rv;
828 nsString id;
829 GetID(id);
831 nsString alertName;
832 GetAlertName(alertName);
834 nsAutoString behavior;
835 if (!mBehavior.ToJSON(behavior)) {
836 return NS_ERROR_FAILURE;
839 rv = notificationStorage->Put(origin, id, mTitle, DirectionToString(mDir),
840 mLang, mBody, mTag, mIconUrl, alertName,
841 mDataAsBase64, behavior, mScope);
843 if (NS_FAILED(rv)) {
844 return rv;
847 SetStoredState(true);
848 return NS_OK;
851 void Notification::UnpersistNotification() {
852 AssertIsOnMainThread();
853 if (IsStored()) {
854 nsCOMPtr<nsINotificationStorage> notificationStorage =
855 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
856 if (notificationStorage) {
857 nsString origin;
858 nsresult rv = GetOrigin(GetPrincipal(), origin);
859 if (NS_SUCCEEDED(rv)) {
860 notificationStorage->Delete(origin, mID);
863 SetStoredState(false);
867 already_AddRefed<Notification> Notification::CreateInternal(
868 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
869 const NotificationOptions& aOptions) {
870 nsresult rv;
871 nsString id;
872 if (!aID.IsEmpty()) {
873 id = aID;
874 } else {
875 nsCOMPtr<nsIUUIDGenerator> uuidgen =
876 do_GetService("@mozilla.org/uuid-generator;1");
877 NS_ENSURE_TRUE(uuidgen, nullptr);
878 nsID uuid;
879 rv = uuidgen->GenerateUUIDInPlace(&uuid);
880 NS_ENSURE_SUCCESS(rv, nullptr);
882 char buffer[NSID_LENGTH];
883 uuid.ToProvidedString(buffer);
884 NS_ConvertASCIItoUTF16 convertedID(buffer);
885 id = convertedID;
888 RefPtr<Notification> notification =
889 new Notification(aGlobal, id, aTitle, aOptions.mBody, aOptions.mDir,
890 aOptions.mLang, aOptions.mTag, aOptions.mIcon,
891 aOptions.mRequireInteraction, aOptions.mMozbehavior);
892 rv = notification->Init();
893 NS_ENSURE_SUCCESS(rv, nullptr);
894 return notification.forget();
897 Notification::~Notification() {
898 mData.setUndefined();
899 mozilla::DropJSObjects(this);
900 AssertIsOnTargetThread();
901 MOZ_ASSERT(!mWorkerRef);
902 MOZ_ASSERT(!mTempRef);
905 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
906 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
907 DOMEventTargetHelper)
908 tmp->mData.setUndefined();
909 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
910 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
912 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
913 DOMEventTargetHelper)
914 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
916 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
917 DOMEventTargetHelper)
918 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
919 NS_IMPL_CYCLE_COLLECTION_TRACE_END
921 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
922 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
924 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
925 NS_INTERFACE_MAP_ENTRY(nsIObserver)
926 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
927 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
929 nsIPrincipal* Notification::GetPrincipal() {
930 AssertIsOnMainThread();
931 if (mWorkerPrivate) {
932 return mWorkerPrivate->GetPrincipal();
933 } else {
934 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
935 NS_ENSURE_TRUE(sop, nullptr);
936 return sop->GetPrincipal();
940 class WorkerNotificationObserver final : public MainThreadNotificationObserver {
941 public:
942 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver,
943 MainThreadNotificationObserver)
944 NS_DECL_NSIOBSERVER
946 explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
947 : MainThreadNotificationObserver(std::move(aRef)) {
948 AssertIsOnMainThread();
949 MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate);
952 void ForgetNotification() {
953 AssertIsOnMainThread();
954 mNotificationRef->Forget();
957 protected:
958 virtual ~WorkerNotificationObserver() {
959 AssertIsOnMainThread();
961 MOZ_ASSERT(mNotificationRef);
962 Notification* notification = mNotificationRef->GetNotification();
963 if (notification) {
964 notification->mObserver = nullptr;
969 class ServiceWorkerNotificationObserver final : public nsIObserver {
970 public:
971 NS_DECL_ISUPPORTS
972 NS_DECL_NSIOBSERVER
974 ServiceWorkerNotificationObserver(
975 const nsAString& aScope, nsIPrincipal* aPrincipal, const nsAString& aID,
976 const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang,
977 const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon,
978 const nsAString& aData, const nsAString& aBehavior)
979 : mScope(aScope),
980 mID(aID),
981 mPrincipal(aPrincipal),
982 mTitle(aTitle),
983 mDir(aDir),
984 mLang(aLang),
985 mBody(aBody),
986 mTag(aTag),
987 mIcon(aIcon),
988 mData(aData),
989 mBehavior(aBehavior) {
990 AssertIsOnMainThread();
991 MOZ_ASSERT(aPrincipal);
994 private:
995 ~ServiceWorkerNotificationObserver() = default;
997 const nsString mScope;
998 const nsString mID;
999 nsCOMPtr<nsIPrincipal> mPrincipal;
1000 const nsString mTitle;
1001 const nsString mDir;
1002 const nsString mLang;
1003 const nsString mBody;
1004 const nsString mTag;
1005 const nsString mIcon;
1006 const nsString mData;
1007 const nsString mBehavior;
1010 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver)
1012 bool Notification::DispatchClickEvent() {
1013 AssertIsOnTargetThread();
1014 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1015 event->InitEvent(u"click"_ns, false, true);
1016 event->SetTrusted(true);
1017 WantsPopupControlCheck popupControlCheck(event);
1018 return DispatchEvent(*event, CallerType::System, IgnoreErrors());
1021 // Overrides dispatch and run handlers so we can directly dispatch from main
1022 // thread to child workers.
1023 class NotificationClickWorkerRunnable final
1024 : public NotificationWorkerRunnable {
1025 Notification* mNotification;
1026 // Optional window that gets focused if click event is not
1027 // preventDefault()ed.
1028 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
1030 public:
1031 NotificationClickWorkerRunnable(
1032 Notification* aNotification,
1033 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
1034 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
1035 mNotification(aNotification),
1036 mWindow(aWindow) {
1037 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow);
1040 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1041 bool doDefaultAction = mNotification->DispatchClickEvent();
1042 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction);
1043 if (doDefaultAction) {
1044 RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
1045 mWorkerPrivate->DispatchToMainThread(r.forget());
1050 NS_IMETHODIMP
1051 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1052 const char16_t* aData) {
1053 AssertIsOnMainThread();
1055 if (!strcmp("alertdisablecallback", aTopic)) {
1056 if (XRE_IsParentProcess()) {
1057 return Notification::RemovePermission(mPrincipal);
1059 // Permissions can't be removed from the content process. Send a message
1060 // to the parent; `ContentParent::RecvDisableNotifications` will call
1061 // `RemovePermission`.
1062 ContentChild::GetSingleton()->SendDisableNotifications(
1063 IPC::Principal(mPrincipal));
1064 return NS_OK;
1065 } else if (!strcmp("alertsettingscallback", aTopic)) {
1066 if (XRE_IsParentProcess()) {
1067 return Notification::OpenSettings(mPrincipal);
1069 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1070 // parent process.
1071 ContentChild::GetSingleton()->SendOpenNotificationSettings(
1072 IPC::Principal(mPrincipal));
1073 return NS_OK;
1074 } else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) {
1075 Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
1078 return mObserver->Observe(aSubject, aTopic, aData);
1081 nsresult NotificationObserver::AdjustPushQuota(const char* aTopic) {
1082 nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
1083 do_GetService("@mozilla.org/push/Service;1");
1084 if (!pushQuotaManager) {
1085 return NS_ERROR_FAILURE;
1088 nsAutoCString origin;
1089 nsresult rv = mPrincipal->GetOrigin(origin);
1090 if (NS_FAILED(rv)) {
1091 return rv;
1094 if (!strcmp("alertshow", aTopic)) {
1095 return pushQuotaManager->NotificationForOriginShown(origin.get());
1097 return pushQuotaManager->NotificationForOriginClosed(origin.get());
1100 NS_IMETHODIMP
1101 MainThreadNotificationObserver::Observe(nsISupports* aSubject,
1102 const char* aTopic,
1103 const char16_t* aData) {
1104 AssertIsOnMainThread();
1105 MOZ_ASSERT(mNotificationRef);
1106 Notification* notification = mNotificationRef->GetNotification();
1107 MOZ_ASSERT(notification);
1108 if (!strcmp("alertclickcallback", aTopic)) {
1109 nsCOMPtr<nsPIDOMWindowInner> window = notification->GetOwner();
1110 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1111 // Window has been closed, this observer is not valid anymore
1112 return NS_ERROR_FAILURE;
1115 bool doDefaultAction = notification->DispatchClickEvent();
1116 if (doDefaultAction) {
1117 nsFocusManager::FocusWindow(window->GetOuterWindow(), CallerType::System);
1119 } else if (!strcmp("alertfinished", aTopic)) {
1120 notification->UnpersistNotification();
1121 notification->mIsClosed = true;
1122 notification->DispatchTrustedEvent(u"close"_ns);
1123 } else if (!strcmp("alertshow", aTopic)) {
1124 notification->DispatchTrustedEvent(u"show"_ns);
1126 return NS_OK;
1129 NS_IMETHODIMP
1130 WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1131 const char16_t* aData) {
1132 AssertIsOnMainThread();
1133 MOZ_ASSERT(mNotificationRef);
1134 // For an explanation of why it is OK to pass this rawptr to the event
1135 // runnables, see the Notification class comment.
1136 Notification* notification = mNotificationRef->GetNotification();
1137 // We can't assert notification here since the feature could've unset it.
1138 if (NS_WARN_IF(!notification)) {
1139 return NS_ERROR_FAILURE;
1142 MOZ_ASSERT(notification->mWorkerPrivate);
1144 RefPtr<WorkerRunnable> r;
1145 if (!strcmp("alertclickcallback", aTopic)) {
1146 nsPIDOMWindowInner* window = nullptr;
1147 if (!notification->mWorkerPrivate->IsServiceWorker()) {
1148 WorkerPrivate* top = notification->mWorkerPrivate;
1149 while (top->GetParent()) {
1150 top = top->GetParent();
1153 window = top->GetWindow();
1154 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1155 // Window has been closed, this observer is not valid anymore
1156 return NS_ERROR_FAILURE;
1160 // Instead of bothering with adding features and other worker lifecycle
1161 // management, we simply hold strongrefs to the window and document.
1162 nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
1163 new nsMainThreadPtrHolder<nsPIDOMWindowInner>(
1164 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window));
1166 r = new NotificationClickWorkerRunnable(notification, windowHandle);
1167 } else if (!strcmp("alertfinished", aTopic)) {
1168 notification->UnpersistNotification();
1169 notification->mIsClosed = true;
1170 r = new NotificationEventWorkerRunnable(notification, u"close"_ns);
1171 } else if (!strcmp("alertshow", aTopic)) {
1172 r = new NotificationEventWorkerRunnable(notification, u"show"_ns);
1175 MOZ_ASSERT(r);
1176 if (!r->Dispatch()) {
1177 NS_WARNING("Could not dispatch event to worker notification");
1179 return NS_OK;
1182 NS_IMETHODIMP
1183 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
1184 const char* aTopic,
1185 const char16_t* aData) {
1186 AssertIsOnMainThread();
1188 nsAutoCString originSuffix;
1189 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
1190 if (NS_WARN_IF(NS_FAILED(rv))) {
1191 return rv;
1194 if (!strcmp("alertclickcallback", aTopic)) {
1195 if (XRE_IsParentProcess() || !ServiceWorkerParentInterceptEnabled()) {
1196 nsCOMPtr<nsIServiceWorkerManager> swm =
1197 mozilla::services::GetServiceWorkerManager();
1198 if (NS_WARN_IF(!swm)) {
1199 return NS_ERROR_FAILURE;
1202 rv = swm->SendNotificationClickEvent(
1203 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1204 mBody, mTag, mIcon, mData, mBehavior);
1205 Unused << NS_WARN_IF(NS_FAILED(rv));
1206 } else {
1207 auto* cc = ContentChild::GetSingleton();
1208 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1209 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1210 mData, mBehavior);
1211 Unused << cc->SendNotificationEvent(u"click"_ns, data);
1213 return NS_OK;
1216 if (!strcmp("alertfinished", aTopic)) {
1217 nsString origin;
1218 nsresult rv = Notification::GetOrigin(mPrincipal, origin);
1219 if (NS_WARN_IF(NS_FAILED(rv))) {
1220 return rv;
1223 // Remove closed or dismissed persistent notifications.
1224 nsCOMPtr<nsINotificationStorage> notificationStorage =
1225 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1226 if (notificationStorage) {
1227 notificationStorage->Delete(origin, mID);
1230 if (XRE_IsParentProcess() || !ServiceWorkerParentInterceptEnabled()) {
1231 nsCOMPtr<nsIServiceWorkerManager> swm =
1232 mozilla::services::GetServiceWorkerManager();
1233 if (NS_WARN_IF(!swm)) {
1234 return NS_ERROR_FAILURE;
1237 rv = swm->SendNotificationCloseEvent(
1238 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1239 mBody, mTag, mIcon, mData, mBehavior);
1240 Unused << NS_WARN_IF(NS_FAILED(rv));
1241 } else {
1242 auto* cc = ContentChild::GetSingleton();
1243 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1244 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1245 mData, mBehavior);
1246 Unused << cc->SendNotificationEvent(u"close"_ns, data);
1248 return NS_OK;
1251 return NS_OK;
1254 bool Notification::IsInPrivateBrowsing() {
1255 AssertIsOnMainThread();
1257 Document* doc = nullptr;
1259 if (mWorkerPrivate) {
1260 doc = mWorkerPrivate->GetDocument();
1261 } else if (GetOwner()) {
1262 doc = GetOwner()->GetExtantDoc();
1265 if (doc) {
1266 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
1267 return loadContext && loadContext->UsePrivateBrowsing();
1270 if (mWorkerPrivate) {
1271 // Not all workers may have a document, but with Bug 1107516 fixed, they
1272 // should all have a loadcontext.
1273 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1274 nsCOMPtr<nsILoadContext> loadContext;
1275 NS_QueryNotificationCallbacks(nullptr, loadGroup,
1276 NS_GET_IID(nsILoadContext),
1277 getter_AddRefs(loadContext));
1278 return loadContext && loadContext->UsePrivateBrowsing();
1281 // XXXnsm Should this default to true?
1282 return false;
1285 namespace {
1286 struct StringWriteFunc : public JSONWriteFunc {
1287 nsAString& mBuffer; // This struct must not outlive this buffer
1288 explicit StringWriteFunc(nsAString& buffer) : mBuffer(buffer) {}
1290 void Write(const Span<const char>& aStr) override {
1291 mBuffer.Append(NS_ConvertUTF8toUTF16(aStr.data(), aStr.size()));
1294 } // namespace
1296 void Notification::ShowInternal() {
1297 AssertIsOnMainThread();
1298 MOZ_ASSERT(mTempRef,
1299 "Notification should take ownership of itself before"
1300 "calling ShowInternal!");
1301 // A notification can only have one observer and one call to ShowInternal.
1302 MOZ_ASSERT(!mObserver);
1304 // Transfer ownership to local scope so we can either release it at the end
1305 // of this function or transfer it to the observer.
1306 UniquePtr<NotificationRef> ownership;
1307 std::swap(ownership, mTempRef);
1308 MOZ_ASSERT(ownership->GetNotification() == this);
1310 nsresult rv = PersistNotification();
1311 if (NS_FAILED(rv)) {
1312 NS_WARNING("Could not persist Notification");
1315 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1317 ErrorResult result;
1318 NotificationPermission permission = NotificationPermission::Denied;
1319 if (mWorkerPrivate) {
1320 permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
1321 } else {
1322 permission = GetPermissionInternal(GetOwner(), result);
1324 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1325 MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
1326 result.SuppressException();
1327 if (permission != NotificationPermission::Granted || !alertService) {
1328 if (mWorkerPrivate) {
1329 RefPtr<NotificationEventWorkerRunnable> r =
1330 new NotificationEventWorkerRunnable(this, u"error"_ns);
1331 if (!r->Dispatch()) {
1332 NS_WARNING("Could not dispatch event to worker notification");
1334 } else {
1335 DispatchTrustedEvent(u"error"_ns);
1337 return;
1340 nsAutoString iconUrl;
1341 nsAutoString soundUrl;
1342 ResolveIconAndSoundURL(iconUrl, soundUrl);
1344 bool isPersistent = false;
1345 nsCOMPtr<nsIObserver> observer;
1346 if (mScope.IsEmpty()) {
1347 // Ownership passed to observer.
1348 if (mWorkerPrivate) {
1349 // Scope better be set on ServiceWorker initiated requests.
1350 MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
1351 // Keep a pointer so that the feature can tell the observer not to release
1352 // the notification.
1353 mObserver = new WorkerNotificationObserver(std::move(ownership));
1354 observer = mObserver;
1355 } else {
1356 observer = new MainThreadNotificationObserver(std::move(ownership));
1358 } else {
1359 isPersistent = true;
1360 // This observer does not care about the Notification. It will be released
1361 // at the end of this function.
1363 // The observer is wholly owned by the NotificationObserver passed to the
1364 // alert service.
1365 nsAutoString behavior;
1366 if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
1367 behavior.Truncate();
1369 observer = new ServiceWorkerNotificationObserver(
1370 mScope, GetPrincipal(), mID, mTitle, DirectionToString(mDir), mLang,
1371 mBody, mTag, iconUrl, mDataAsBase64, behavior);
1373 MOZ_ASSERT(observer);
1374 nsCOMPtr<nsIObserver> alertObserver =
1375 new NotificationObserver(observer, GetPrincipal(), IsInPrivateBrowsing());
1377 // In the case of IPC, the parent process uses the cookie to map to
1378 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1379 nsString uniqueCookie = u"notification:"_ns;
1380 uniqueCookie.AppendInt(sCount++);
1381 bool inPrivateBrowsing = IsInPrivateBrowsing();
1383 bool requireInteraction = mRequireInteraction;
1384 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1385 requireInteraction = false;
1388 nsAutoString alertName;
1389 GetAlertName(alertName);
1390 nsCOMPtr<nsIAlertNotification> alert =
1391 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
1392 NS_ENSURE_TRUE_VOID(alert);
1393 nsIPrincipal* principal = GetPrincipal();
1394 rv = alert->Init(alertName, iconUrl, mTitle, mBody, true, uniqueCookie,
1395 DirectionToString(mDir), mLang, mDataAsBase64,
1396 GetPrincipal(), inPrivateBrowsing, requireInteraction);
1397 NS_ENSURE_SUCCESS_VOID(rv);
1399 if (isPersistent) {
1400 nsAutoString persistentData;
1402 JSONWriter w(MakeUnique<StringWriteFunc>(persistentData));
1403 w.Start();
1405 nsAutoString origin;
1406 Notification::GetOrigin(principal, origin);
1407 w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin));
1409 w.StringProperty("id", NS_ConvertUTF16toUTF8(mID));
1411 nsAutoCString originSuffix;
1412 principal->GetOriginSuffix(originSuffix);
1413 w.StringProperty("originSuffix", originSuffix);
1415 w.End();
1417 alertService->ShowPersistentNotification(persistentData, alert,
1418 alertObserver);
1419 } else {
1420 alertService->ShowAlert(alert, alertObserver);
1424 /* static */
1425 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
1426 JSObject* /* unused */) {
1427 // requestPermission() is not allowed on workers. The calling page should ask
1428 // for permission on the worker's behalf. This is to prevent 'which window
1429 // should show the browser pop-up'. See discussion:
1430 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1431 return NS_IsMainThread();
1434 // static
1435 already_AddRefed<Promise> Notification::RequestPermission(
1436 const GlobalObject& aGlobal,
1437 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
1438 ErrorResult& aRv) {
1439 AssertIsOnMainThread();
1441 // Get principal from global to make permission request for notifications.
1442 nsCOMPtr<nsPIDOMWindowInner> window =
1443 do_QueryInterface(aGlobal.GetAsSupports());
1444 nsCOMPtr<nsIScriptObjectPrincipal> sop =
1445 do_QueryInterface(aGlobal.GetAsSupports());
1446 if (!sop || !window) {
1447 aRv.Throw(NS_ERROR_UNEXPECTED);
1448 return nullptr;
1450 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1452 RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
1453 if (aRv.Failed()) {
1454 return nullptr;
1456 NotificationPermissionCallback* permissionCallback = nullptr;
1457 if (aCallback.WasPassed()) {
1458 permissionCallback = &aCallback.Value();
1460 nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
1461 principal, window, promise, permissionCallback);
1463 window->AsGlobal()->Dispatch(TaskCategory::Other, request.forget());
1465 return promise.forget();
1468 // static
1469 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
1470 ErrorResult& aRv) {
1471 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1472 return GetPermission(global, aRv);
1475 // static
1476 NotificationPermission Notification::GetPermission(nsIGlobalObject* aGlobal,
1477 ErrorResult& aRv) {
1478 if (NS_IsMainThread()) {
1479 return GetPermissionInternal(aGlobal, aRv);
1480 } else {
1481 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1482 MOZ_ASSERT(worker);
1483 RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(worker);
1484 r->Dispatch(Canceling, aRv);
1485 if (aRv.Failed()) {
1486 return NotificationPermission::Denied;
1489 return r->GetPermission();
1493 /* static */
1494 NotificationPermission Notification::GetPermissionInternal(nsISupports* aGlobal,
1495 ErrorResult& aRv) {
1496 // Get principal from global to check permission for notifications.
1497 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
1498 if (!sop) {
1499 aRv.Throw(NS_ERROR_UNEXPECTED);
1500 return NotificationPermission::Denied;
1503 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1504 return GetPermissionInternal(principal, aRv);
1507 /* static */
1508 NotificationPermission Notification::GetPermissionInternal(
1509 nsIPrincipal* aPrincipal, ErrorResult& aRv) {
1510 AssertIsOnMainThread();
1511 MOZ_ASSERT(aPrincipal);
1513 if (aPrincipal->IsSystemPrincipal()) {
1514 return NotificationPermission::Granted;
1515 } else {
1516 // Allow files to show notifications by default.
1517 if (aPrincipal->SchemeIs("file")) {
1518 return NotificationPermission::Granted;
1522 // We also allow notifications is they are pref'ed on.
1523 if (Preferences::GetBool("notification.prompt.testing", false)) {
1524 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1525 return NotificationPermission::Granted;
1526 } else {
1527 return NotificationPermission::Denied;
1531 return TestPermission(aPrincipal);
1534 /* static */
1535 NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) {
1536 AssertIsOnMainThread();
1538 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
1540 nsCOMPtr<nsIPermissionManager> permissionManager =
1541 services::GetPermissionManager();
1542 if (!permissionManager) {
1543 return NotificationPermission::Default;
1546 permissionManager->TestExactPermissionFromPrincipal(
1547 aPrincipal, "desktop-notification"_ns, &permission);
1549 // Convert the result to one of the enum types.
1550 switch (permission) {
1551 case nsIPermissionManager::ALLOW_ACTION:
1552 return NotificationPermission::Granted;
1553 case nsIPermissionManager::DENY_ACTION:
1554 return NotificationPermission::Denied;
1555 default:
1556 return NotificationPermission::Default;
1560 nsresult Notification::ResolveIconAndSoundURL(nsString& iconUrl,
1561 nsString& soundUrl) {
1562 AssertIsOnMainThread();
1563 nsresult rv = NS_OK;
1565 nsIURI* baseUri = nullptr;
1567 // XXXnsm If I understand correctly, the character encoding for resolving
1568 // URIs in new specs is dictated by the URL spec, which states that unless
1569 // the URL parser is passed an override encoding, the charset to be used is
1570 // UTF-8. The new Notification icon/sound specification just says to use the
1571 // Fetch API, where the Request constructor defers to URL parsing specifying
1572 // the API base URL and no override encoding. So we've to use UTF-8 on
1573 // workers, but for backwards compat keeping it document charset on main
1574 // thread.
1575 auto encoding = UTF_8_ENCODING;
1577 if (mWorkerPrivate) {
1578 baseUri = mWorkerPrivate->GetBaseURI();
1579 } else {
1580 Document* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1581 if (doc) {
1582 baseUri = doc->GetBaseURI();
1583 encoding = doc->GetDocumentCharacterSet();
1584 } else {
1585 NS_WARNING("No document found for main thread notification!");
1586 return NS_ERROR_FAILURE;
1590 if (baseUri) {
1591 if (mIconUrl.Length() > 0) {
1592 nsCOMPtr<nsIURI> srcUri;
1593 rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, encoding, baseUri);
1594 if (NS_SUCCEEDED(rv)) {
1595 nsAutoCString src;
1596 srcUri->GetSpec(src);
1597 CopyUTF8toUTF16(src, iconUrl);
1600 if (mBehavior.mSoundFile.Length() > 0) {
1601 nsCOMPtr<nsIURI> srcUri;
1602 rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding,
1603 baseUri);
1604 if (NS_SUCCEEDED(rv)) {
1605 nsAutoCString src;
1606 srcUri->GetSpec(src);
1607 CopyUTF8toUTF16(src, soundUrl);
1612 return rv;
1615 already_AddRefed<Promise> Notification::Get(
1616 nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter,
1617 const nsAString& aScope, ErrorResult& aRv) {
1618 AssertIsOnMainThread();
1619 MOZ_ASSERT(aWindow);
1621 nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
1622 if (!doc) {
1623 aRv.Throw(NS_ERROR_UNEXPECTED);
1624 return nullptr;
1627 nsString origin;
1628 aRv = GetOrigin(doc->NodePrincipal(), origin);
1629 if (aRv.Failed()) {
1630 return nullptr;
1633 RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
1634 if (aRv.Failed()) {
1635 return nullptr;
1638 nsCOMPtr<nsINotificationStorageCallback> callback =
1639 new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
1641 RefPtr<NotificationGetRunnable> r =
1642 new NotificationGetRunnable(origin, aFilter.mTag, callback);
1644 aRv = aWindow->AsGlobal()->Dispatch(TaskCategory::Other, r.forget());
1645 if (NS_WARN_IF(aRv.Failed())) {
1646 return nullptr;
1649 return promise.forget();
1652 already_AddRefed<Promise> Notification::Get(
1653 const GlobalObject& aGlobal, const GetNotificationOptions& aFilter,
1654 ErrorResult& aRv) {
1655 AssertIsOnMainThread();
1656 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1657 MOZ_ASSERT(global);
1658 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
1660 return Get(window, aFilter, u""_ns, aRv);
1663 class WorkerGetResultRunnable final : public NotificationWorkerRunnable {
1664 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1665 const nsTArray<NotificationStrings> mStrings;
1667 public:
1668 WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
1669 PromiseWorkerProxy* aPromiseProxy,
1670 nsTArray<NotificationStrings>&& aStrings)
1671 : NotificationWorkerRunnable(aWorkerPrivate),
1672 mPromiseProxy(aPromiseProxy),
1673 mStrings(std::move(aStrings)) {}
1675 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1676 RefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise();
1678 ErrorResult result;
1679 AutoTArray<RefPtr<Notification>, 5> notifications;
1680 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
1681 RefPtr<Notification> n = Notification::ConstructFromFields(
1682 aWorkerPrivate->GlobalScope(), mStrings[i].mID, mStrings[i].mTitle,
1683 mStrings[i].mDir, mStrings[i].mLang, mStrings[i].mBody,
1684 mStrings[i].mTag, mStrings[i].mIcon, mStrings[i].mData,
1685 /* mStrings[i].mBehavior, not
1686 * supported */
1687 mStrings[i].mServiceWorkerRegistrationScope, result);
1689 n->SetStoredState(true);
1690 Unused << NS_WARN_IF(result.Failed());
1691 if (!result.Failed()) {
1692 notifications.AppendElement(n.forget());
1696 workerPromise->MaybeResolve(notifications);
1697 mPromiseProxy->CleanUp();
1701 class WorkerGetCallback final : public ScopeCheckingGetCallback {
1702 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1704 public:
1705 NS_DECL_ISUPPORTS
1707 WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
1708 : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) {
1709 AssertIsOnMainThread();
1710 MOZ_ASSERT(aProxy);
1713 NS_IMETHOD Done() final {
1714 AssertIsOnMainThread();
1715 MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
1717 RefPtr<PromiseWorkerProxy> proxy = std::move(mPromiseProxy);
1718 MutexAutoLock lock(proxy->Lock());
1719 if (proxy->CleanedUp()) {
1720 return NS_OK;
1723 RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
1724 proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
1726 r->Dispatch();
1727 return NS_OK;
1730 private:
1731 ~WorkerGetCallback() = default;
1734 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
1736 class WorkerGetRunnable final : public Runnable {
1737 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1738 const nsString mTag;
1739 const nsString mScope;
1741 public:
1742 WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag,
1743 const nsAString& aScope)
1744 : Runnable("WorkerGetRunnable"),
1745 mPromiseProxy(aProxy),
1746 mTag(aTag),
1747 mScope(aScope) {
1748 MOZ_ASSERT(mPromiseProxy);
1751 NS_IMETHOD
1752 Run() override {
1753 AssertIsOnMainThread();
1754 nsCOMPtr<nsINotificationStorageCallback> callback =
1755 new WorkerGetCallback(mPromiseProxy, mScope);
1757 nsresult rv;
1758 nsCOMPtr<nsINotificationStorage> notificationStorage =
1759 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
1760 if (NS_WARN_IF(NS_FAILED(rv))) {
1761 callback->Done();
1762 return rv;
1765 MutexAutoLock lock(mPromiseProxy->Lock());
1766 if (mPromiseProxy->CleanedUp()) {
1767 return NS_OK;
1770 nsString origin;
1771 rv = Notification::GetOrigin(
1772 mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
1773 if (NS_WARN_IF(NS_FAILED(rv))) {
1774 callback->Done();
1775 return rv;
1778 rv = notificationStorage->Get(origin, mTag, callback);
1779 if (NS_WARN_IF(NS_FAILED(rv))) {
1780 callback->Done();
1781 return rv;
1784 return NS_OK;
1787 private:
1788 ~WorkerGetRunnable() = default;
1791 // static
1792 already_AddRefed<Promise> Notification::WorkerGet(
1793 WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
1794 const nsAString& aScope, ErrorResult& aRv) {
1795 MOZ_ASSERT(aWorkerPrivate);
1796 aWorkerPrivate->AssertIsOnWorkerThread();
1797 RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
1798 if (NS_WARN_IF(aRv.Failed())) {
1799 return nullptr;
1802 RefPtr<PromiseWorkerProxy> proxy =
1803 PromiseWorkerProxy::Create(aWorkerPrivate, p);
1804 if (!proxy) {
1805 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
1806 return nullptr;
1809 RefPtr<WorkerGetRunnable> r =
1810 new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
1811 // Since this is called from script via
1812 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1813 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
1814 return p.forget();
1817 JSObject* Notification::WrapObject(JSContext* aCx,
1818 JS::Handle<JSObject*> aGivenProto) {
1819 return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
1822 void Notification::Close() {
1823 AssertIsOnTargetThread();
1824 auto ref = MakeUnique<NotificationRef>(this);
1825 if (!ref->Initialized()) {
1826 return;
1829 nsCOMPtr<nsIRunnable> closeNotificationTask = new NotificationTask(
1830 "Notification::Close", std::move(ref), NotificationTask::eClose);
1831 nsresult rv = DispatchToMainThread(closeNotificationTask.forget());
1833 if (NS_FAILED(rv)) {
1834 DispatchTrustedEvent(u"error"_ns);
1835 // If dispatch fails, NotificationTask will release the ref when it goes
1836 // out of scope at the end of this function.
1840 void Notification::CloseInternal() {
1841 AssertIsOnMainThread();
1842 // Transfer ownership (if any) to local scope so we can release it at the end
1843 // of this function. This is relevant when the call is from
1844 // NotificationTask::Run().
1845 UniquePtr<NotificationRef> ownership;
1846 std::swap(ownership, mTempRef);
1848 SetAlertName();
1849 UnpersistNotification();
1850 if (!mIsClosed) {
1851 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1852 if (alertService) {
1853 nsAutoString alertName;
1854 GetAlertName(alertName);
1855 alertService->CloseAlert(alertName);
1860 nsresult Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) {
1861 if (!aPrincipal) {
1862 return NS_ERROR_FAILURE;
1865 nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, aOrigin);
1866 NS_ENSURE_SUCCESS(rv, rv);
1868 return NS_OK;
1871 bool Notification::RequireInteraction() const { return mRequireInteraction; }
1873 void Notification::GetData(JSContext* aCx,
1874 JS::MutableHandle<JS::Value> aRetval) {
1875 if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
1876 nsresult rv;
1877 RefPtr<nsStructuredCloneContainer> container =
1878 new nsStructuredCloneContainer();
1879 rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
1880 if (NS_WARN_IF(NS_FAILED(rv))) {
1881 aRetval.setNull();
1882 return;
1885 JS::Rooted<JS::Value> data(aCx);
1886 rv = container->DeserializeToJsval(aCx, &data);
1887 if (NS_WARN_IF(NS_FAILED(rv))) {
1888 aRetval.setNull();
1889 return;
1892 if (data.isGCThing()) {
1893 mozilla::HoldJSObjects(this);
1895 mData = data;
1897 if (mData.isNull()) {
1898 aRetval.setNull();
1899 return;
1902 aRetval.set(mData);
1905 void Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
1906 ErrorResult& aRv) {
1907 if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
1908 return;
1910 RefPtr<nsStructuredCloneContainer> dataObjectContainer =
1911 new nsStructuredCloneContainer();
1912 aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
1913 if (NS_WARN_IF(aRv.Failed())) {
1914 return;
1917 aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64);
1918 if (NS_WARN_IF(aRv.Failed())) {
1919 return;
1923 void Notification::InitFromBase64(const nsAString& aData, ErrorResult& aRv) {
1924 if (!mDataAsBase64.IsEmpty() || aData.IsEmpty()) {
1925 return;
1928 // To and fro to ensure it is valid base64.
1929 RefPtr<nsStructuredCloneContainer> container =
1930 new nsStructuredCloneContainer();
1931 aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION);
1932 if (NS_WARN_IF(aRv.Failed())) {
1933 return;
1936 aRv = container->GetDataAsBase64(mDataAsBase64);
1937 if (NS_WARN_IF(aRv.Failed())) {
1938 return;
1942 bool Notification::AddRefObject() {
1943 AssertIsOnTargetThread();
1944 MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerRef, mTaskCount == 0);
1945 MOZ_ASSERT_IF(mWorkerPrivate && mWorkerRef, mTaskCount > 0);
1946 if (mWorkerPrivate && !mWorkerRef) {
1947 if (!CreateWorkerRef()) {
1948 return false;
1951 AddRef();
1952 ++mTaskCount;
1953 return true;
1956 void Notification::ReleaseObject() {
1957 AssertIsOnTargetThread();
1958 MOZ_ASSERT(mTaskCount > 0);
1959 MOZ_ASSERT_IF(mWorkerPrivate, mWorkerRef);
1961 --mTaskCount;
1962 if (mWorkerPrivate && mTaskCount == 0) {
1963 MOZ_ASSERT(mWorkerRef);
1964 mWorkerRef = nullptr;
1966 Release();
1970 * Called from the worker, runs on main thread, blocks worker.
1972 * We can freely access mNotification here because the feature supplied it and
1973 * the Notification owns the feature.
1975 class CloseNotificationRunnable final : public WorkerMainThreadRunnable {
1976 Notification* mNotification;
1977 bool mHadObserver;
1979 public:
1980 explicit CloseNotificationRunnable(Notification* aNotification)
1981 : WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
1982 "Notification :: Close Notification"_ns),
1983 mNotification(aNotification),
1984 mHadObserver(false) {}
1986 bool MainThreadRun() override {
1987 if (mNotification->mObserver) {
1988 // The Notify() take's responsibility of releasing the Notification.
1989 mNotification->mObserver->ForgetNotification();
1990 mNotification->mObserver = nullptr;
1991 mHadObserver = true;
1993 mNotification->CloseInternal();
1994 return true;
1997 bool HadObserver() { return mHadObserver; }
2000 bool Notification::CreateWorkerRef() {
2001 MOZ_ASSERT(mWorkerPrivate);
2002 mWorkerPrivate->AssertIsOnWorkerThread();
2003 MOZ_ASSERT(!mWorkerRef);
2005 RefPtr<Notification> self = this;
2006 mWorkerRef =
2007 StrongWorkerRef::Create(mWorkerPrivate, "Notification", [self]() {
2008 // CloseNotificationRunnable blocks the worker by pushing a sync event
2009 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2010 // the worker can still continue running. One of these is
2011 // ReleaseNotificationControlRunnable that releases the notification,
2012 // invalidating the notification and this feature. We hold this
2013 // reference to keep the notification valid until we are done with it.
2015 // An example of when the control runnable could get dispatched to the
2016 // worker is if a Notification is created and the worker is immediately
2017 // closed, but there is no permission to show it so that the main thread
2018 // immediately drops the NotificationRef. In this case, this function
2019 // blocks on the main thread, but the main thread dispatches the control
2020 // runnable, invalidating mNotification.
2022 // Dispatched to main thread, blocks on closing the Notification.
2023 RefPtr<CloseNotificationRunnable> r =
2024 new CloseNotificationRunnable(self);
2025 ErrorResult rv;
2026 r->Dispatch(Killing, rv);
2027 // XXXbz I'm told throwing and returning false from here is pointless
2028 // (and also that doing sync stuff from here is really weird), so I
2029 // guess we just suppress the exception on rv, if any.
2030 rv.SuppressException();
2032 // Only call ReleaseObject() to match the observer's NotificationRef
2033 // ownership (since CloseNotificationRunnable asked the observer to drop
2034 // the reference to the notification).
2035 if (r->HadObserver()) {
2036 self->ReleaseObject();
2039 // From this point we cannot touch properties of this feature because
2040 // ReleaseObject() may have led to the notification going away and the
2041 // notification owns this feature!
2044 if (NS_WARN_IF(!mWorkerRef)) {
2045 return false;
2048 return true;
2052 * Checks:
2053 * 1) Is aWorker allowed to show a notification for scope?
2054 * 2) Is aWorker an active worker?
2056 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2058 class CheckLoadRunnable final : public WorkerMainThreadRunnable {
2059 nsresult mRv;
2060 nsCString mScope;
2061 ServiceWorkerRegistrationDescriptor mDescriptor;
2063 public:
2064 explicit CheckLoadRunnable(
2065 WorkerPrivate* aWorker, const nsACString& aScope,
2066 const ServiceWorkerRegistrationDescriptor& aDescriptor)
2067 : WorkerMainThreadRunnable(aWorker, "Notification :: Check Load"_ns),
2068 mRv(NS_ERROR_DOM_SECURITY_ERR),
2069 mScope(aScope),
2070 mDescriptor(aDescriptor) {}
2072 bool MainThreadRun() override {
2073 nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
2074 mRv = CheckScope(principal, mScope, mWorkerPrivate->WindowID());
2076 if (NS_FAILED(mRv)) {
2077 return true;
2080 auto activeWorker = mDescriptor.GetActive();
2082 if (!activeWorker ||
2083 activeWorker.ref().Id() != mWorkerPrivate->ServiceWorkerID()) {
2084 mRv = NS_ERROR_NOT_AVAILABLE;
2087 return true;
2090 nsresult Result() { return mRv; }
2093 /* static */
2094 already_AddRefed<Promise> Notification::ShowPersistentNotification(
2095 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
2096 const nsAString& aTitle, const NotificationOptions& aOptions,
2097 const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
2098 MOZ_ASSERT(aGlobal);
2100 // Validate scope.
2101 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2102 // thread. On calls from content, we can be sure the scope is valid since
2103 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2104 // debug only? The problem is that there would be different semantics in
2105 // debug and non-debug builds in such a case.
2106 if (NS_IsMainThread()) {
2107 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
2108 if (NS_WARN_IF(!sop)) {
2109 aRv.Throw(NS_ERROR_UNEXPECTED);
2110 return nullptr;
2113 nsIPrincipal* principal = sop->GetPrincipal();
2114 if (NS_WARN_IF(!principal)) {
2115 aRv.Throw(NS_ERROR_UNEXPECTED);
2116 return nullptr;
2119 uint64_t windowID = 0;
2120 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
2121 if (win) {
2122 windowID = win->WindowID();
2125 aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope), windowID);
2126 if (NS_WARN_IF(aRv.Failed())) {
2127 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2128 return nullptr;
2130 } else {
2131 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
2132 MOZ_ASSERT(worker);
2133 worker->AssertIsOnWorkerThread();
2135 RefPtr<CheckLoadRunnable> loadChecker = new CheckLoadRunnable(
2136 worker, NS_ConvertUTF16toUTF8(aScope), aDescriptor);
2137 loadChecker->Dispatch(Canceling, aRv);
2138 if (aRv.Failed()) {
2139 return nullptr;
2142 if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
2143 if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
2144 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
2145 } else {
2146 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2148 return nullptr;
2152 RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
2153 if (NS_WARN_IF(aRv.Failed())) {
2154 return nullptr;
2157 // We check permission here rather than pass the Promise to NotificationTask
2158 // which leads to uglier code.
2159 NotificationPermission permission = GetPermission(aGlobal, aRv);
2161 // "If permission for notification's origin is not "granted", reject promise
2162 // with a TypeError exception, and terminate these substeps."
2163 if (NS_WARN_IF(aRv.Failed()) ||
2164 permission == NotificationPermission::Denied) {
2165 p->MaybeRejectWithTypeError("Permission to show Notification denied.");
2166 return p.forget();
2169 // "Otherwise, resolve promise with undefined."
2170 // The Notification may still not be shown due to other errors, but the spec
2171 // is not concerned with those.
2172 p->MaybeResolveWithUndefined();
2174 RefPtr<Notification> notification =
2175 CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
2176 if (NS_WARN_IF(aRv.Failed())) {
2177 return nullptr;
2180 return p.forget();
2183 /* static */
2184 already_AddRefed<Notification> Notification::CreateAndShow(
2185 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
2186 const NotificationOptions& aOptions, const nsAString& aScope,
2187 ErrorResult& aRv) {
2188 MOZ_ASSERT(aGlobal);
2190 RefPtr<Notification> notification =
2191 CreateInternal(aGlobal, u""_ns, aTitle, aOptions);
2193 // Make a structured clone of the aOptions.mData object
2194 JS::Rooted<JS::Value> data(aCx, aOptions.mData);
2195 notification->InitFromJSVal(aCx, data, aRv);
2196 if (NS_WARN_IF(aRv.Failed())) {
2197 return nullptr;
2200 notification->SetScope(aScope);
2202 auto ref = MakeUnique<NotificationRef>(notification);
2203 if (NS_WARN_IF(!ref->Initialized())) {
2204 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2205 return nullptr;
2208 // Queue a task to show the notification.
2209 nsCOMPtr<nsIRunnable> showNotificationTask = new NotificationTask(
2210 "Notification::CreateAndShow", std::move(ref), NotificationTask::eShow);
2212 nsresult rv =
2213 notification->DispatchToMainThread(showNotificationTask.forget());
2215 if (NS_WARN_IF(NS_FAILED(rv))) {
2216 notification->DispatchTrustedEvent(u"error"_ns);
2219 return notification.forget();
2222 /* static */
2223 nsresult Notification::RemovePermission(nsIPrincipal* aPrincipal) {
2224 MOZ_ASSERT(XRE_IsParentProcess());
2225 nsCOMPtr<nsIPermissionManager> permissionManager =
2226 mozilla::services::GetPermissionManager();
2227 if (!permissionManager) {
2228 return NS_ERROR_FAILURE;
2230 permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"_ns);
2231 return NS_OK;
2234 /* static */
2235 nsresult Notification::OpenSettings(nsIPrincipal* aPrincipal) {
2236 MOZ_ASSERT(XRE_IsParentProcess());
2237 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2238 if (!obs) {
2239 return NS_ERROR_FAILURE;
2241 // Notify other observers so they can show settings UI.
2242 obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
2243 return NS_OK;
2246 NS_IMETHODIMP
2247 Notification::Observe(nsISupports* aSubject, const char* aTopic,
2248 const char16_t* aData) {
2249 AssertIsOnMainThread();
2251 if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
2252 !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
2253 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
2254 if (SameCOMIdentity(aSubject, window)) {
2255 nsCOMPtr<nsIObserverService> obs =
2256 mozilla::services::GetObserverService();
2257 if (obs) {
2258 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
2259 obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
2262 CloseInternal();
2266 return NS_OK;
2269 nsresult Notification::DispatchToMainThread(
2270 already_AddRefed<nsIRunnable>&& aRunnable) {
2271 if (mWorkerPrivate) {
2272 return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable));
2274 AssertIsOnMainThread();
2275 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
2276 if (nsIEventTarget* target = global->EventTargetFor(TaskCategory::Other)) {
2277 return target->Dispatch(std::move(aRunnable),
2278 nsIEventTarget::DISPATCH_NORMAL);
2281 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
2282 MOZ_ASSERT(mainTarget);
2283 return mainTarget->Dispatch(std::move(aRunnable),
2284 nsIEventTarget::DISPATCH_NORMAL);
2287 } // namespace mozilla::dom