Bug 1472338: part 2) Change `clipboard.readText()` to read from the clipboard asynchr...
[gecko.git] / dom / notification / Notification.cpp
blobbcb9660cf28c024521feb27162cc426d2badfbc0
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/StaticPrefs_dom.h"
20 #include "mozilla/Telemetry.h"
21 #include "mozilla/Unused.h"
22 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
23 #include "mozilla/dom/BindingUtils.h"
24 #include "mozilla/dom/ContentChild.h"
25 #include "mozilla/dom/Document.h"
26 #include "mozilla/dom/NotificationEvent.h"
27 #include "mozilla/dom/PermissionMessageUtils.h"
28 #include "mozilla/dom/Promise.h"
29 #include "mozilla/dom/PromiseWorkerProxy.h"
30 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
31 #include "mozilla/dom/ServiceWorkerManager.h"
32 #include "mozilla/dom/ServiceWorkerUtils.h"
33 #include "mozilla/dom/WorkerRunnable.h"
34 #include "mozilla/dom/WorkerScope.h"
35 #include "Navigator.h"
36 #include "nsAlertsUtils.h"
37 #include "nsCRTGlue.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsContentPermissionHelper.h"
40 #include "nsContentUtils.h"
41 #include "nsDOMJSUtils.h"
42 #include "nsFocusManager.h"
43 #include "nsGlobalWindow.h"
44 #include "nsIAlertsService.h"
45 #include "nsIContentPermissionPrompt.h"
46 #include "nsILoadContext.h"
47 #include "nsINotificationStorage.h"
48 #include "nsIPermission.h"
49 #include "nsIPermissionManager.h"
50 #include "nsIPushService.h"
51 #include "nsIScriptError.h"
52 #include "nsIServiceWorkerManager.h"
53 #include "nsISimpleEnumerator.h"
54 #include "nsIUUIDGenerator.h"
55 #include "nsNetUtil.h"
56 #include "nsProxyRelease.h"
57 #include "nsServiceManagerUtils.h"
58 #include "nsStructuredCloneContainer.h"
59 #include "nsThreadUtils.h"
60 #include "nsXULAppAPI.h"
62 namespace mozilla::dom {
64 struct NotificationStrings {
65 const nsString mID;
66 const nsString mTitle;
67 const nsString mDir;
68 const nsString mLang;
69 const nsString mBody;
70 const nsString mTag;
71 const nsString mIcon;
72 const nsString mData;
73 const nsString mBehavior;
74 const nsString mServiceWorkerRegistrationScope;
77 class ScopeCheckingGetCallback : public nsINotificationStorageCallback {
78 const nsString mScope;
80 public:
81 explicit ScopeCheckingGetCallback(const nsAString& aScope) : mScope(aScope) {}
83 NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle,
84 const nsAString& aDir, const nsAString& aLang,
85 const nsAString& aBody, const nsAString& aTag,
86 const nsAString& aIcon, const nsAString& aData,
87 const nsAString& aBehavior,
88 const nsAString& aServiceWorkerRegistrationScope) final {
89 AssertIsOnMainThread();
90 MOZ_ASSERT(!aID.IsEmpty());
92 // Skip scopes that don't match when called from getNotifications().
93 if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) {
94 return NS_OK;
97 NotificationStrings strings = {
98 nsString(aID), nsString(aTitle),
99 nsString(aDir), nsString(aLang),
100 nsString(aBody), nsString(aTag),
101 nsString(aIcon), nsString(aData),
102 nsString(aBehavior), nsString(aServiceWorkerRegistrationScope),
105 mStrings.AppendElement(std::move(strings));
106 return NS_OK;
109 NS_IMETHOD Done() override = 0;
111 protected:
112 virtual ~ScopeCheckingGetCallback() = default;
114 nsTArray<NotificationStrings> mStrings;
117 class NotificationStorageCallback final : public ScopeCheckingGetCallback {
118 public:
119 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
120 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
122 NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
123 Promise* aPromise)
124 : ScopeCheckingGetCallback(aScope), mWindow(aWindow), mPromise(aPromise) {
125 AssertIsOnMainThread();
126 MOZ_ASSERT(aWindow);
127 MOZ_ASSERT(aPromise);
130 NS_IMETHOD Done() final {
131 ErrorResult result;
132 AutoTArray<RefPtr<Notification>, 5> notifications;
134 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
135 RefPtr<Notification> n = Notification::ConstructFromFields(
136 mWindow, mStrings[i].mID, mStrings[i].mTitle, mStrings[i].mDir,
137 mStrings[i].mLang, mStrings[i].mBody, mStrings[i].mTag,
138 mStrings[i].mIcon, mStrings[i].mData,
139 /* mStrings[i].mBehavior, not
140 * supported */
141 mStrings[i].mServiceWorkerRegistrationScope, result);
143 n->SetStoredState(true);
144 Unused << NS_WARN_IF(result.Failed());
145 if (!result.Failed()) {
146 notifications.AppendElement(n.forget());
150 mPromise->MaybeResolve(notifications);
151 return NS_OK;
154 private:
155 virtual ~NotificationStorageCallback() = default;
157 nsCOMPtr<nsIGlobalObject> mWindow;
158 RefPtr<Promise> mPromise;
159 const nsString mScope;
162 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
163 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
164 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
166 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
167 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
168 NS_INTERFACE_MAP_ENTRY(nsISupports)
169 NS_INTERFACE_MAP_END
171 class NotificationGetRunnable final : public Runnable {
172 const nsString mOrigin;
173 const nsString mTag;
174 nsCOMPtr<nsINotificationStorageCallback> mCallback;
176 public:
177 NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
178 nsINotificationStorageCallback* aCallback)
179 : Runnable("NotificationGetRunnable"),
180 mOrigin(aOrigin),
181 mTag(aTag),
182 mCallback(aCallback) {}
184 NS_IMETHOD
185 Run() override {
186 nsresult rv;
187 nsCOMPtr<nsINotificationStorage> notificationStorage =
188 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
189 if (NS_WARN_IF(NS_FAILED(rv))) {
190 return rv;
193 rv = notificationStorage->Get(mOrigin, mTag, mCallback);
194 // XXXnsm Is it guaranteed mCallback will be called in case of failure?
195 Unused << NS_WARN_IF(NS_FAILED(rv));
196 return rv;
200 class NotificationPermissionRequest : public ContentPermissionRequestBase,
201 public nsIRunnable,
202 public nsINamed {
203 public:
204 NS_DECL_NSIRUNNABLE
205 NS_DECL_ISUPPORTS_INHERITED
206 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
207 ContentPermissionRequestBase)
209 // nsIContentPermissionRequest
210 NS_IMETHOD Cancel(void) override;
211 NS_IMETHOD Allow(JS::HandleValue choices) override;
213 NotificationPermissionRequest(nsIPrincipal* aPrincipal,
214 nsPIDOMWindowInner* aWindow, Promise* aPromise,
215 NotificationPermissionCallback* aCallback)
216 : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
217 "desktop-notification"_ns),
218 mPermission(NotificationPermission::Default),
219 mPromise(aPromise),
220 mCallback(aCallback) {
221 MOZ_ASSERT(aPromise);
224 NS_IMETHOD GetName(nsACString& aName) override {
225 aName.AssignLiteral("NotificationPermissionRequest");
226 return NS_OK;
229 protected:
230 ~NotificationPermissionRequest() = default;
232 MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
233 nsresult DispatchResolvePromise();
234 NotificationPermission mPermission;
235 RefPtr<Promise> mPromise;
236 RefPtr<NotificationPermissionCallback> mCallback;
239 namespace {
240 class ReleaseNotificationControlRunnable final
241 : public MainThreadWorkerControlRunnable {
242 Notification* mNotification;
244 public:
245 explicit ReleaseNotificationControlRunnable(Notification* aNotification)
246 : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate),
247 mNotification(aNotification) {}
249 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
250 mNotification->ReleaseObject();
251 return true;
255 class GetPermissionRunnable final : public WorkerMainThreadRunnable {
256 NotificationPermission mPermission;
258 public:
259 explicit GetPermissionRunnable(WorkerPrivate* aWorker)
260 : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns),
261 mPermission(NotificationPermission::Denied) {}
263 bool MainThreadRun() override {
264 ErrorResult result;
265 mPermission = Notification::GetPermissionInternal(
266 mWorkerPrivate->GetPrincipal(), result);
267 return true;
270 NotificationPermission GetPermission() { return mPermission; }
273 class FocusWindowRunnable final : public Runnable {
274 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
276 public:
277 explicit FocusWindowRunnable(
278 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
279 : Runnable("FocusWindowRunnable"), mWindow(aWindow) {}
281 NS_IMETHOD
282 Run() override {
283 AssertIsOnMainThread();
284 if (!mWindow->IsCurrentInnerWindow()) {
285 // Window has been closed, this observer is not valid anymore
286 return NS_OK;
289 nsFocusManager::FocusWindow(mWindow->GetOuterWindow(), CallerType::System);
290 return NS_OK;
294 nsresult CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope,
295 uint64_t aWindowID) {
296 AssertIsOnMainThread();
297 MOZ_ASSERT(aPrincipal);
299 nsCOMPtr<nsIURI> scopeURI;
300 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
301 if (NS_WARN_IF(NS_FAILED(rv))) {
302 return rv;
305 return aPrincipal->CheckMayLoadWithReporting(
306 scopeURI,
307 /* allowIfInheritsPrincipal = */ false, aWindowID);
309 } // anonymous namespace
311 // Subclass that can be directly dispatched to child workers from the main
312 // thread.
313 class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
314 protected:
315 explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate)
316 : MainThreadWorkerRunnable(aWorkerPrivate) {}
318 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
319 aWorkerPrivate->AssertIsOnWorkerThread();
320 aWorkerPrivate->ModifyBusyCountFromWorker(true);
321 WorkerRunInternal(aWorkerPrivate);
322 return true;
325 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
326 bool aRunResult) override {
327 aWorkerPrivate->ModifyBusyCountFromWorker(false);
330 virtual void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0;
333 // Overrides dispatch and run handlers so we can directly dispatch from main
334 // thread to child workers.
335 class NotificationEventWorkerRunnable final
336 : public NotificationWorkerRunnable {
337 Notification* mNotification;
338 const nsString mEventName;
340 public:
341 NotificationEventWorkerRunnable(Notification* aNotification,
342 const nsString& aEventName)
343 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
344 mNotification(aNotification),
345 mEventName(aEventName) {}
347 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
348 mNotification->DispatchTrustedEvent(mEventName);
352 class ReleaseNotificationRunnable final : public NotificationWorkerRunnable {
353 Notification* mNotification;
355 public:
356 explicit ReleaseNotificationRunnable(Notification* aNotification)
357 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
358 mNotification(aNotification) {}
360 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
361 mNotification->ReleaseObject();
364 nsresult Cancel() override {
365 // We need to check first if cancel is called twice
366 nsresult rv = NotificationWorkerRunnable::Cancel();
367 NS_ENSURE_SUCCESS(rv, rv);
369 mNotification->ReleaseObject();
370 return NS_OK;
374 // Create one whenever you require ownership of the notification. Use with
375 // UniquePtr<>. See Notification.h for details.
376 class NotificationRef final {
377 friend class WorkerNotificationObserver;
379 private:
380 Notification* mNotification;
381 bool mInited;
383 // Only useful for workers.
384 void Forget() { mNotification = nullptr; }
386 public:
387 explicit NotificationRef(Notification* aNotification)
388 : mNotification(aNotification) {
389 MOZ_ASSERT(mNotification);
390 if (mNotification->mWorkerPrivate) {
391 mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
392 } else {
393 AssertIsOnMainThread();
396 mInited = mNotification->AddRefObject();
399 // This is only required because Gecko runs script in a worker's onclose
400 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
401 // fail. Due to non-standardness and added complications if we decide to
402 // support this, attempts to create a Notification in onclose just throw
403 // exceptions.
404 bool Initialized() { return mInited; }
406 ~NotificationRef() {
407 if (Initialized() && mNotification) {
408 Notification* notification = mNotification;
409 mNotification = nullptr;
410 if (notification->mWorkerPrivate && NS_IsMainThread()) {
411 // Try to pass ownership back to the worker. If the dispatch succeeds we
412 // are guaranteed this runnable will run, and that it will run after
413 // queued event runnables, so event runnables will have a safe pointer
414 // to the Notification.
416 // If the dispatch fails, the worker isn't running anymore and the event
417 // runnables have already run or been canceled. We can use a control
418 // runnable to release the reference.
419 RefPtr<ReleaseNotificationRunnable> r =
420 new ReleaseNotificationRunnable(notification);
422 if (!r->Dispatch()) {
423 RefPtr<ReleaseNotificationControlRunnable> r =
424 new ReleaseNotificationControlRunnable(notification);
425 MOZ_ALWAYS_TRUE(r->Dispatch());
427 } else {
428 notification->AssertIsOnTargetThread();
429 notification->ReleaseObject();
434 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
435 // a rawptr that the NotificationRef can invalidate?
436 Notification* GetNotification() {
437 MOZ_ASSERT(Initialized());
438 return mNotification;
442 class NotificationTask : public Runnable {
443 public:
444 enum NotificationAction { eShow, eClose };
446 NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef,
447 NotificationAction aAction)
448 : Runnable(aName), mNotificationRef(std::move(aRef)), mAction(aAction) {}
450 NS_IMETHOD
451 Run() override;
453 protected:
454 virtual ~NotificationTask() = default;
456 UniquePtr<NotificationRef> mNotificationRef;
457 NotificationAction mAction;
460 uint32_t Notification::sCount = 0;
462 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
463 ContentPermissionRequestBase, mCallback)
464 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
465 ContentPermissionRequestBase)
466 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
467 ContentPermissionRequestBase)
469 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
470 NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
471 nsINamed)
473 NS_IMETHODIMP
474 NotificationPermissionRequest::Run() {
475 bool isSystem = mPrincipal->IsSystemPrincipal();
476 bool blocked = false;
477 if (isSystem) {
478 mPermission = NotificationPermission::Granted;
479 } else {
480 // File are automatically granted permission.
482 if (mPrincipal->SchemeIs("file")) {
483 mPermission = NotificationPermission::Granted;
484 } else if (!StaticPrefs::dom_webnotifications_allowinsecure() &&
485 !mWindow->IsSecureContext()) {
486 mPermission = NotificationPermission::Denied;
487 blocked = true;
488 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
489 if (doc) {
490 nsContentUtils::ReportToConsole(
491 nsIScriptError::errorFlag, "DOM"_ns, doc,
492 nsContentUtils::eDOM_PROPERTIES,
493 "NotificationsInsecureRequestIsForbidden");
498 // We can't call ShowPrompt() directly here since our logic for determining
499 // whether to display a prompt depends on the checks above as well as the
500 // result of CheckPromptPrefs(). So we have to manually check the prompt
501 // prefs and decide what to do based on that.
502 PromptResult pr = CheckPromptPrefs();
503 switch (pr) {
504 case PromptResult::Granted:
505 mPermission = NotificationPermission::Granted;
506 break;
507 case PromptResult::Denied:
508 mPermission = NotificationPermission::Denied;
509 break;
510 default:
511 // ignore
512 break;
515 if (!mHasValidTransientUserGestureActivation &&
516 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
517 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
518 if (doc) {
519 doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
523 // Check this after checking the prompt prefs to make sure this pref overrides
524 // those. We rely on this for testing purposes.
525 if (!isSystem && !blocked &&
526 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
527 !mPrincipal->Subsumes(mTopLevelPrincipal)) {
528 mPermission = NotificationPermission::Denied;
529 blocked = true;
530 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
531 if (doc) {
532 nsContentUtils::ReportToConsole(
533 nsIScriptError::errorFlag, "DOM"_ns, doc,
534 nsContentUtils::eDOM_PROPERTIES,
535 "NotificationsCrossOriginIframeRequestIsForbidden");
539 if (mPermission != NotificationPermission::Default) {
540 return DispatchResolvePromise();
543 return nsContentPermissionUtils::AskPermission(this, mWindow);
546 NS_IMETHODIMP
547 NotificationPermissionRequest::Cancel() {
548 // `Cancel` is called if the user denied permission or dismissed the
549 // permission request. To distinguish between the two, we set the
550 // permission to "default" and query the permission manager in
551 // `ResolvePromise`.
552 mPermission = NotificationPermission::Default;
553 return DispatchResolvePromise();
556 NS_IMETHODIMP
557 NotificationPermissionRequest::Allow(JS::HandleValue aChoices) {
558 MOZ_ASSERT(aChoices.isUndefined());
560 mPermission = NotificationPermission::Granted;
561 return DispatchResolvePromise();
564 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
565 nsCOMPtr<nsIRunnable> resolver =
566 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
567 this, &NotificationPermissionRequest::ResolvePromise);
568 if (nsIEventTarget* target = mWindow->EventTargetFor(TaskCategory::Other)) {
569 return target->Dispatch(resolver.forget(), nsIEventTarget::DISPATCH_NORMAL);
571 return NS_ERROR_FAILURE;
574 nsresult NotificationPermissionRequest::ResolvePromise() {
575 nsresult rv = NS_OK;
576 // This will still be "default" if the user dismissed the doorhanger,
577 // or "denied" otherwise.
578 if (mPermission == NotificationPermission::Default) {
579 // When the front-end has decided to deny the permission request
580 // automatically and we are not handling user input, then log a
581 // warning in the current document that this happened because
582 // Notifications require a user gesture.
583 if (!mHasValidTransientUserGestureActivation &&
584 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
585 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
586 if (doc) {
587 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
588 doc, nsContentUtils::eDOM_PROPERTIES,
589 "NotificationsRequireUserGesture");
593 mPermission = Notification::TestPermission(mPrincipal);
595 if (mCallback) {
596 ErrorResult error;
597 RefPtr<NotificationPermissionCallback> callback(mCallback);
598 callback->Call(mPermission, error);
599 rv = error.StealNSResult();
601 mPromise->MaybeResolve(mPermission);
602 return rv;
605 // Observer that the alert service calls to do common tasks and/or dispatch to
606 // the specific observer for the context e.g. main thread, worker, or service
607 // worker.
608 class NotificationObserver final : public nsIObserver {
609 public:
610 nsCOMPtr<nsIObserver> mObserver;
611 nsCOMPtr<nsIPrincipal> mPrincipal;
612 bool mInPrivateBrowsing;
613 NS_DECL_ISUPPORTS
614 NS_DECL_NSIOBSERVER
616 NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
617 bool aInPrivateBrowsing)
618 : mObserver(aObserver),
619 mPrincipal(aPrincipal),
620 mInPrivateBrowsing(aInPrivateBrowsing) {
621 AssertIsOnMainThread();
622 MOZ_ASSERT(mObserver);
623 MOZ_ASSERT(mPrincipal);
626 protected:
627 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
629 nsresult AdjustPushQuota(const char* aTopic);
632 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
634 class MainThreadNotificationObserver : public nsIObserver {
635 public:
636 UniquePtr<NotificationRef> mNotificationRef;
637 NS_DECL_ISUPPORTS
638 NS_DECL_NSIOBSERVER
640 explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
641 : mNotificationRef(std::move(aRef)) {
642 AssertIsOnMainThread();
645 protected:
646 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
649 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
651 NS_IMETHODIMP
652 NotificationTask::Run() {
653 AssertIsOnMainThread();
655 // Get a pointer to notification before the notification takes ownership of
656 // the ref (it owns itself temporarily, with ShowInternal() and
657 // CloseInternal() passing on the ownership appropriately.)
658 Notification* notif = mNotificationRef->GetNotification();
659 notif->mTempRef.swap(mNotificationRef);
660 if (mAction == eShow) {
661 notif->ShowInternal();
662 } else if (mAction == eClose) {
663 notif->CloseInternal();
664 } else {
665 MOZ_CRASH("Invalid action");
668 MOZ_ASSERT(!mNotificationRef);
669 return NS_OK;
672 // static
673 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
674 if (!NS_IsMainThread()) {
675 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
676 if (!workerPrivate) {
677 return false;
680 if (workerPrivate->IsServiceWorker()) {
681 return StaticPrefs::dom_webnotifications_serviceworker_enabled();
685 return StaticPrefs::dom_webnotifications_enabled();
688 // static
689 bool Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj) {
690 return NS_IsMainThread();
693 Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
694 const nsAString& aTitle, const nsAString& aBody,
695 NotificationDirection aDir, const nsAString& aLang,
696 const nsAString& aTag, const nsAString& aIconUrl,
697 bool aRequireInteraction, bool aSilent,
698 nsTArray<uint32_t>&& aVibrate,
699 const NotificationBehavior& aBehavior)
700 : DOMEventTargetHelper(aGlobal),
701 mWorkerPrivate(nullptr),
702 mObserver(nullptr),
703 mID(aID),
704 mTitle(aTitle),
705 mBody(aBody),
706 mDir(aDir),
707 mLang(aLang),
708 mTag(aTag),
709 mIconUrl(aIconUrl),
710 mRequireInteraction(aRequireInteraction),
711 mSilent(aSilent),
712 mVibrate(std::move(aVibrate)),
713 mBehavior(aBehavior),
714 mData(JS::NullValue()),
715 mIsClosed(false),
716 mIsStored(false),
717 mTaskCount(0) {
718 if (!NS_IsMainThread()) {
719 mWorkerPrivate = GetCurrentThreadWorkerPrivate();
720 MOZ_ASSERT(mWorkerPrivate);
724 nsresult Notification::Init() {
725 if (!mWorkerPrivate) {
726 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
727 NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
729 nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
730 NS_ENSURE_SUCCESS(rv, rv);
732 rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
733 NS_ENSURE_SUCCESS(rv, rv);
736 return NS_OK;
739 void Notification::SetAlertName() {
740 AssertIsOnMainThread();
741 if (!mAlertName.IsEmpty()) {
742 return;
745 nsAutoString alertName;
746 nsresult rv = GetOrigin(GetPrincipal(), alertName);
747 if (NS_WARN_IF(NS_FAILED(rv))) {
748 return;
751 // Get the notification name that is unique per origin + tag/ID.
752 // The name of the alert is of the form origin#tag/ID.
753 alertName.Append('#');
754 if (!mTag.IsEmpty()) {
755 alertName.AppendLiteral("tag:");
756 alertName.Append(mTag);
757 } else {
758 alertName.AppendLiteral("notag:");
759 alertName.Append(mID);
762 mAlertName = alertName;
765 // May be called on any thread.
766 // static
767 already_AddRefed<Notification> Notification::Constructor(
768 const GlobalObject& aGlobal, const nsAString& aTitle,
769 const NotificationOptions& aOptions, ErrorResult& aRv) {
770 // FIXME(nsm): If the sticky flag is set, throw an error.
771 RefPtr<ServiceWorkerGlobalScope> scope;
772 UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
773 if (scope) {
774 aRv.ThrowTypeError(
775 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
776 "Use registration.showNotification() instead.");
777 return nullptr;
780 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
781 RefPtr<Notification> notification =
782 CreateAndShow(aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
783 if (NS_WARN_IF(aRv.Failed())) {
784 return nullptr;
787 // This is be ok since we are on the worker thread where this function will
788 // run to completion before the Notification has a chance to go away.
789 return notification.forget();
792 // static
793 already_AddRefed<Notification> Notification::ConstructFromFields(
794 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
795 const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
796 const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
797 const nsAString& aServiceWorkerRegistrationScope, ErrorResult& aRv) {
798 MOZ_ASSERT(aGlobal);
800 RootedDictionary<NotificationOptions> options(RootingCx());
801 options.mDir = Notification::StringToDirection(nsString(aDir));
802 options.mLang = aLang;
803 options.mBody = aBody;
804 options.mTag = aTag;
805 options.mIcon = aIcon;
806 RefPtr<Notification> notification =
807 CreateInternal(aGlobal, aID, aTitle, options, aRv);
809 notification->InitFromBase64(aData, aRv);
810 if (NS_WARN_IF(aRv.Failed())) {
811 return nullptr;
814 notification->SetScope(aServiceWorkerRegistrationScope);
816 return notification.forget();
819 nsresult Notification::PersistNotification() {
820 AssertIsOnMainThread();
821 nsresult rv;
822 nsCOMPtr<nsINotificationStorage> notificationStorage =
823 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
824 if (NS_FAILED(rv)) {
825 return rv;
828 nsString origin;
829 rv = GetOrigin(GetPrincipal(), origin);
830 if (NS_WARN_IF(NS_FAILED(rv))) {
831 return rv;
834 nsString id;
835 GetID(id);
837 nsString alertName;
838 GetAlertName(alertName);
840 nsAutoString behavior;
841 if (!mBehavior.ToJSON(behavior)) {
842 return NS_ERROR_FAILURE;
845 rv = notificationStorage->Put(origin, id, mTitle, DirectionToString(mDir),
846 mLang, mBody, mTag, mIconUrl, alertName,
847 mDataAsBase64, behavior, mScope);
849 if (NS_FAILED(rv)) {
850 return rv;
853 SetStoredState(true);
854 return NS_OK;
857 void Notification::UnpersistNotification() {
858 AssertIsOnMainThread();
859 if (IsStored()) {
860 nsCOMPtr<nsINotificationStorage> notificationStorage =
861 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
862 if (notificationStorage) {
863 nsString origin;
864 nsresult rv = GetOrigin(GetPrincipal(), origin);
865 if (NS_SUCCEEDED(rv)) {
866 notificationStorage->Delete(origin, mID);
869 SetStoredState(false);
873 already_AddRefed<Notification> Notification::CreateInternal(
874 nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
875 const NotificationOptions& aOptions, ErrorResult& aRv) {
876 nsresult rv;
877 nsString id;
878 if (!aID.IsEmpty()) {
879 id = aID;
880 } else {
881 nsCOMPtr<nsIUUIDGenerator> uuidgen =
882 do_GetService("@mozilla.org/uuid-generator;1");
883 NS_ENSURE_TRUE(uuidgen, nullptr);
884 nsID uuid;
885 rv = uuidgen->GenerateUUIDInPlace(&uuid);
886 NS_ENSURE_SUCCESS(rv, nullptr);
888 char buffer[NSID_LENGTH];
889 uuid.ToProvidedString(buffer);
890 NS_ConvertASCIItoUTF16 convertedID(buffer);
891 id = convertedID;
894 bool silent = false;
895 if (StaticPrefs::dom_webnotifications_silent_enabled()) {
896 silent = aOptions.mSilent;
899 nsTArray<uint32_t> vibrate;
900 if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
901 aOptions.mVibrate.WasPassed()) {
902 if (silent) {
903 aRv.ThrowTypeError(
904 "Silent notifications must not specify vibration patterns.");
905 return nullptr;
908 const OwningUnsignedLongOrUnsignedLongSequence& value =
909 aOptions.mVibrate.Value();
910 if (value.IsUnsignedLong()) {
911 AutoTArray<uint32_t, 1> array;
912 array.AppendElement(value.GetAsUnsignedLong());
913 vibrate = SanitizeVibratePattern(array);
914 } else {
915 vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence());
919 RefPtr<Notification> notification = new Notification(
920 aGlobal, id, aTitle, aOptions.mBody, aOptions.mDir, aOptions.mLang,
921 aOptions.mTag, aOptions.mIcon, aOptions.mRequireInteraction, silent,
922 std::move(vibrate), aOptions.mMozbehavior);
923 rv = notification->Init();
924 NS_ENSURE_SUCCESS(rv, nullptr);
925 return notification.forget();
928 Notification::~Notification() {
929 mozilla::DropJSObjects(this);
930 AssertIsOnTargetThread();
931 MOZ_ASSERT(!mWorkerRef);
932 MOZ_ASSERT(!mTempRef);
935 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
936 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
937 DOMEventTargetHelper)
938 tmp->mData.setUndefined();
939 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
940 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
942 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
943 DOMEventTargetHelper)
944 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
946 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
947 DOMEventTargetHelper)
948 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
949 NS_IMPL_CYCLE_COLLECTION_TRACE_END
951 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
952 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
954 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
955 NS_INTERFACE_MAP_ENTRY(nsIObserver)
956 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
957 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
959 nsIPrincipal* Notification::GetPrincipal() {
960 AssertIsOnMainThread();
961 if (mWorkerPrivate) {
962 return mWorkerPrivate->GetPrincipal();
963 } else {
964 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
965 NS_ENSURE_TRUE(sop, nullptr);
966 return sop->GetPrincipal();
970 class WorkerNotificationObserver final : public MainThreadNotificationObserver {
971 public:
972 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver,
973 MainThreadNotificationObserver)
974 NS_DECL_NSIOBSERVER
976 explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
977 : MainThreadNotificationObserver(std::move(aRef)) {
978 AssertIsOnMainThread();
979 MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate);
982 void ForgetNotification() {
983 AssertIsOnMainThread();
984 mNotificationRef->Forget();
987 protected:
988 virtual ~WorkerNotificationObserver() {
989 AssertIsOnMainThread();
991 MOZ_ASSERT(mNotificationRef);
992 Notification* notification = mNotificationRef->GetNotification();
993 if (notification) {
994 notification->mObserver = nullptr;
999 class ServiceWorkerNotificationObserver final : public nsIObserver {
1000 public:
1001 NS_DECL_ISUPPORTS
1002 NS_DECL_NSIOBSERVER
1004 ServiceWorkerNotificationObserver(
1005 const nsAString& aScope, nsIPrincipal* aPrincipal, const nsAString& aID,
1006 const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang,
1007 const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon,
1008 const nsAString& aData, const nsAString& aBehavior)
1009 : mScope(aScope),
1010 mID(aID),
1011 mPrincipal(aPrincipal),
1012 mTitle(aTitle),
1013 mDir(aDir),
1014 mLang(aLang),
1015 mBody(aBody),
1016 mTag(aTag),
1017 mIcon(aIcon),
1018 mData(aData),
1019 mBehavior(aBehavior) {
1020 AssertIsOnMainThread();
1021 MOZ_ASSERT(aPrincipal);
1024 private:
1025 ~ServiceWorkerNotificationObserver() = default;
1027 const nsString mScope;
1028 const nsString mID;
1029 nsCOMPtr<nsIPrincipal> mPrincipal;
1030 const nsString mTitle;
1031 const nsString mDir;
1032 const nsString mLang;
1033 const nsString mBody;
1034 const nsString mTag;
1035 const nsString mIcon;
1036 const nsString mData;
1037 const nsString mBehavior;
1040 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver)
1042 bool Notification::DispatchClickEvent() {
1043 AssertIsOnTargetThread();
1044 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
1045 event->InitEvent(u"click"_ns, false, true);
1046 event->SetTrusted(true);
1047 WantsPopupControlCheck popupControlCheck(event);
1048 return DispatchEvent(*event, CallerType::System, IgnoreErrors());
1051 // Overrides dispatch and run handlers so we can directly dispatch from main
1052 // thread to child workers.
1053 class NotificationClickWorkerRunnable final
1054 : public NotificationWorkerRunnable {
1055 Notification* mNotification;
1056 // Optional window that gets focused if click event is not
1057 // preventDefault()ed.
1058 nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
1060 public:
1061 NotificationClickWorkerRunnable(
1062 Notification* aNotification,
1063 const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
1064 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
1065 mNotification(aNotification),
1066 mWindow(aWindow) {
1067 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow);
1070 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1071 bool doDefaultAction = mNotification->DispatchClickEvent();
1072 MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction);
1073 if (doDefaultAction) {
1074 RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
1075 mWorkerPrivate->DispatchToMainThread(r.forget());
1080 NS_IMETHODIMP
1081 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1082 const char16_t* aData) {
1083 AssertIsOnMainThread();
1085 if (!strcmp("alertdisablecallback", aTopic)) {
1086 if (XRE_IsParentProcess()) {
1087 return Notification::RemovePermission(mPrincipal);
1089 // Permissions can't be removed from the content process. Send a message
1090 // to the parent; `ContentParent::RecvDisableNotifications` will call
1091 // `RemovePermission`.
1092 ContentChild::GetSingleton()->SendDisableNotifications(
1093 IPC::Principal(mPrincipal));
1094 return NS_OK;
1095 } else if (!strcmp("alertsettingscallback", aTopic)) {
1096 if (XRE_IsParentProcess()) {
1097 return Notification::OpenSettings(mPrincipal);
1099 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1100 // parent process.
1101 ContentChild::GetSingleton()->SendOpenNotificationSettings(
1102 IPC::Principal(mPrincipal));
1103 return NS_OK;
1104 } else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) {
1105 Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
1108 return mObserver->Observe(aSubject, aTopic, aData);
1111 nsresult NotificationObserver::AdjustPushQuota(const char* aTopic) {
1112 nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
1113 do_GetService("@mozilla.org/push/Service;1");
1114 if (!pushQuotaManager) {
1115 return NS_ERROR_FAILURE;
1118 nsAutoCString origin;
1119 nsresult rv = mPrincipal->GetOrigin(origin);
1120 if (NS_FAILED(rv)) {
1121 return rv;
1124 if (!strcmp("alertshow", aTopic)) {
1125 return pushQuotaManager->NotificationForOriginShown(origin.get());
1127 return pushQuotaManager->NotificationForOriginClosed(origin.get());
1130 NS_IMETHODIMP
1131 MainThreadNotificationObserver::Observe(nsISupports* aSubject,
1132 const char* aTopic,
1133 const char16_t* aData) {
1134 AssertIsOnMainThread();
1135 MOZ_ASSERT(mNotificationRef);
1136 Notification* notification = mNotificationRef->GetNotification();
1137 MOZ_ASSERT(notification);
1138 if (!strcmp("alertclickcallback", aTopic)) {
1139 nsCOMPtr<nsPIDOMWindowInner> window = notification->GetOwner();
1140 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1141 // Window has been closed, this observer is not valid anymore
1142 return NS_ERROR_FAILURE;
1145 bool doDefaultAction = notification->DispatchClickEvent();
1146 if (doDefaultAction) {
1147 nsFocusManager::FocusWindow(window->GetOuterWindow(), CallerType::System);
1149 } else if (!strcmp("alertfinished", aTopic)) {
1150 notification->UnpersistNotification();
1151 notification->mIsClosed = true;
1152 notification->DispatchTrustedEvent(u"close"_ns);
1153 } else if (!strcmp("alertshow", aTopic)) {
1154 notification->DispatchTrustedEvent(u"show"_ns);
1156 return NS_OK;
1159 NS_IMETHODIMP
1160 WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1161 const char16_t* aData) {
1162 AssertIsOnMainThread();
1163 MOZ_ASSERT(mNotificationRef);
1164 // For an explanation of why it is OK to pass this rawptr to the event
1165 // runnables, see the Notification class comment.
1166 Notification* notification = mNotificationRef->GetNotification();
1167 // We can't assert notification here since the feature could've unset it.
1168 if (NS_WARN_IF(!notification)) {
1169 return NS_ERROR_FAILURE;
1172 MOZ_ASSERT(notification->mWorkerPrivate);
1174 RefPtr<WorkerRunnable> r;
1175 if (!strcmp("alertclickcallback", aTopic)) {
1176 nsPIDOMWindowInner* window = nullptr;
1177 if (!notification->mWorkerPrivate->IsServiceWorker()) {
1178 WorkerPrivate* top = notification->mWorkerPrivate;
1179 while (top->GetParent()) {
1180 top = top->GetParent();
1183 window = top->GetWindow();
1184 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1185 // Window has been closed, this observer is not valid anymore
1186 return NS_ERROR_FAILURE;
1190 // Instead of bothering with adding features and other worker lifecycle
1191 // management, we simply hold strongrefs to the window and document.
1192 nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
1193 new nsMainThreadPtrHolder<nsPIDOMWindowInner>(
1194 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window));
1196 r = new NotificationClickWorkerRunnable(notification, windowHandle);
1197 } else if (!strcmp("alertfinished", aTopic)) {
1198 notification->UnpersistNotification();
1199 notification->mIsClosed = true;
1200 r = new NotificationEventWorkerRunnable(notification, u"close"_ns);
1201 } else if (!strcmp("alertshow", aTopic)) {
1202 r = new NotificationEventWorkerRunnable(notification, u"show"_ns);
1205 MOZ_ASSERT(r);
1206 if (!r->Dispatch()) {
1207 NS_WARNING("Could not dispatch event to worker notification");
1209 return NS_OK;
1212 NS_IMETHODIMP
1213 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
1214 const char* aTopic,
1215 const char16_t* aData) {
1216 AssertIsOnMainThread();
1218 nsAutoCString originSuffix;
1219 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
1220 if (NS_WARN_IF(NS_FAILED(rv))) {
1221 return rv;
1224 if (!strcmp("alertclickcallback", aTopic)) {
1225 if (XRE_IsParentProcess()) {
1226 nsCOMPtr<nsIServiceWorkerManager> swm =
1227 mozilla::components::ServiceWorkerManager::Service();
1228 if (NS_WARN_IF(!swm)) {
1229 return NS_ERROR_FAILURE;
1232 rv = swm->SendNotificationClickEvent(
1233 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1234 mBody, mTag, mIcon, mData, mBehavior);
1235 Unused << NS_WARN_IF(NS_FAILED(rv));
1236 } else {
1237 auto* cc = ContentChild::GetSingleton();
1238 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1239 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1240 mData, mBehavior);
1241 Unused << cc->SendNotificationEvent(u"click"_ns, data);
1243 return NS_OK;
1246 if (!strcmp("alertfinished", aTopic)) {
1247 nsString origin;
1248 nsresult rv = Notification::GetOrigin(mPrincipal, origin);
1249 if (NS_WARN_IF(NS_FAILED(rv))) {
1250 return rv;
1253 // Remove closed or dismissed persistent notifications.
1254 nsCOMPtr<nsINotificationStorage> notificationStorage =
1255 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1256 if (notificationStorage) {
1257 notificationStorage->Delete(origin, mID);
1260 if (XRE_IsParentProcess()) {
1261 nsCOMPtr<nsIServiceWorkerManager> swm =
1262 mozilla::components::ServiceWorkerManager::Service();
1263 if (NS_WARN_IF(!swm)) {
1264 return NS_ERROR_FAILURE;
1267 rv = swm->SendNotificationCloseEvent(
1268 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1269 mBody, mTag, mIcon, mData, mBehavior);
1270 Unused << NS_WARN_IF(NS_FAILED(rv));
1271 } else {
1272 auto* cc = ContentChild::GetSingleton();
1273 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1274 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1275 mData, mBehavior);
1276 Unused << cc->SendNotificationEvent(u"close"_ns, data);
1278 return NS_OK;
1281 return NS_OK;
1284 bool Notification::IsInPrivateBrowsing() {
1285 AssertIsOnMainThread();
1287 Document* doc = nullptr;
1289 if (mWorkerPrivate) {
1290 doc = mWorkerPrivate->GetDocument();
1291 } else if (GetOwner()) {
1292 doc = GetOwner()->GetExtantDoc();
1295 if (doc) {
1296 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
1297 return loadContext && loadContext->UsePrivateBrowsing();
1300 if (mWorkerPrivate) {
1301 // Not all workers may have a document, but with Bug 1107516 fixed, they
1302 // should all have a loadcontext.
1303 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1304 nsCOMPtr<nsILoadContext> loadContext;
1305 NS_QueryNotificationCallbacks(nullptr, loadGroup,
1306 NS_GET_IID(nsILoadContext),
1307 getter_AddRefs(loadContext));
1308 return loadContext && loadContext->UsePrivateBrowsing();
1311 // XXXnsm Should this default to true?
1312 return false;
1315 namespace {
1316 struct StringWriteFunc : public JSONWriteFunc {
1317 nsAString& mBuffer; // This struct must not outlive this buffer
1318 explicit StringWriteFunc(nsAString& buffer) : mBuffer(buffer) {}
1320 void Write(const Span<const char>& aStr) override {
1321 mBuffer.Append(NS_ConvertUTF8toUTF16(aStr.data(), aStr.size()));
1324 } // namespace
1326 void Notification::ShowInternal() {
1327 AssertIsOnMainThread();
1328 MOZ_ASSERT(mTempRef,
1329 "Notification should take ownership of itself before"
1330 "calling ShowInternal!");
1331 // A notification can only have one observer and one call to ShowInternal.
1332 MOZ_ASSERT(!mObserver);
1334 // Transfer ownership to local scope so we can either release it at the end
1335 // of this function or transfer it to the observer.
1336 UniquePtr<NotificationRef> ownership;
1337 std::swap(ownership, mTempRef);
1338 MOZ_ASSERT(ownership->GetNotification() == this);
1340 nsresult rv = PersistNotification();
1341 if (NS_FAILED(rv)) {
1342 NS_WARNING("Could not persist Notification");
1345 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1347 ErrorResult result;
1348 NotificationPermission permission = NotificationPermission::Denied;
1349 if (mWorkerPrivate) {
1350 permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
1351 } else {
1352 permission = GetPermissionInternal(GetOwner(), result);
1354 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1355 MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
1356 result.SuppressException();
1357 if (permission != NotificationPermission::Granted || !alertService) {
1358 if (mWorkerPrivate) {
1359 RefPtr<NotificationEventWorkerRunnable> r =
1360 new NotificationEventWorkerRunnable(this, u"error"_ns);
1361 if (!r->Dispatch()) {
1362 NS_WARNING("Could not dispatch event to worker notification");
1364 } else {
1365 DispatchTrustedEvent(u"error"_ns);
1367 return;
1370 nsAutoString iconUrl;
1371 nsAutoString soundUrl;
1372 ResolveIconAndSoundURL(iconUrl, soundUrl);
1374 bool isPersistent = false;
1375 nsCOMPtr<nsIObserver> observer;
1376 if (mScope.IsEmpty()) {
1377 // Ownership passed to observer.
1378 if (mWorkerPrivate) {
1379 // Scope better be set on ServiceWorker initiated requests.
1380 MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
1381 // Keep a pointer so that the feature can tell the observer not to release
1382 // the notification.
1383 mObserver = new WorkerNotificationObserver(std::move(ownership));
1384 observer = mObserver;
1385 } else {
1386 observer = new MainThreadNotificationObserver(std::move(ownership));
1388 } else {
1389 isPersistent = true;
1390 // This observer does not care about the Notification. It will be released
1391 // at the end of this function.
1393 // The observer is wholly owned by the NotificationObserver passed to the
1394 // alert service.
1395 nsAutoString behavior;
1396 if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
1397 behavior.Truncate();
1399 observer = new ServiceWorkerNotificationObserver(
1400 mScope, GetPrincipal(), mID, mTitle, DirectionToString(mDir), mLang,
1401 mBody, mTag, iconUrl, mDataAsBase64, behavior);
1403 MOZ_ASSERT(observer);
1404 nsCOMPtr<nsIObserver> alertObserver =
1405 new NotificationObserver(observer, GetPrincipal(), IsInPrivateBrowsing());
1407 // In the case of IPC, the parent process uses the cookie to map to
1408 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1409 nsString uniqueCookie = u"notification:"_ns;
1410 uniqueCookie.AppendInt(sCount++);
1411 bool inPrivateBrowsing = IsInPrivateBrowsing();
1413 bool requireInteraction = mRequireInteraction;
1414 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1415 requireInteraction = false;
1418 nsAutoString alertName;
1419 GetAlertName(alertName);
1420 nsCOMPtr<nsIAlertNotification> alert =
1421 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
1422 NS_ENSURE_TRUE_VOID(alert);
1423 nsIPrincipal* principal = GetPrincipal();
1424 rv =
1425 alert->Init(alertName, iconUrl, mTitle, mBody, true, uniqueCookie,
1426 DirectionToString(mDir), mLang, mDataAsBase64, GetPrincipal(),
1427 inPrivateBrowsing, requireInteraction, mSilent, mVibrate);
1428 NS_ENSURE_SUCCESS_VOID(rv);
1430 if (isPersistent) {
1431 nsAutoString persistentData;
1433 JSONWriter w(MakeUnique<StringWriteFunc>(persistentData));
1434 w.Start();
1436 nsAutoString origin;
1437 Notification::GetOrigin(principal, origin);
1438 w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin));
1440 w.StringProperty("id", NS_ConvertUTF16toUTF8(mID));
1442 nsAutoCString originSuffix;
1443 principal->GetOriginSuffix(originSuffix);
1444 w.StringProperty("originSuffix", originSuffix);
1446 w.End();
1448 alertService->ShowPersistentNotification(persistentData, alert,
1449 alertObserver);
1450 } else {
1451 alertService->ShowAlert(alert, alertObserver);
1455 /* static */
1456 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
1457 JSObject* /* unused */) {
1458 // requestPermission() is not allowed on workers. The calling page should ask
1459 // for permission on the worker's behalf. This is to prevent 'which window
1460 // should show the browser pop-up'. See discussion:
1461 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1462 return NS_IsMainThread();
1465 // static
1466 already_AddRefed<Promise> Notification::RequestPermission(
1467 const GlobalObject& aGlobal,
1468 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
1469 ErrorResult& aRv) {
1470 AssertIsOnMainThread();
1472 // Get principal from global to make permission request for notifications.
1473 nsCOMPtr<nsPIDOMWindowInner> window =
1474 do_QueryInterface(aGlobal.GetAsSupports());
1475 nsCOMPtr<nsIScriptObjectPrincipal> sop =
1476 do_QueryInterface(aGlobal.GetAsSupports());
1477 if (!sop || !window) {
1478 aRv.Throw(NS_ERROR_UNEXPECTED);
1479 return nullptr;
1481 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1483 RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
1484 if (aRv.Failed()) {
1485 return nullptr;
1487 NotificationPermissionCallback* permissionCallback = nullptr;
1488 if (aCallback.WasPassed()) {
1489 permissionCallback = &aCallback.Value();
1491 nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
1492 principal, window, promise, permissionCallback);
1494 window->AsGlobal()->Dispatch(TaskCategory::Other, request.forget());
1496 return promise.forget();
1499 // static
1500 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
1501 ErrorResult& aRv) {
1502 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1503 return GetPermission(global, aRv);
1506 // static
1507 NotificationPermission Notification::GetPermission(nsIGlobalObject* aGlobal,
1508 ErrorResult& aRv) {
1509 if (NS_IsMainThread()) {
1510 return GetPermissionInternal(aGlobal, aRv);
1511 } else {
1512 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1513 MOZ_ASSERT(worker);
1514 RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(worker);
1515 r->Dispatch(Canceling, aRv);
1516 if (aRv.Failed()) {
1517 return NotificationPermission::Denied;
1520 return r->GetPermission();
1524 /* static */
1525 NotificationPermission Notification::GetPermissionInternal(nsISupports* aGlobal,
1526 ErrorResult& aRv) {
1527 // Get principal from global to check permission for notifications.
1528 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
1529 if (!sop) {
1530 aRv.Throw(NS_ERROR_UNEXPECTED);
1531 return NotificationPermission::Denied;
1534 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1535 return GetPermissionInternal(principal, aRv);
1538 /* static */
1539 NotificationPermission Notification::GetPermissionInternal(
1540 nsIPrincipal* aPrincipal, ErrorResult& aRv) {
1541 AssertIsOnMainThread();
1542 MOZ_ASSERT(aPrincipal);
1544 if (aPrincipal->IsSystemPrincipal()) {
1545 return NotificationPermission::Granted;
1546 } else {
1547 // Allow files to show notifications by default.
1548 if (aPrincipal->SchemeIs("file")) {
1549 return NotificationPermission::Granted;
1553 // We also allow notifications is they are pref'ed on.
1554 if (Preferences::GetBool("notification.prompt.testing", false)) {
1555 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1556 return NotificationPermission::Granted;
1557 } else {
1558 return NotificationPermission::Denied;
1562 return TestPermission(aPrincipal);
1565 /* static */
1566 NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) {
1567 AssertIsOnMainThread();
1569 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
1571 nsCOMPtr<nsIPermissionManager> permissionManager =
1572 components::PermissionManager::Service();
1573 if (!permissionManager) {
1574 return NotificationPermission::Default;
1577 permissionManager->TestExactPermissionFromPrincipal(
1578 aPrincipal, "desktop-notification"_ns, &permission);
1580 // Convert the result to one of the enum types.
1581 switch (permission) {
1582 case nsIPermissionManager::ALLOW_ACTION:
1583 return NotificationPermission::Granted;
1584 case nsIPermissionManager::DENY_ACTION:
1585 return NotificationPermission::Denied;
1586 default:
1587 return NotificationPermission::Default;
1591 nsresult Notification::ResolveIconAndSoundURL(nsString& iconUrl,
1592 nsString& soundUrl) {
1593 AssertIsOnMainThread();
1594 nsresult rv = NS_OK;
1596 nsIURI* baseUri = nullptr;
1598 // XXXnsm If I understand correctly, the character encoding for resolving
1599 // URIs in new specs is dictated by the URL spec, which states that unless
1600 // the URL parser is passed an override encoding, the charset to be used is
1601 // UTF-8. The new Notification icon/sound specification just says to use the
1602 // Fetch API, where the Request constructor defers to URL parsing specifying
1603 // the API base URL and no override encoding. So we've to use UTF-8 on
1604 // workers, but for backwards compat keeping it document charset on main
1605 // thread.
1606 auto encoding = UTF_8_ENCODING;
1608 if (mWorkerPrivate) {
1609 baseUri = mWorkerPrivate->GetBaseURI();
1610 } else {
1611 Document* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1612 if (doc) {
1613 baseUri = doc->GetBaseURI();
1614 encoding = doc->GetDocumentCharacterSet();
1615 } else {
1616 NS_WARNING("No document found for main thread notification!");
1617 return NS_ERROR_FAILURE;
1621 if (baseUri) {
1622 if (mIconUrl.Length() > 0) {
1623 nsCOMPtr<nsIURI> srcUri;
1624 rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, encoding, baseUri);
1625 if (NS_SUCCEEDED(rv)) {
1626 nsAutoCString src;
1627 srcUri->GetSpec(src);
1628 CopyUTF8toUTF16(src, iconUrl);
1631 if (mBehavior.mSoundFile.Length() > 0) {
1632 nsCOMPtr<nsIURI> srcUri;
1633 rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding,
1634 baseUri);
1635 if (NS_SUCCEEDED(rv)) {
1636 nsAutoCString src;
1637 srcUri->GetSpec(src);
1638 CopyUTF8toUTF16(src, soundUrl);
1643 return rv;
1646 already_AddRefed<Promise> Notification::Get(
1647 nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter,
1648 const nsAString& aScope, ErrorResult& aRv) {
1649 AssertIsOnMainThread();
1650 MOZ_ASSERT(aWindow);
1652 nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
1653 if (!doc) {
1654 aRv.Throw(NS_ERROR_UNEXPECTED);
1655 return nullptr;
1658 nsString origin;
1659 aRv = GetOrigin(doc->NodePrincipal(), origin);
1660 if (aRv.Failed()) {
1661 return nullptr;
1664 RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
1665 if (aRv.Failed()) {
1666 return nullptr;
1669 nsCOMPtr<nsINotificationStorageCallback> callback =
1670 new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
1672 RefPtr<NotificationGetRunnable> r =
1673 new NotificationGetRunnable(origin, aFilter.mTag, callback);
1675 aRv = aWindow->AsGlobal()->Dispatch(TaskCategory::Other, r.forget());
1676 if (NS_WARN_IF(aRv.Failed())) {
1677 return nullptr;
1680 return promise.forget();
1683 already_AddRefed<Promise> Notification::Get(
1684 const GlobalObject& aGlobal, const GetNotificationOptions& aFilter,
1685 ErrorResult& aRv) {
1686 AssertIsOnMainThread();
1687 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1688 MOZ_ASSERT(global);
1689 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
1691 return Get(window, aFilter, u""_ns, aRv);
1694 class WorkerGetResultRunnable final : public NotificationWorkerRunnable {
1695 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1696 const nsTArray<NotificationStrings> mStrings;
1698 public:
1699 WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
1700 PromiseWorkerProxy* aPromiseProxy,
1701 nsTArray<NotificationStrings>&& aStrings)
1702 : NotificationWorkerRunnable(aWorkerPrivate),
1703 mPromiseProxy(aPromiseProxy),
1704 mStrings(std::move(aStrings)) {}
1706 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1707 RefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise();
1709 ErrorResult result;
1710 AutoTArray<RefPtr<Notification>, 5> notifications;
1711 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
1712 RefPtr<Notification> n = Notification::ConstructFromFields(
1713 aWorkerPrivate->GlobalScope(), mStrings[i].mID, mStrings[i].mTitle,
1714 mStrings[i].mDir, mStrings[i].mLang, mStrings[i].mBody,
1715 mStrings[i].mTag, mStrings[i].mIcon, mStrings[i].mData,
1716 /* mStrings[i].mBehavior, not
1717 * supported */
1718 mStrings[i].mServiceWorkerRegistrationScope, result);
1720 n->SetStoredState(true);
1721 Unused << NS_WARN_IF(result.Failed());
1722 if (!result.Failed()) {
1723 notifications.AppendElement(n.forget());
1727 workerPromise->MaybeResolve(notifications);
1728 mPromiseProxy->CleanUp();
1732 class WorkerGetCallback final : public ScopeCheckingGetCallback {
1733 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1735 public:
1736 NS_DECL_ISUPPORTS
1738 WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
1739 : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) {
1740 AssertIsOnMainThread();
1741 MOZ_ASSERT(aProxy);
1744 NS_IMETHOD Done() final {
1745 AssertIsOnMainThread();
1746 MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
1748 RefPtr<PromiseWorkerProxy> proxy = std::move(mPromiseProxy);
1749 MutexAutoLock lock(proxy->Lock());
1750 if (proxy->CleanedUp()) {
1751 return NS_OK;
1754 RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
1755 proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
1757 r->Dispatch();
1758 return NS_OK;
1761 private:
1762 ~WorkerGetCallback() = default;
1765 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
1767 class WorkerGetRunnable final : public Runnable {
1768 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1769 const nsString mTag;
1770 const nsString mScope;
1772 public:
1773 WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag,
1774 const nsAString& aScope)
1775 : Runnable("WorkerGetRunnable"),
1776 mPromiseProxy(aProxy),
1777 mTag(aTag),
1778 mScope(aScope) {
1779 MOZ_ASSERT(mPromiseProxy);
1782 NS_IMETHOD
1783 Run() override {
1784 AssertIsOnMainThread();
1785 nsCOMPtr<nsINotificationStorageCallback> callback =
1786 new WorkerGetCallback(mPromiseProxy, mScope);
1788 nsresult rv;
1789 nsCOMPtr<nsINotificationStorage> notificationStorage =
1790 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
1791 if (NS_WARN_IF(NS_FAILED(rv))) {
1792 callback->Done();
1793 return rv;
1796 MutexAutoLock lock(mPromiseProxy->Lock());
1797 if (mPromiseProxy->CleanedUp()) {
1798 return NS_OK;
1801 nsString origin;
1802 rv = Notification::GetOrigin(
1803 mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
1804 if (NS_WARN_IF(NS_FAILED(rv))) {
1805 callback->Done();
1806 return rv;
1809 rv = notificationStorage->Get(origin, mTag, callback);
1810 if (NS_WARN_IF(NS_FAILED(rv))) {
1811 callback->Done();
1812 return rv;
1815 return NS_OK;
1818 private:
1819 ~WorkerGetRunnable() = default;
1822 // static
1823 already_AddRefed<Promise> Notification::WorkerGet(
1824 WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
1825 const nsAString& aScope, ErrorResult& aRv) {
1826 MOZ_ASSERT(aWorkerPrivate);
1827 aWorkerPrivate->AssertIsOnWorkerThread();
1828 RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
1829 if (NS_WARN_IF(aRv.Failed())) {
1830 return nullptr;
1833 RefPtr<PromiseWorkerProxy> proxy =
1834 PromiseWorkerProxy::Create(aWorkerPrivate, p);
1835 if (!proxy) {
1836 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
1837 return nullptr;
1840 RefPtr<WorkerGetRunnable> r =
1841 new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
1842 // Since this is called from script via
1843 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1844 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
1845 return p.forget();
1848 JSObject* Notification::WrapObject(JSContext* aCx,
1849 JS::Handle<JSObject*> aGivenProto) {
1850 return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
1853 void Notification::Close() {
1854 AssertIsOnTargetThread();
1855 auto ref = MakeUnique<NotificationRef>(this);
1856 if (!ref->Initialized()) {
1857 return;
1860 nsCOMPtr<nsIRunnable> closeNotificationTask = new NotificationTask(
1861 "Notification::Close", std::move(ref), NotificationTask::eClose);
1862 nsresult rv = DispatchToMainThread(closeNotificationTask.forget());
1864 if (NS_FAILED(rv)) {
1865 DispatchTrustedEvent(u"error"_ns);
1866 // If dispatch fails, NotificationTask will release the ref when it goes
1867 // out of scope at the end of this function.
1871 void Notification::CloseInternal() {
1872 AssertIsOnMainThread();
1873 // Transfer ownership (if any) to local scope so we can release it at the end
1874 // of this function. This is relevant when the call is from
1875 // NotificationTask::Run().
1876 UniquePtr<NotificationRef> ownership;
1877 std::swap(ownership, mTempRef);
1879 SetAlertName();
1880 UnpersistNotification();
1881 if (!mIsClosed) {
1882 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1883 if (alertService) {
1884 nsAutoString alertName;
1885 GetAlertName(alertName);
1886 alertService->CloseAlert(alertName);
1891 nsresult Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) {
1892 if (!aPrincipal) {
1893 return NS_ERROR_FAILURE;
1896 nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, aOrigin);
1897 NS_ENSURE_SUCCESS(rv, rv);
1899 return NS_OK;
1902 bool Notification::RequireInteraction() const { return mRequireInteraction; }
1904 bool Notification::Silent() const { return mSilent; }
1906 void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
1907 aRetval = mVibrate.Clone();
1910 void Notification::GetData(JSContext* aCx,
1911 JS::MutableHandle<JS::Value> aRetval) {
1912 if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
1913 nsresult rv;
1914 RefPtr<nsStructuredCloneContainer> container =
1915 new nsStructuredCloneContainer();
1916 rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
1917 if (NS_WARN_IF(NS_FAILED(rv))) {
1918 aRetval.setNull();
1919 return;
1922 JS::Rooted<JS::Value> data(aCx);
1923 rv = container->DeserializeToJsval(aCx, &data);
1924 if (NS_WARN_IF(NS_FAILED(rv))) {
1925 aRetval.setNull();
1926 return;
1929 if (data.isGCThing()) {
1930 mozilla::HoldJSObjects(this);
1932 mData = data;
1934 if (mData.isNull()) {
1935 aRetval.setNull();
1936 return;
1939 aRetval.set(mData);
1942 void Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
1943 ErrorResult& aRv) {
1944 if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
1945 return;
1947 RefPtr<nsStructuredCloneContainer> dataObjectContainer =
1948 new nsStructuredCloneContainer();
1949 aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
1950 if (NS_WARN_IF(aRv.Failed())) {
1951 return;
1954 aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64);
1955 if (NS_WARN_IF(aRv.Failed())) {
1956 return;
1960 void Notification::InitFromBase64(const nsAString& aData, ErrorResult& aRv) {
1961 if (!mDataAsBase64.IsEmpty() || aData.IsEmpty()) {
1962 return;
1965 // To and fro to ensure it is valid base64.
1966 RefPtr<nsStructuredCloneContainer> container =
1967 new nsStructuredCloneContainer();
1968 aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION);
1969 if (NS_WARN_IF(aRv.Failed())) {
1970 return;
1973 aRv = container->GetDataAsBase64(mDataAsBase64);
1974 if (NS_WARN_IF(aRv.Failed())) {
1975 return;
1979 bool Notification::AddRefObject() {
1980 AssertIsOnTargetThread();
1981 MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerRef, mTaskCount == 0);
1982 MOZ_ASSERT_IF(mWorkerPrivate && mWorkerRef, mTaskCount > 0);
1983 if (mWorkerPrivate && !mWorkerRef) {
1984 if (!CreateWorkerRef()) {
1985 return false;
1988 AddRef();
1989 ++mTaskCount;
1990 return true;
1993 void Notification::ReleaseObject() {
1994 AssertIsOnTargetThread();
1995 MOZ_ASSERT(mTaskCount > 0);
1996 MOZ_ASSERT_IF(mWorkerPrivate, mWorkerRef);
1998 --mTaskCount;
1999 if (mWorkerPrivate && mTaskCount == 0) {
2000 MOZ_ASSERT(mWorkerRef);
2001 mWorkerRef = nullptr;
2003 Release();
2007 * Called from the worker, runs on main thread, blocks worker.
2009 * We can freely access mNotification here because the feature supplied it and
2010 * the Notification owns the feature.
2012 class CloseNotificationRunnable final : public WorkerMainThreadRunnable {
2013 Notification* mNotification;
2014 bool mHadObserver;
2016 public:
2017 explicit CloseNotificationRunnable(Notification* aNotification)
2018 : WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
2019 "Notification :: Close Notification"_ns),
2020 mNotification(aNotification),
2021 mHadObserver(false) {}
2023 bool MainThreadRun() override {
2024 if (mNotification->mObserver) {
2025 // The Notify() take's responsibility of releasing the Notification.
2026 mNotification->mObserver->ForgetNotification();
2027 mNotification->mObserver = nullptr;
2028 mHadObserver = true;
2030 mNotification->CloseInternal();
2031 return true;
2034 bool HadObserver() { return mHadObserver; }
2037 bool Notification::CreateWorkerRef() {
2038 MOZ_ASSERT(mWorkerPrivate);
2039 mWorkerPrivate->AssertIsOnWorkerThread();
2040 MOZ_ASSERT(!mWorkerRef);
2042 RefPtr<Notification> self = this;
2043 mWorkerRef =
2044 StrongWorkerRef::Create(mWorkerPrivate, "Notification", [self]() {
2045 // CloseNotificationRunnable blocks the worker by pushing a sync event
2046 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2047 // the worker can still continue running. One of these is
2048 // ReleaseNotificationControlRunnable that releases the notification,
2049 // invalidating the notification and this feature. We hold this
2050 // reference to keep the notification valid until we are done with it.
2052 // An example of when the control runnable could get dispatched to the
2053 // worker is if a Notification is created and the worker is immediately
2054 // closed, but there is no permission to show it so that the main thread
2055 // immediately drops the NotificationRef. In this case, this function
2056 // blocks on the main thread, but the main thread dispatches the control
2057 // runnable, invalidating mNotification.
2059 // Dispatched to main thread, blocks on closing the Notification.
2060 RefPtr<CloseNotificationRunnable> r =
2061 new CloseNotificationRunnable(self);
2062 ErrorResult rv;
2063 r->Dispatch(Killing, rv);
2064 // XXXbz I'm told throwing and returning false from here is pointless
2065 // (and also that doing sync stuff from here is really weird), so I
2066 // guess we just suppress the exception on rv, if any.
2067 rv.SuppressException();
2069 // Only call ReleaseObject() to match the observer's NotificationRef
2070 // ownership (since CloseNotificationRunnable asked the observer to drop
2071 // the reference to the notification).
2072 if (r->HadObserver()) {
2073 self->ReleaseObject();
2076 // From this point we cannot touch properties of this feature because
2077 // ReleaseObject() may have led to the notification going away and the
2078 // notification owns this feature!
2081 if (NS_WARN_IF(!mWorkerRef)) {
2082 return false;
2085 return true;
2089 * Checks:
2090 * 1) Is aWorker allowed to show a notification for scope?
2091 * 2) Is aWorker an active worker?
2093 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2095 class CheckLoadRunnable final : public WorkerMainThreadRunnable {
2096 nsresult mRv;
2097 nsCString mScope;
2098 ServiceWorkerRegistrationDescriptor mDescriptor;
2100 public:
2101 explicit CheckLoadRunnable(
2102 WorkerPrivate* aWorker, const nsACString& aScope,
2103 const ServiceWorkerRegistrationDescriptor& aDescriptor)
2104 : WorkerMainThreadRunnable(aWorker, "Notification :: Check Load"_ns),
2105 mRv(NS_ERROR_DOM_SECURITY_ERR),
2106 mScope(aScope),
2107 mDescriptor(aDescriptor) {}
2109 bool MainThreadRun() override {
2110 nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
2111 mRv = CheckScope(principal, mScope, mWorkerPrivate->WindowID());
2113 if (NS_FAILED(mRv)) {
2114 return true;
2117 auto activeWorker = mDescriptor.GetActive();
2119 if (!activeWorker ||
2120 activeWorker.ref().Id() != mWorkerPrivate->ServiceWorkerID()) {
2121 mRv = NS_ERROR_NOT_AVAILABLE;
2124 return true;
2127 nsresult Result() { return mRv; }
2130 /* static */
2131 already_AddRefed<Promise> Notification::ShowPersistentNotification(
2132 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
2133 const nsAString& aTitle, const NotificationOptions& aOptions,
2134 const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
2135 MOZ_ASSERT(aGlobal);
2137 // Validate scope.
2138 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2139 // thread. On calls from content, we can be sure the scope is valid since
2140 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2141 // debug only? The problem is that there would be different semantics in
2142 // debug and non-debug builds in such a case.
2143 if (NS_IsMainThread()) {
2144 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
2145 if (NS_WARN_IF(!sop)) {
2146 aRv.Throw(NS_ERROR_UNEXPECTED);
2147 return nullptr;
2150 nsIPrincipal* principal = sop->GetPrincipal();
2151 if (NS_WARN_IF(!principal)) {
2152 aRv.Throw(NS_ERROR_UNEXPECTED);
2153 return nullptr;
2156 uint64_t windowID = 0;
2157 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
2158 if (win) {
2159 windowID = win->WindowID();
2162 aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope), windowID);
2163 if (NS_WARN_IF(aRv.Failed())) {
2164 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2165 return nullptr;
2167 } else {
2168 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
2169 MOZ_ASSERT(worker);
2170 worker->AssertIsOnWorkerThread();
2172 RefPtr<CheckLoadRunnable> loadChecker = new CheckLoadRunnable(
2173 worker, NS_ConvertUTF16toUTF8(aScope), aDescriptor);
2174 loadChecker->Dispatch(Canceling, aRv);
2175 if (aRv.Failed()) {
2176 return nullptr;
2179 if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
2180 if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
2181 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
2182 } else {
2183 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2185 return nullptr;
2189 RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
2190 if (NS_WARN_IF(aRv.Failed())) {
2191 return nullptr;
2194 // We check permission here rather than pass the Promise to NotificationTask
2195 // which leads to uglier code.
2196 NotificationPermission permission = GetPermission(aGlobal, aRv);
2198 // "If permission for notification's origin is not "granted", reject promise
2199 // with a TypeError exception, and terminate these substeps."
2200 if (NS_WARN_IF(aRv.Failed()) ||
2201 permission == NotificationPermission::Denied) {
2202 p->MaybeRejectWithTypeError("Permission to show Notification denied.");
2203 return p.forget();
2206 // "Otherwise, resolve promise with undefined."
2207 // The Notification may still not be shown due to other errors, but the spec
2208 // is not concerned with those.
2209 p->MaybeResolveWithUndefined();
2211 RefPtr<Notification> notification =
2212 CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
2213 if (NS_WARN_IF(aRv.Failed())) {
2214 return nullptr;
2217 return p.forget();
2220 /* static */
2221 already_AddRefed<Notification> Notification::CreateAndShow(
2222 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
2223 const NotificationOptions& aOptions, const nsAString& aScope,
2224 ErrorResult& aRv) {
2225 MOZ_ASSERT(aGlobal);
2227 RefPtr<Notification> notification =
2228 CreateInternal(aGlobal, u""_ns, aTitle, aOptions, aRv);
2229 if (aRv.Failed()) {
2230 return nullptr;
2233 // Make a structured clone of the aOptions.mData object
2234 JS::Rooted<JS::Value> data(aCx, aOptions.mData);
2235 notification->InitFromJSVal(aCx, data, aRv);
2236 if (NS_WARN_IF(aRv.Failed())) {
2237 return nullptr;
2240 notification->SetScope(aScope);
2242 auto ref = MakeUnique<NotificationRef>(notification);
2243 if (NS_WARN_IF(!ref->Initialized())) {
2244 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2245 return nullptr;
2248 // Queue a task to show the notification.
2249 nsCOMPtr<nsIRunnable> showNotificationTask = new NotificationTask(
2250 "Notification::CreateAndShow", std::move(ref), NotificationTask::eShow);
2252 nsresult rv =
2253 notification->DispatchToMainThread(showNotificationTask.forget());
2255 if (NS_WARN_IF(NS_FAILED(rv))) {
2256 notification->DispatchTrustedEvent(u"error"_ns);
2259 return notification.forget();
2262 /* static */
2263 nsresult Notification::RemovePermission(nsIPrincipal* aPrincipal) {
2264 MOZ_ASSERT(XRE_IsParentProcess());
2265 nsCOMPtr<nsIPermissionManager> permissionManager =
2266 mozilla::components::PermissionManager::Service();
2267 if (!permissionManager) {
2268 return NS_ERROR_FAILURE;
2270 permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"_ns);
2271 return NS_OK;
2274 /* static */
2275 nsresult Notification::OpenSettings(nsIPrincipal* aPrincipal) {
2276 MOZ_ASSERT(XRE_IsParentProcess());
2277 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2278 if (!obs) {
2279 return NS_ERROR_FAILURE;
2281 // Notify other observers so they can show settings UI.
2282 obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
2283 return NS_OK;
2286 NS_IMETHODIMP
2287 Notification::Observe(nsISupports* aSubject, const char* aTopic,
2288 const char16_t* aData) {
2289 AssertIsOnMainThread();
2291 if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
2292 !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
2293 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
2294 if (SameCOMIdentity(aSubject, window)) {
2295 nsCOMPtr<nsIObserverService> obs =
2296 mozilla::services::GetObserverService();
2297 if (obs) {
2298 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
2299 obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
2302 CloseInternal();
2306 return NS_OK;
2309 nsresult Notification::DispatchToMainThread(
2310 already_AddRefed<nsIRunnable>&& aRunnable) {
2311 if (mWorkerPrivate) {
2312 return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable));
2314 AssertIsOnMainThread();
2315 if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
2316 if (nsIEventTarget* target = global->EventTargetFor(TaskCategory::Other)) {
2317 return target->Dispatch(std::move(aRunnable),
2318 nsIEventTarget::DISPATCH_NORMAL);
2321 nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
2322 MOZ_ASSERT(mainTarget);
2323 return mainTarget->Dispatch(std::move(aRunnable),
2324 nsIEventTarget::DISPATCH_NORMAL);
2327 } // namespace mozilla::dom