Bug 1854550 - pt 2. Move PHC into memory/build r=glandium
[gecko.git] / dom / notification / Notification.cpp
blobe691b1785d66df34910509b6439cbc9b37d48214
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/Notification.h"
9 #include <utility>
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/Components.h"
13 #include "mozilla/Encoding.h"
14 #include "mozilla/EventStateManager.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/JSONStringWriteFuncs.h"
17 #include "mozilla/OwningNonNull.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/StaticPrefs_dom.h"
20 #include "mozilla/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/RootedDictionary.h"
31 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
32 #include "mozilla/dom/ServiceWorkerManager.h"
33 #include "mozilla/dom/ServiceWorkerUtils.h"
34 #include "mozilla/dom/WorkerRunnable.h"
35 #include "mozilla/dom/WorkerScope.h"
36 #include "Navigator.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 "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::Handle<JS::Value> 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 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
282 // bug 1535398.
283 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
284 AssertIsOnMainThread();
285 if (!mWindow->IsCurrentInnerWindow()) {
286 // Window has been closed, this observer is not valid anymore
287 return NS_OK;
290 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mWindow->GetOuterWindow();
291 nsFocusManager::FocusWindow(outerWindow, CallerType::System);
292 return NS_OK;
296 nsresult CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope,
297 uint64_t aWindowID) {
298 AssertIsOnMainThread();
299 MOZ_ASSERT(aPrincipal);
301 nsCOMPtr<nsIURI> scopeURI;
302 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
303 if (NS_WARN_IF(NS_FAILED(rv))) {
304 return rv;
307 return aPrincipal->CheckMayLoadWithReporting(
308 scopeURI,
309 /* allowIfInheritsPrincipal = */ false, aWindowID);
311 } // anonymous namespace
313 // Subclass that can be directly dispatched to child workers from the main
314 // thread.
315 class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
316 protected:
317 explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate)
318 : MainThreadWorkerRunnable(aWorkerPrivate) {}
320 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
321 aWorkerPrivate->AssertIsOnWorkerThread();
322 aWorkerPrivate->ModifyBusyCountFromWorker(true);
323 // WorkerScope might start dying at the moment. And WorkerRunInternal()
324 // should not be executed once WorkerScope is dying, since
325 // WorkerRunInternal() might access resources which already been freed
326 // during WorkerRef::Notify().
327 if (aWorkerPrivate->GlobalScope() &&
328 !aWorkerPrivate->GlobalScope()->IsDying()) {
329 WorkerRunInternal(aWorkerPrivate);
331 return true;
334 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
335 bool aRunResult) override {
336 aWorkerPrivate->ModifyBusyCountFromWorker(false);
339 virtual void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0;
342 // Overrides dispatch and run handlers so we can directly dispatch from main
343 // thread to child workers.
344 class NotificationEventWorkerRunnable final
345 : public NotificationWorkerRunnable {
346 Notification* mNotification;
347 const nsString mEventName;
349 public:
350 NotificationEventWorkerRunnable(Notification* aNotification,
351 const nsString& aEventName)
352 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
353 mNotification(aNotification),
354 mEventName(aEventName) {}
356 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
357 mNotification->DispatchTrustedEvent(mEventName);
361 class ReleaseNotificationRunnable final : public NotificationWorkerRunnable {
362 Notification* mNotification;
364 public:
365 explicit ReleaseNotificationRunnable(Notification* aNotification)
366 : NotificationWorkerRunnable(aNotification->mWorkerPrivate),
367 mNotification(aNotification) {}
369 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
370 aWorkerPrivate->AssertIsOnWorkerThread();
371 aWorkerPrivate->ModifyBusyCountFromWorker(true);
372 // ReleaseNotificationRunnable is only used in StrongWorkerRef's shutdown
373 // callback. At the moment, it is supposed to executing
374 // mNotification->ReleaseObject() safely even though the corresponding
375 // WorkerScope::IsDying() is true. It is unlike other
376 // NotificationWorkerRunnable.
377 WorkerRunInternal(aWorkerPrivate);
378 return true;
381 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
382 mNotification->ReleaseObject();
385 nsresult Cancel() override {
386 mNotification->ReleaseObject();
387 return NS_OK;
391 // Create one whenever you require ownership of the notification. Use with
392 // UniquePtr<>. See Notification.h for details.
393 class NotificationRef final {
394 friend class WorkerNotificationObserver;
396 private:
397 Notification* mNotification;
398 bool mInited;
400 // Only useful for workers.
401 void Forget() { mNotification = nullptr; }
403 public:
404 explicit NotificationRef(Notification* aNotification)
405 : mNotification(aNotification) {
406 MOZ_ASSERT(mNotification);
407 if (mNotification->mWorkerPrivate) {
408 mNotification->mWorkerPrivate->AssertIsOnWorkerThread();
409 } else {
410 AssertIsOnMainThread();
413 mInited = mNotification->AddRefObject();
416 // This is only required because Gecko runs script in a worker's onclose
417 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
418 // fail. Due to non-standardness and added complications if we decide to
419 // support this, attempts to create a Notification in onclose just throw
420 // exceptions.
421 bool Initialized() { return mInited; }
423 ~NotificationRef() {
424 if (Initialized() && mNotification) {
425 Notification* notification = mNotification;
426 mNotification = nullptr;
427 if (notification->mWorkerPrivate && NS_IsMainThread()) {
428 // Try to pass ownership back to the worker. If the dispatch succeeds we
429 // are guaranteed this runnable will run, and that it will run after
430 // queued event runnables, so event runnables will have a safe pointer
431 // to the Notification.
433 // If the dispatch fails, the worker isn't running anymore and the event
434 // runnables have already run or been canceled. We can use a control
435 // runnable to release the reference.
436 RefPtr<ReleaseNotificationRunnable> r =
437 new ReleaseNotificationRunnable(notification);
439 if (!r->Dispatch()) {
440 RefPtr<ReleaseNotificationControlRunnable> r =
441 new ReleaseNotificationControlRunnable(notification);
442 MOZ_ALWAYS_TRUE(r->Dispatch());
444 } else {
445 notification->AssertIsOnTargetThread();
446 notification->ReleaseObject();
451 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
452 // a rawptr that the NotificationRef can invalidate?
453 Notification* GetNotification() {
454 MOZ_ASSERT(Initialized());
455 return mNotification;
459 class NotificationTask : public Runnable {
460 public:
461 enum NotificationAction { eShow, eClose };
463 NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef,
464 NotificationAction aAction)
465 : Runnable(aName), mNotificationRef(std::move(aRef)), mAction(aAction) {}
467 NS_IMETHOD
468 Run() override;
470 protected:
471 virtual ~NotificationTask() = default;
473 UniquePtr<NotificationRef> mNotificationRef;
474 NotificationAction mAction;
477 uint32_t Notification::sCount = 0;
479 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
480 ContentPermissionRequestBase, mCallback)
481 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
482 ContentPermissionRequestBase)
483 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
484 ContentPermissionRequestBase)
486 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
487 NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
488 nsINamed)
490 NS_IMETHODIMP
491 NotificationPermissionRequest::Run() {
492 bool isSystem = mPrincipal->IsSystemPrincipal();
493 bool blocked = false;
494 if (isSystem) {
495 mPermission = NotificationPermission::Granted;
496 } else if (mPrincipal->GetPrivateBrowsingId() != 0) {
497 mPermission = NotificationPermission::Denied;
498 blocked = true;
499 } else {
500 // File are automatically granted permission.
502 if (mPrincipal->SchemeIs("file")) {
503 mPermission = NotificationPermission::Granted;
504 } else if (!mWindow->IsSecureContext()) {
505 mPermission = NotificationPermission::Denied;
506 blocked = true;
507 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
508 if (doc) {
509 nsContentUtils::ReportToConsole(
510 nsIScriptError::errorFlag, "DOM"_ns, doc,
511 nsContentUtils::eDOM_PROPERTIES,
512 "NotificationsInsecureRequestIsForbidden");
517 // We can't call ShowPrompt() directly here since our logic for determining
518 // whether to display a prompt depends on the checks above as well as the
519 // result of CheckPromptPrefs(). So we have to manually check the prompt
520 // prefs and decide what to do based on that.
521 PromptResult pr = CheckPromptPrefs();
522 switch (pr) {
523 case PromptResult::Granted:
524 mPermission = NotificationPermission::Granted;
525 break;
526 case PromptResult::Denied:
527 mPermission = NotificationPermission::Denied;
528 break;
529 default:
530 // ignore
531 break;
534 if (!mHasValidTransientUserGestureActivation &&
535 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
536 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
537 if (doc) {
538 doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
542 // Check this after checking the prompt prefs to make sure this pref overrides
543 // those. We rely on this for testing purposes.
544 if (!isSystem && !blocked &&
545 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
546 !mPrincipal->Subsumes(mTopLevelPrincipal)) {
547 mPermission = NotificationPermission::Denied;
548 blocked = true;
549 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
550 if (doc) {
551 nsContentUtils::ReportToConsole(
552 nsIScriptError::errorFlag, "DOM"_ns, doc,
553 nsContentUtils::eDOM_PROPERTIES,
554 "NotificationsCrossOriginIframeRequestIsForbidden");
558 if (mPermission != NotificationPermission::Default) {
559 return DispatchResolvePromise();
562 return nsContentPermissionUtils::AskPermission(this, mWindow);
565 NS_IMETHODIMP
566 NotificationPermissionRequest::Cancel() {
567 // `Cancel` is called if the user denied permission or dismissed the
568 // permission request. To distinguish between the two, we set the
569 // permission to "default" and query the permission manager in
570 // `ResolvePromise`.
571 mPermission = NotificationPermission::Default;
572 return DispatchResolvePromise();
575 NS_IMETHODIMP
576 NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
577 MOZ_ASSERT(aChoices.isUndefined());
579 mPermission = NotificationPermission::Granted;
580 return DispatchResolvePromise();
583 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
584 nsCOMPtr<nsIRunnable> resolver =
585 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
586 this, &NotificationPermissionRequest::ResolvePromise);
587 return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget());
590 nsresult NotificationPermissionRequest::ResolvePromise() {
591 nsresult rv = NS_OK;
592 // This will still be "default" if the user dismissed the doorhanger,
593 // or "denied" otherwise.
594 if (mPermission == NotificationPermission::Default) {
595 // When the front-end has decided to deny the permission request
596 // automatically and we are not handling user input, then log a
597 // warning in the current document that this happened because
598 // Notifications require a user gesture.
599 if (!mHasValidTransientUserGestureActivation &&
600 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
601 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
602 if (doc) {
603 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
604 doc, nsContentUtils::eDOM_PROPERTIES,
605 "NotificationsRequireUserGesture");
609 mPermission = Notification::TestPermission(mPrincipal);
611 if (mCallback) {
612 ErrorResult error;
613 RefPtr<NotificationPermissionCallback> callback(mCallback);
614 callback->Call(mPermission, error);
615 rv = error.StealNSResult();
617 mPromise->MaybeResolve(mPermission);
618 return rv;
621 // Observer that the alert service calls to do common tasks and/or dispatch to
622 // the specific observer for the context e.g. main thread, worker, or service
623 // worker.
624 class NotificationObserver final : public nsIObserver {
625 public:
626 nsCOMPtr<nsIObserver> mObserver;
627 nsCOMPtr<nsIPrincipal> mPrincipal;
628 bool mInPrivateBrowsing;
629 NS_DECL_ISUPPORTS
630 NS_DECL_NSIOBSERVER
632 NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
633 bool aInPrivateBrowsing)
634 : mObserver(aObserver),
635 mPrincipal(aPrincipal),
636 mInPrivateBrowsing(aInPrivateBrowsing) {
637 AssertIsOnMainThread();
638 MOZ_ASSERT(mObserver);
639 MOZ_ASSERT(mPrincipal);
642 protected:
643 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
645 nsresult AdjustPushQuota(const char* aTopic);
648 NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
650 class MainThreadNotificationObserver : public nsIObserver {
651 public:
652 UniquePtr<NotificationRef> mNotificationRef;
653 NS_DECL_ISUPPORTS
654 NS_DECL_NSIOBSERVER
656 explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
657 : mNotificationRef(std::move(aRef)) {
658 AssertIsOnMainThread();
661 protected:
662 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
665 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
667 NS_IMETHODIMP
668 NotificationTask::Run() {
669 AssertIsOnMainThread();
671 // Get a pointer to notification before the notification takes ownership of
672 // the ref (it owns itself temporarily, with ShowInternal() and
673 // CloseInternal() passing on the ownership appropriately.)
674 Notification* notif = mNotificationRef->GetNotification();
675 notif->mTempRef.swap(mNotificationRef);
676 if (mAction == eShow) {
677 notif->ShowInternal();
678 } else if (mAction == eClose) {
679 notif->CloseInternal();
680 } else {
681 MOZ_CRASH("Invalid action");
684 MOZ_ASSERT(!mNotificationRef);
685 return NS_OK;
688 // static
689 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
690 return StaticPrefs::dom_webnotifications_enabled();
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(mPrincipal);
1093 return NS_OK;
1094 } else if (!strcmp("alertsettingscallback", aTopic)) {
1095 if (XRE_IsParentProcess()) {
1096 return Notification::OpenSettings(mPrincipal);
1098 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1099 // parent process.
1100 ContentChild::GetSingleton()->SendOpenNotificationSettings(mPrincipal);
1101 return NS_OK;
1102 } else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) {
1103 Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
1106 return mObserver->Observe(aSubject, aTopic, aData);
1109 nsresult NotificationObserver::AdjustPushQuota(const char* aTopic) {
1110 nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
1111 do_GetService("@mozilla.org/push/Service;1");
1112 if (!pushQuotaManager) {
1113 return NS_ERROR_FAILURE;
1116 nsAutoCString origin;
1117 nsresult rv = mPrincipal->GetOrigin(origin);
1118 if (NS_FAILED(rv)) {
1119 return rv;
1122 if (!strcmp("alertshow", aTopic)) {
1123 return pushQuotaManager->NotificationForOriginShown(origin.get());
1125 return pushQuotaManager->NotificationForOriginClosed(origin.get());
1128 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
1129 // bug 1539845.
1130 MOZ_CAN_RUN_SCRIPT_BOUNDARY 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 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->GetOuterWindow();
1148 nsFocusManager::FocusWindow(outerWindow, CallerType::System);
1150 } else if (!strcmp("alertfinished", aTopic)) {
1151 notification->UnpersistNotification();
1152 notification->mIsClosed = true;
1153 notification->DispatchTrustedEvent(u"close"_ns);
1154 } else if (!strcmp("alertshow", aTopic)) {
1155 notification->DispatchTrustedEvent(u"show"_ns);
1157 return NS_OK;
1160 NS_IMETHODIMP
1161 WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
1162 const char16_t* aData) {
1163 AssertIsOnMainThread();
1164 MOZ_ASSERT(mNotificationRef);
1165 // For an explanation of why it is OK to pass this rawptr to the event
1166 // runnables, see the Notification class comment.
1167 Notification* notification = mNotificationRef->GetNotification();
1168 // We can't assert notification here since the feature could've unset it.
1169 if (NS_WARN_IF(!notification)) {
1170 return NS_ERROR_FAILURE;
1173 MOZ_ASSERT(notification->mWorkerPrivate);
1175 RefPtr<WorkerRunnable> r;
1176 if (!strcmp("alertclickcallback", aTopic)) {
1177 nsPIDOMWindowInner* window = nullptr;
1178 if (!notification->mWorkerPrivate->IsServiceWorker()) {
1179 WorkerPrivate* top = notification->mWorkerPrivate;
1180 while (top->GetParent()) {
1181 top = top->GetParent();
1184 window = top->GetWindow();
1185 if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
1186 // Window has been closed, this observer is not valid anymore
1187 return NS_ERROR_FAILURE;
1191 // Instead of bothering with adding features and other worker lifecycle
1192 // management, we simply hold strongrefs to the window and document.
1193 nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
1194 new nsMainThreadPtrHolder<nsPIDOMWindowInner>(
1195 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window));
1197 r = new NotificationClickWorkerRunnable(notification, windowHandle);
1198 } else if (!strcmp("alertfinished", aTopic)) {
1199 notification->UnpersistNotification();
1200 notification->mIsClosed = true;
1201 r = new NotificationEventWorkerRunnable(notification, u"close"_ns);
1202 } else if (!strcmp("alertshow", aTopic)) {
1203 r = new NotificationEventWorkerRunnable(notification, u"show"_ns);
1206 MOZ_ASSERT(r);
1207 if (!r->Dispatch()) {
1208 NS_WARNING("Could not dispatch event to worker notification");
1210 return NS_OK;
1213 NS_IMETHODIMP
1214 ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
1215 const char* aTopic,
1216 const char16_t* aData) {
1217 AssertIsOnMainThread();
1219 nsAutoCString originSuffix;
1220 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
1221 if (NS_WARN_IF(NS_FAILED(rv))) {
1222 return rv;
1225 if (!strcmp("alertclickcallback", aTopic)) {
1226 if (XRE_IsParentProcess()) {
1227 nsCOMPtr<nsIServiceWorkerManager> swm =
1228 mozilla::components::ServiceWorkerManager::Service();
1229 if (NS_WARN_IF(!swm)) {
1230 return NS_ERROR_FAILURE;
1233 rv = swm->SendNotificationClickEvent(
1234 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1235 mBody, mTag, mIcon, mData, mBehavior);
1236 Unused << NS_WARN_IF(NS_FAILED(rv));
1237 } else {
1238 auto* cc = ContentChild::GetSingleton();
1239 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1240 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1241 mData, mBehavior);
1242 Unused << cc->SendNotificationEvent(u"click"_ns, data);
1244 return NS_OK;
1247 if (!strcmp("alertfinished", aTopic)) {
1248 nsString origin;
1249 nsresult rv = Notification::GetOrigin(mPrincipal, origin);
1250 if (NS_WARN_IF(NS_FAILED(rv))) {
1251 return rv;
1254 // Remove closed or dismissed persistent notifications.
1255 nsCOMPtr<nsINotificationStorage> notificationStorage =
1256 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
1257 if (notificationStorage) {
1258 notificationStorage->Delete(origin, mID);
1261 if (XRE_IsParentProcess()) {
1262 nsCOMPtr<nsIServiceWorkerManager> swm =
1263 mozilla::components::ServiceWorkerManager::Service();
1264 if (NS_WARN_IF(!swm)) {
1265 return NS_ERROR_FAILURE;
1268 rv = swm->SendNotificationCloseEvent(
1269 originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
1270 mBody, mTag, mIcon, mData, mBehavior);
1271 Unused << NS_WARN_IF(NS_FAILED(rv));
1272 } else {
1273 auto* cc = ContentChild::GetSingleton();
1274 NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
1275 mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
1276 mData, mBehavior);
1277 Unused << cc->SendNotificationEvent(u"close"_ns, data);
1279 return NS_OK;
1282 return NS_OK;
1285 bool Notification::IsInPrivateBrowsing() {
1286 AssertIsOnMainThread();
1288 Document* doc = nullptr;
1290 if (mWorkerPrivate) {
1291 doc = mWorkerPrivate->GetDocument();
1292 } else if (GetOwner()) {
1293 doc = GetOwner()->GetExtantDoc();
1296 if (doc) {
1297 nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
1298 return loadContext && loadContext->UsePrivateBrowsing();
1301 if (mWorkerPrivate) {
1302 // Not all workers may have a document, but with Bug 1107516 fixed, they
1303 // should all have a loadcontext.
1304 nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
1305 nsCOMPtr<nsILoadContext> loadContext;
1306 NS_QueryNotificationCallbacks(nullptr, loadGroup,
1307 NS_GET_IID(nsILoadContext),
1308 getter_AddRefs(loadContext));
1309 return loadContext && loadContext->UsePrivateBrowsing();
1312 // XXXnsm Should this default to true?
1313 return false;
1316 void Notification::ShowInternal() {
1317 AssertIsOnMainThread();
1318 MOZ_ASSERT(mTempRef,
1319 "Notification should take ownership of itself before"
1320 "calling ShowInternal!");
1321 // A notification can only have one observer and one call to ShowInternal.
1322 MOZ_ASSERT(!mObserver);
1324 // Transfer ownership to local scope so we can either release it at the end
1325 // of this function or transfer it to the observer.
1326 UniquePtr<NotificationRef> ownership;
1327 std::swap(ownership, mTempRef);
1328 MOZ_ASSERT(ownership->GetNotification() == this);
1330 nsresult rv = PersistNotification();
1331 if (NS_FAILED(rv)) {
1332 NS_WARNING("Could not persist Notification");
1335 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1337 ErrorResult result;
1338 NotificationPermission permission = NotificationPermission::Denied;
1339 if (mWorkerPrivate) {
1340 permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
1341 } else {
1342 permission = GetPermissionInternal(GetOwner(), result);
1344 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1345 MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
1346 result.SuppressException();
1347 if (permission != NotificationPermission::Granted || !alertService) {
1348 if (mWorkerPrivate) {
1349 RefPtr<NotificationEventWorkerRunnable> r =
1350 new NotificationEventWorkerRunnable(this, u"error"_ns);
1351 if (!r->Dispatch()) {
1352 NS_WARNING("Could not dispatch event to worker notification");
1354 } else {
1355 DispatchTrustedEvent(u"error"_ns);
1357 return;
1360 nsAutoString iconUrl;
1361 nsAutoString soundUrl;
1362 ResolveIconAndSoundURL(iconUrl, soundUrl);
1364 bool isPersistent = false;
1365 nsCOMPtr<nsIObserver> observer;
1366 if (mScope.IsEmpty()) {
1367 // Ownership passed to observer.
1368 if (mWorkerPrivate) {
1369 // Scope better be set on ServiceWorker initiated requests.
1370 MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
1371 // Keep a pointer so that the feature can tell the observer not to release
1372 // the notification.
1373 mObserver = new WorkerNotificationObserver(std::move(ownership));
1374 observer = mObserver;
1375 } else {
1376 observer = new MainThreadNotificationObserver(std::move(ownership));
1378 } else {
1379 isPersistent = true;
1380 // This observer does not care about the Notification. It will be released
1381 // at the end of this function.
1383 // The observer is wholly owned by the NotificationObserver passed to the
1384 // alert service.
1385 nsAutoString behavior;
1386 if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
1387 behavior.Truncate();
1389 observer = new ServiceWorkerNotificationObserver(
1390 mScope, GetPrincipal(), mID, mTitle, DirectionToString(mDir), mLang,
1391 mBody, mTag, iconUrl, mDataAsBase64, behavior);
1393 MOZ_ASSERT(observer);
1394 nsCOMPtr<nsIObserver> alertObserver =
1395 new NotificationObserver(observer, GetPrincipal(), IsInPrivateBrowsing());
1397 // In the case of IPC, the parent process uses the cookie to map to
1398 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1399 nsString uniqueCookie = u"notification:"_ns;
1400 uniqueCookie.AppendInt(sCount++);
1401 bool inPrivateBrowsing = IsInPrivateBrowsing();
1403 bool requireInteraction = mRequireInteraction;
1404 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1405 requireInteraction = false;
1408 nsAutoString alertName;
1409 GetAlertName(alertName);
1410 nsCOMPtr<nsIAlertNotification> alert =
1411 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
1412 NS_ENSURE_TRUE_VOID(alert);
1413 nsIPrincipal* principal = GetPrincipal();
1414 rv =
1415 alert->Init(alertName, iconUrl, mTitle, mBody, true, uniqueCookie,
1416 DirectionToString(mDir), mLang, mDataAsBase64, GetPrincipal(),
1417 inPrivateBrowsing, requireInteraction, mSilent, mVibrate);
1418 NS_ENSURE_SUCCESS_VOID(rv);
1420 if (isPersistent) {
1421 JSONStringWriteFunc<nsAutoCString> persistentData;
1422 JSONWriter w(persistentData);
1423 w.Start();
1425 nsAutoString origin;
1426 Notification::GetOrigin(principal, origin);
1427 w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin));
1429 w.StringProperty("id", NS_ConvertUTF16toUTF8(mID));
1431 nsAutoCString originSuffix;
1432 principal->GetOriginSuffix(originSuffix);
1433 w.StringProperty("originSuffix", originSuffix);
1435 w.End();
1437 alertService->ShowPersistentNotification(
1438 NS_ConvertUTF8toUTF16(persistentData.StringCRef()), alert,
1439 alertObserver);
1440 } else {
1441 alertService->ShowAlert(alert, alertObserver);
1445 /* static */
1446 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
1447 JSObject* /* unused */) {
1448 // requestPermission() is not allowed on workers. The calling page should ask
1449 // for permission on the worker's behalf. This is to prevent 'which window
1450 // should show the browser pop-up'. See discussion:
1451 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1452 return NS_IsMainThread();
1455 // static
1456 already_AddRefed<Promise> Notification::RequestPermission(
1457 const GlobalObject& aGlobal,
1458 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
1459 ErrorResult& aRv) {
1460 AssertIsOnMainThread();
1462 // Get principal from global to make permission request for notifications.
1463 nsCOMPtr<nsPIDOMWindowInner> window =
1464 do_QueryInterface(aGlobal.GetAsSupports());
1465 nsCOMPtr<nsIScriptObjectPrincipal> sop =
1466 do_QueryInterface(aGlobal.GetAsSupports());
1467 if (!sop || !window) {
1468 aRv.Throw(NS_ERROR_UNEXPECTED);
1469 return nullptr;
1472 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1473 if (!principal) {
1474 aRv.Throw(NS_ERROR_UNEXPECTED);
1475 return nullptr;
1478 RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
1479 if (aRv.Failed()) {
1480 return nullptr;
1482 NotificationPermissionCallback* permissionCallback = nullptr;
1483 if (aCallback.WasPassed()) {
1484 permissionCallback = &aCallback.Value();
1486 nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
1487 principal, window, promise, permissionCallback);
1489 window->AsGlobal()->Dispatch(request.forget());
1491 return promise.forget();
1494 // static
1495 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
1496 ErrorResult& aRv) {
1497 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
1498 return GetPermission(global, aRv);
1501 // static
1502 NotificationPermission Notification::GetPermission(nsIGlobalObject* aGlobal,
1503 ErrorResult& aRv) {
1504 if (NS_IsMainThread()) {
1505 return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aRv);
1506 } else {
1507 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1508 MOZ_ASSERT(worker);
1509 RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(worker);
1510 r->Dispatch(Canceling, aRv);
1511 if (aRv.Failed()) {
1512 return NotificationPermission::Denied;
1515 return r->GetPermission();
1519 /* static */
1520 NotificationPermission Notification::GetPermissionInternal(
1521 nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
1522 // Get principal from global to check permission for notifications.
1523 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
1524 if (!sop) {
1525 aRv.Throw(NS_ERROR_UNEXPECTED);
1526 return NotificationPermission::Denied;
1529 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
1530 if (!principal) {
1531 aRv.Throw(NS_ERROR_UNEXPECTED);
1532 return NotificationPermission::Denied;
1535 if (principal->GetPrivateBrowsingId() != 0) {
1536 return NotificationPermission::Denied;
1538 // Disallow showing notification if our origin is not the same origin as the
1539 // toplevel one, see https://github.com/whatwg/notifications/issues/177.
1540 if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) {
1541 nsCOMPtr<nsIScriptObjectPrincipal> topSop =
1542 do_QueryInterface(aWindow->GetBrowsingContext()->Top()->GetDOMWindow());
1543 nsIPrincipal* topPrincipal = topSop ? topSop->GetPrincipal() : nullptr;
1544 if (!topPrincipal || !principal->Subsumes(topPrincipal)) {
1545 return NotificationPermission::Denied;
1549 return GetPermissionInternal(principal, aRv);
1552 /* static */
1553 NotificationPermission Notification::GetPermissionInternal(
1554 nsIPrincipal* aPrincipal, ErrorResult& aRv) {
1555 AssertIsOnMainThread();
1556 MOZ_ASSERT(aPrincipal);
1558 if (aPrincipal->IsSystemPrincipal()) {
1559 return NotificationPermission::Granted;
1560 } else {
1561 // Allow files to show notifications by default.
1562 if (aPrincipal->SchemeIs("file")) {
1563 return NotificationPermission::Granted;
1567 // We also allow notifications is they are pref'ed on.
1568 if (Preferences::GetBool("notification.prompt.testing", false)) {
1569 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1570 return NotificationPermission::Granted;
1571 } else {
1572 return NotificationPermission::Denied;
1576 return TestPermission(aPrincipal);
1579 /* static */
1580 NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) {
1581 AssertIsOnMainThread();
1583 uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
1585 nsCOMPtr<nsIPermissionManager> permissionManager =
1586 components::PermissionManager::Service();
1587 if (!permissionManager) {
1588 return NotificationPermission::Default;
1591 permissionManager->TestExactPermissionFromPrincipal(
1592 aPrincipal, "desktop-notification"_ns, &permission);
1594 // Convert the result to one of the enum types.
1595 switch (permission) {
1596 case nsIPermissionManager::ALLOW_ACTION:
1597 return NotificationPermission::Granted;
1598 case nsIPermissionManager::DENY_ACTION:
1599 return NotificationPermission::Denied;
1600 default:
1601 return NotificationPermission::Default;
1605 nsresult Notification::ResolveIconAndSoundURL(nsString& iconUrl,
1606 nsString& soundUrl) {
1607 AssertIsOnMainThread();
1608 nsresult rv = NS_OK;
1610 nsIURI* baseUri = nullptr;
1612 // XXXnsm If I understand correctly, the character encoding for resolving
1613 // URIs in new specs is dictated by the URL spec, which states that unless
1614 // the URL parser is passed an override encoding, the charset to be used is
1615 // UTF-8. The new Notification icon/sound specification just says to use the
1616 // Fetch API, where the Request constructor defers to URL parsing specifying
1617 // the API base URL and no override encoding. So we've to use UTF-8 on
1618 // workers, but for backwards compat keeping it document charset on main
1619 // thread.
1620 auto encoding = UTF_8_ENCODING;
1622 if (mWorkerPrivate) {
1623 baseUri = mWorkerPrivate->GetBaseURI();
1624 } else {
1625 Document* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1626 if (doc) {
1627 baseUri = doc->GetBaseURI();
1628 encoding = doc->GetDocumentCharacterSet();
1629 } else {
1630 NS_WARNING("No document found for main thread notification!");
1631 return NS_ERROR_FAILURE;
1635 if (baseUri) {
1636 if (mIconUrl.Length() > 0) {
1637 nsCOMPtr<nsIURI> srcUri;
1638 rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, encoding, baseUri);
1639 if (NS_SUCCEEDED(rv)) {
1640 nsAutoCString src;
1641 srcUri->GetSpec(src);
1642 CopyUTF8toUTF16(src, iconUrl);
1645 if (mBehavior.mSoundFile.Length() > 0) {
1646 nsCOMPtr<nsIURI> srcUri;
1647 rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding,
1648 baseUri);
1649 if (NS_SUCCEEDED(rv)) {
1650 nsAutoCString src;
1651 srcUri->GetSpec(src);
1652 CopyUTF8toUTF16(src, soundUrl);
1657 return rv;
1660 already_AddRefed<Promise> Notification::Get(
1661 nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter,
1662 const nsAString& aScope, ErrorResult& aRv) {
1663 AssertIsOnMainThread();
1664 MOZ_ASSERT(aWindow);
1666 nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
1667 if (!doc) {
1668 aRv.Throw(NS_ERROR_UNEXPECTED);
1669 return nullptr;
1672 nsString origin;
1673 aRv = GetOrigin(doc->NodePrincipal(), origin);
1674 if (aRv.Failed()) {
1675 return nullptr;
1678 RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
1679 if (aRv.Failed()) {
1680 return nullptr;
1683 nsCOMPtr<nsINotificationStorageCallback> callback =
1684 new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
1686 RefPtr<NotificationGetRunnable> r =
1687 new NotificationGetRunnable(origin, aFilter.mTag, callback);
1689 aRv = aWindow->AsGlobal()->Dispatch(r.forget());
1690 if (NS_WARN_IF(aRv.Failed())) {
1691 return nullptr;
1694 return promise.forget();
1697 class WorkerGetResultRunnable final : public NotificationWorkerRunnable {
1698 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1699 const nsTArray<NotificationStrings> mStrings;
1701 public:
1702 WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
1703 PromiseWorkerProxy* aPromiseProxy,
1704 nsTArray<NotificationStrings>&& aStrings)
1705 : NotificationWorkerRunnable(aWorkerPrivate),
1706 mPromiseProxy(aPromiseProxy),
1707 mStrings(std::move(aStrings)) {}
1709 void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
1710 RefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise();
1712 ErrorResult result;
1713 AutoTArray<RefPtr<Notification>, 5> notifications;
1714 for (uint32_t i = 0; i < mStrings.Length(); ++i) {
1715 RefPtr<Notification> n = Notification::ConstructFromFields(
1716 aWorkerPrivate->GlobalScope(), mStrings[i].mID, mStrings[i].mTitle,
1717 mStrings[i].mDir, mStrings[i].mLang, mStrings[i].mBody,
1718 mStrings[i].mTag, mStrings[i].mIcon, mStrings[i].mData,
1719 /* mStrings[i].mBehavior, not
1720 * supported */
1721 mStrings[i].mServiceWorkerRegistrationScope, result);
1723 n->SetStoredState(true);
1724 Unused << NS_WARN_IF(result.Failed());
1725 if (!result.Failed()) {
1726 notifications.AppendElement(n.forget());
1730 workerPromise->MaybeResolve(notifications);
1731 mPromiseProxy->CleanUp();
1735 class WorkerGetCallback final : public ScopeCheckingGetCallback {
1736 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1738 public:
1739 NS_DECL_ISUPPORTS
1741 WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
1742 : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) {
1743 AssertIsOnMainThread();
1744 MOZ_ASSERT(aProxy);
1747 NS_IMETHOD Done() final {
1748 AssertIsOnMainThread();
1749 MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
1751 RefPtr<PromiseWorkerProxy> proxy = std::move(mPromiseProxy);
1752 MutexAutoLock lock(proxy->Lock());
1753 if (proxy->CleanedUp()) {
1754 return NS_OK;
1757 RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
1758 proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
1760 r->Dispatch();
1761 return NS_OK;
1764 private:
1765 ~WorkerGetCallback() = default;
1768 NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
1770 class WorkerGetRunnable final : public Runnable {
1771 RefPtr<PromiseWorkerProxy> mPromiseProxy;
1772 const nsString mTag;
1773 const nsString mScope;
1775 public:
1776 WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag,
1777 const nsAString& aScope)
1778 : Runnable("WorkerGetRunnable"),
1779 mPromiseProxy(aProxy),
1780 mTag(aTag),
1781 mScope(aScope) {
1782 MOZ_ASSERT(mPromiseProxy);
1785 NS_IMETHOD
1786 Run() override {
1787 AssertIsOnMainThread();
1788 nsCOMPtr<nsINotificationStorageCallback> callback =
1789 new WorkerGetCallback(mPromiseProxy, mScope);
1791 nsresult rv;
1792 nsCOMPtr<nsINotificationStorage> notificationStorage =
1793 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
1794 if (NS_WARN_IF(NS_FAILED(rv))) {
1795 callback->Done();
1796 return rv;
1799 MutexAutoLock lock(mPromiseProxy->Lock());
1800 if (mPromiseProxy->CleanedUp()) {
1801 return NS_OK;
1804 nsString origin;
1805 rv = Notification::GetOrigin(
1806 mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
1807 if (NS_WARN_IF(NS_FAILED(rv))) {
1808 callback->Done();
1809 return rv;
1812 rv = notificationStorage->Get(origin, mTag, callback);
1813 if (NS_WARN_IF(NS_FAILED(rv))) {
1814 callback->Done();
1815 return rv;
1818 return NS_OK;
1821 private:
1822 ~WorkerGetRunnable() = default;
1825 // static
1826 already_AddRefed<Promise> Notification::WorkerGet(
1827 WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
1828 const nsAString& aScope, ErrorResult& aRv) {
1829 MOZ_ASSERT(aWorkerPrivate);
1830 aWorkerPrivate->AssertIsOnWorkerThread();
1831 RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
1832 if (NS_WARN_IF(aRv.Failed())) {
1833 return nullptr;
1836 RefPtr<PromiseWorkerProxy> proxy =
1837 PromiseWorkerProxy::Create(aWorkerPrivate, p);
1838 if (!proxy) {
1839 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
1840 return nullptr;
1843 RefPtr<WorkerGetRunnable> r =
1844 new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
1845 // Since this is called from script via
1846 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1847 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
1848 return p.forget();
1851 JSObject* Notification::WrapObject(JSContext* aCx,
1852 JS::Handle<JSObject*> aGivenProto) {
1853 return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
1856 void Notification::Close() {
1857 AssertIsOnTargetThread();
1858 auto ref = MakeUnique<NotificationRef>(this);
1859 if (!ref->Initialized()) {
1860 return;
1863 nsCOMPtr<nsIRunnable> closeNotificationTask = new NotificationTask(
1864 "Notification::Close", std::move(ref), NotificationTask::eClose);
1865 nsresult rv = DispatchToMainThread(closeNotificationTask.forget());
1867 if (NS_FAILED(rv)) {
1868 DispatchTrustedEvent(u"error"_ns);
1869 // If dispatch fails, NotificationTask will release the ref when it goes
1870 // out of scope at the end of this function.
1874 void Notification::CloseInternal(bool aContextClosed) {
1875 AssertIsOnMainThread();
1876 // Transfer ownership (if any) to local scope so we can release it at the end
1877 // of this function. This is relevant when the call is from
1878 // NotificationTask::Run().
1879 UniquePtr<NotificationRef> ownership;
1880 std::swap(ownership, mTempRef);
1882 SetAlertName();
1883 UnpersistNotification();
1884 if (!mIsClosed) {
1885 nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
1886 if (alertService) {
1887 nsAutoString alertName;
1888 GetAlertName(alertName);
1889 alertService->CloseAlert(alertName, aContextClosed);
1894 nsresult Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) {
1895 if (!aPrincipal) {
1896 return NS_ERROR_FAILURE;
1899 nsresult rv =
1900 nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, aOrigin);
1901 NS_ENSURE_SUCCESS(rv, rv);
1903 return NS_OK;
1906 bool Notification::RequireInteraction() const { return mRequireInteraction; }
1908 bool Notification::Silent() const { return mSilent; }
1910 void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
1911 aRetval = mVibrate.Clone();
1914 void Notification::GetData(JSContext* aCx,
1915 JS::MutableHandle<JS::Value> aRetval) {
1916 if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
1917 nsresult rv;
1918 RefPtr<nsStructuredCloneContainer> container =
1919 new nsStructuredCloneContainer();
1920 rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
1921 if (NS_WARN_IF(NS_FAILED(rv))) {
1922 aRetval.setNull();
1923 return;
1926 JS::Rooted<JS::Value> data(aCx);
1927 rv = container->DeserializeToJsval(aCx, &data);
1928 if (NS_WARN_IF(NS_FAILED(rv))) {
1929 aRetval.setNull();
1930 return;
1933 if (data.isGCThing()) {
1934 mozilla::HoldJSObjects(this);
1936 mData = data;
1938 if (mData.isNull()) {
1939 aRetval.setNull();
1940 return;
1943 aRetval.set(mData);
1946 void Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
1947 ErrorResult& aRv) {
1948 if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
1949 return;
1951 RefPtr<nsStructuredCloneContainer> dataObjectContainer =
1952 new nsStructuredCloneContainer();
1953 aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
1954 if (NS_WARN_IF(aRv.Failed())) {
1955 return;
1958 aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64);
1959 if (NS_WARN_IF(aRv.Failed())) {
1960 return;
1964 void Notification::InitFromBase64(const nsAString& aData, ErrorResult& aRv) {
1965 if (!mDataAsBase64.IsEmpty() || aData.IsEmpty()) {
1966 return;
1969 // To and fro to ensure it is valid base64.
1970 RefPtr<nsStructuredCloneContainer> container =
1971 new nsStructuredCloneContainer();
1972 aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION);
1973 if (NS_WARN_IF(aRv.Failed())) {
1974 return;
1977 aRv = container->GetDataAsBase64(mDataAsBase64);
1978 if (NS_WARN_IF(aRv.Failed())) {
1979 return;
1983 bool Notification::AddRefObject() {
1984 AssertIsOnTargetThread();
1985 MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerRef, mTaskCount == 0);
1986 MOZ_ASSERT_IF(mWorkerPrivate && mWorkerRef, mTaskCount > 0);
1987 if (mWorkerPrivate && !mWorkerRef) {
1988 if (!CreateWorkerRef()) {
1989 return false;
1992 AddRef();
1993 ++mTaskCount;
1994 return true;
1997 void Notification::ReleaseObject() {
1998 AssertIsOnTargetThread();
1999 MOZ_ASSERT(mTaskCount > 0);
2000 MOZ_ASSERT_IF(mWorkerPrivate, mWorkerRef);
2002 --mTaskCount;
2003 if (mWorkerPrivate && mTaskCount == 0) {
2004 MOZ_ASSERT(mWorkerRef);
2005 mWorkerRef = nullptr;
2007 Release();
2011 * Called from the worker, runs on main thread, blocks worker.
2013 * We can freely access mNotification here because the feature supplied it and
2014 * the Notification owns the feature.
2016 class CloseNotificationRunnable final : public WorkerMainThreadRunnable {
2017 Notification* mNotification;
2018 bool mHadObserver;
2020 public:
2021 explicit CloseNotificationRunnable(Notification* aNotification)
2022 : WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
2023 "Notification :: Close Notification"_ns),
2024 mNotification(aNotification),
2025 mHadObserver(false) {}
2027 bool MainThreadRun() override {
2028 if (mNotification->mObserver) {
2029 // The Notify() take's responsibility of releasing the Notification.
2030 mNotification->mObserver->ForgetNotification();
2031 mNotification->mObserver = nullptr;
2032 mHadObserver = true;
2034 mNotification->CloseInternal();
2035 return true;
2038 bool HadObserver() { return mHadObserver; }
2041 bool Notification::CreateWorkerRef() {
2042 MOZ_ASSERT(mWorkerPrivate);
2043 mWorkerPrivate->AssertIsOnWorkerThread();
2044 MOZ_ASSERT(!mWorkerRef);
2046 RefPtr<Notification> self = this;
2047 mWorkerRef =
2048 StrongWorkerRef::Create(mWorkerPrivate, "Notification", [self]() {
2049 // CloseNotificationRunnable blocks the worker by pushing a sync event
2050 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2051 // the worker can still continue running. One of these is
2052 // ReleaseNotificationControlRunnable that releases the notification,
2053 // invalidating the notification and this feature. We hold this
2054 // reference to keep the notification valid until we are done with it.
2056 // An example of when the control runnable could get dispatched to the
2057 // worker is if a Notification is created and the worker is immediately
2058 // closed, but there is no permission to show it so that the main thread
2059 // immediately drops the NotificationRef. In this case, this function
2060 // blocks on the main thread, but the main thread dispatches the control
2061 // runnable, invalidating mNotification.
2063 // Dispatched to main thread, blocks on closing the Notification.
2064 RefPtr<CloseNotificationRunnable> r =
2065 new CloseNotificationRunnable(self);
2066 ErrorResult rv;
2067 r->Dispatch(Killing, rv);
2068 // XXXbz I'm told throwing and returning false from here is pointless
2069 // (and also that doing sync stuff from here is really weird), so I
2070 // guess we just suppress the exception on rv, if any.
2071 rv.SuppressException();
2073 // Only call ReleaseObject() to match the observer's NotificationRef
2074 // ownership (since CloseNotificationRunnable asked the observer to drop
2075 // the reference to the notification).
2076 if (r->HadObserver()) {
2077 self->ReleaseObject();
2080 // From this point we cannot touch properties of this feature because
2081 // ReleaseObject() may have led to the notification going away and the
2082 // notification owns this feature!
2085 if (NS_WARN_IF(!mWorkerRef)) {
2086 return false;
2089 return true;
2093 * Checks:
2094 * 1) Is aWorker allowed to show a notification for scope?
2095 * 2) Is aWorker an active worker?
2097 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2099 class CheckLoadRunnable final : public WorkerMainThreadRunnable {
2100 nsresult mRv;
2101 nsCString mScope;
2102 ServiceWorkerRegistrationDescriptor mDescriptor;
2104 public:
2105 explicit CheckLoadRunnable(
2106 WorkerPrivate* aWorker, const nsACString& aScope,
2107 const ServiceWorkerRegistrationDescriptor& aDescriptor)
2108 : WorkerMainThreadRunnable(aWorker, "Notification :: Check Load"_ns),
2109 mRv(NS_ERROR_DOM_SECURITY_ERR),
2110 mScope(aScope),
2111 mDescriptor(aDescriptor) {}
2113 bool MainThreadRun() override {
2114 nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
2115 mRv = CheckScope(principal, mScope, mWorkerPrivate->WindowID());
2117 if (NS_FAILED(mRv)) {
2118 return true;
2121 auto activeWorker = mDescriptor.GetActive();
2123 if (!activeWorker ||
2124 activeWorker.ref().Id() != mWorkerPrivate->ServiceWorkerID()) {
2125 mRv = NS_ERROR_NOT_AVAILABLE;
2128 return true;
2131 nsresult Result() { return mRv; }
2134 /* static */
2135 already_AddRefed<Promise> Notification::ShowPersistentNotification(
2136 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
2137 const nsAString& aTitle, const NotificationOptions& aOptions,
2138 const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
2139 MOZ_ASSERT(aGlobal);
2141 // Validate scope.
2142 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2143 // thread. On calls from content, we can be sure the scope is valid since
2144 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2145 // debug only? The problem is that there would be different semantics in
2146 // debug and non-debug builds in such a case.
2147 if (NS_IsMainThread()) {
2148 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
2149 if (NS_WARN_IF(!sop)) {
2150 aRv.Throw(NS_ERROR_UNEXPECTED);
2151 return nullptr;
2154 nsIPrincipal* principal = sop->GetPrincipal();
2155 if (NS_WARN_IF(!principal)) {
2156 aRv.Throw(NS_ERROR_UNEXPECTED);
2157 return nullptr;
2160 uint64_t windowID = 0;
2161 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
2162 if (win) {
2163 windowID = win->WindowID();
2166 aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope), windowID);
2167 if (NS_WARN_IF(aRv.Failed())) {
2168 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2169 return nullptr;
2171 } else {
2172 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
2173 MOZ_ASSERT(worker);
2174 worker->AssertIsOnWorkerThread();
2176 RefPtr<CheckLoadRunnable> loadChecker = new CheckLoadRunnable(
2177 worker, NS_ConvertUTF16toUTF8(aScope), aDescriptor);
2178 loadChecker->Dispatch(Canceling, aRv);
2179 if (aRv.Failed()) {
2180 return nullptr;
2183 if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
2184 if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
2185 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
2186 } else {
2187 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
2189 return nullptr;
2193 RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
2194 if (NS_WARN_IF(aRv.Failed())) {
2195 return nullptr;
2198 // We check permission here rather than pass the Promise to NotificationTask
2199 // which leads to uglier code.
2200 NotificationPermission permission = GetPermission(aGlobal, aRv);
2202 // "If permission for notification's origin is not "granted", reject promise
2203 // with a TypeError exception, and terminate these substeps."
2204 if (NS_WARN_IF(aRv.Failed()) ||
2205 permission == NotificationPermission::Denied) {
2206 p->MaybeRejectWithTypeError("Permission to show Notification denied.");
2207 return p.forget();
2210 // "Otherwise, resolve promise with undefined."
2211 // The Notification may still not be shown due to other errors, but the spec
2212 // is not concerned with those.
2213 p->MaybeResolveWithUndefined();
2215 RefPtr<Notification> notification =
2216 CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
2217 if (NS_WARN_IF(aRv.Failed())) {
2218 return nullptr;
2221 return p.forget();
2224 /* static */
2225 already_AddRefed<Notification> Notification::CreateAndShow(
2226 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
2227 const NotificationOptions& aOptions, const nsAString& aScope,
2228 ErrorResult& aRv) {
2229 MOZ_ASSERT(aGlobal);
2231 RefPtr<Notification> notification =
2232 CreateInternal(aGlobal, u""_ns, aTitle, aOptions, aRv);
2233 if (aRv.Failed()) {
2234 return nullptr;
2237 // Make a structured clone of the aOptions.mData object
2238 JS::Rooted<JS::Value> data(aCx, aOptions.mData);
2239 notification->InitFromJSVal(aCx, data, aRv);
2240 if (NS_WARN_IF(aRv.Failed())) {
2241 return nullptr;
2244 notification->SetScope(aScope);
2246 auto ref = MakeUnique<NotificationRef>(notification);
2247 if (NS_WARN_IF(!ref->Initialized())) {
2248 aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
2249 return nullptr;
2252 // Queue a task to show the notification.
2253 nsCOMPtr<nsIRunnable> showNotificationTask = new NotificationTask(
2254 "Notification::CreateAndShow", std::move(ref), NotificationTask::eShow);
2256 nsresult rv =
2257 notification->DispatchToMainThread(showNotificationTask.forget());
2259 if (NS_WARN_IF(NS_FAILED(rv))) {
2260 notification->DispatchTrustedEvent(u"error"_ns);
2263 return notification.forget();
2266 /* static */
2267 nsresult Notification::RemovePermission(nsIPrincipal* aPrincipal) {
2268 MOZ_ASSERT(XRE_IsParentProcess());
2269 nsCOMPtr<nsIPermissionManager> permissionManager =
2270 mozilla::components::PermissionManager::Service();
2271 if (!permissionManager) {
2272 return NS_ERROR_FAILURE;
2274 permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"_ns);
2275 return NS_OK;
2278 /* static */
2279 nsresult Notification::OpenSettings(nsIPrincipal* aPrincipal) {
2280 MOZ_ASSERT(XRE_IsParentProcess());
2281 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
2282 if (!obs) {
2283 return NS_ERROR_FAILURE;
2285 // Notify other observers so they can show settings UI.
2286 obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
2287 return NS_OK;
2290 NS_IMETHODIMP
2291 Notification::Observe(nsISupports* aSubject, const char* aTopic,
2292 const char16_t* aData) {
2293 AssertIsOnMainThread();
2295 if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
2296 !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
2297 nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
2298 if (SameCOMIdentity(aSubject, window)) {
2299 nsCOMPtr<nsIObserverService> obs =
2300 mozilla::services::GetObserverService();
2301 if (obs) {
2302 obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
2303 obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
2306 CloseInternal(true);
2310 return NS_OK;
2313 nsresult Notification::DispatchToMainThread(
2314 already_AddRefed<nsIRunnable>&& aRunnable) {
2315 if (mWorkerPrivate) {
2316 return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable));
2318 AssertIsOnMainThread();
2319 return NS_DispatchToCurrentThread(std::move(aRunnable));
2322 } // namespace mozilla::dom