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"
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
{
66 const nsString mTitle
;
73 const nsString mBehavior
;
74 const nsString mServiceWorkerRegistrationScope
;
77 class ScopeCheckingGetCallback
: public nsINotificationStorageCallback
{
78 const nsString mScope
;
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
)) {
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
));
109 NS_IMETHOD
Done() override
= 0;
112 virtual ~ScopeCheckingGetCallback() = default;
114 nsTArray
<NotificationStrings
> mStrings
;
117 class NotificationStorageCallback final
: public ScopeCheckingGetCallback
{
119 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
120 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback
)
122 NotificationStorageCallback(nsIGlobalObject
* aWindow
, const nsAString
& aScope
,
124 : ScopeCheckingGetCallback(aScope
), mWindow(aWindow
), mPromise(aPromise
) {
125 AssertIsOnMainThread();
127 MOZ_ASSERT(aPromise
);
130 NS_IMETHOD
Done() final
{
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
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
);
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
)
171 class NotificationGetRunnable final
: public Runnable
{
172 const nsString mOrigin
;
174 nsCOMPtr
<nsINotificationStorageCallback
> mCallback
;
177 NotificationGetRunnable(const nsAString
& aOrigin
, const nsAString
& aTag
,
178 nsINotificationStorageCallback
* aCallback
)
179 : Runnable("NotificationGetRunnable"),
182 mCallback(aCallback
) {}
187 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
188 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
189 if (NS_WARN_IF(NS_FAILED(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
));
200 class NotificationPermissionRequest
: public ContentPermissionRequestBase
,
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
),
220 mCallback(aCallback
) {
221 MOZ_ASSERT(aPromise
);
224 NS_IMETHOD
GetName(nsACString
& aName
) override
{
225 aName
.AssignLiteral("NotificationPermissionRequest");
230 ~NotificationPermissionRequest() = default;
232 MOZ_CAN_RUN_SCRIPT nsresult
ResolvePromise();
233 nsresult
DispatchResolvePromise();
234 NotificationPermission mPermission
;
235 RefPtr
<Promise
> mPromise
;
236 RefPtr
<NotificationPermissionCallback
> mCallback
;
240 class ReleaseNotificationControlRunnable final
241 : public MainThreadWorkerControlRunnable
{
242 Notification
* mNotification
;
245 explicit ReleaseNotificationControlRunnable(Notification
* aNotification
)
246 : MainThreadWorkerControlRunnable(aNotification
->mWorkerPrivate
),
247 mNotification(aNotification
) {}
249 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
250 mNotification
->ReleaseObject();
255 class GetPermissionRunnable final
: public WorkerMainThreadRunnable
{
256 NotificationPermission mPermission
;
259 explicit GetPermissionRunnable(WorkerPrivate
* aWorker
)
260 : WorkerMainThreadRunnable(aWorker
, "Notification :: Get Permission"_ns
),
261 mPermission(NotificationPermission::Denied
) {}
263 bool MainThreadRun() override
{
265 mPermission
= Notification::GetPermissionInternal(
266 mWorkerPrivate
->GetPrincipal(), result
);
270 NotificationPermission
GetPermission() { return mPermission
; }
273 class FocusWindowRunnable final
: public Runnable
{
274 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> mWindow
;
277 explicit FocusWindowRunnable(
278 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
279 : Runnable("FocusWindowRunnable"), mWindow(aWindow
) {}
283 AssertIsOnMainThread();
284 if (!mWindow
->IsCurrentInnerWindow()) {
285 // Window has been closed, this observer is not valid anymore
289 nsFocusManager::FocusWindow(mWindow
->GetOuterWindow(), CallerType::System
);
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
))) {
305 return aPrincipal
->CheckMayLoadWithReporting(
307 /* allowIfInheritsPrincipal = */ false, aWindowID
);
309 } // anonymous namespace
311 // Subclass that can be directly dispatched to child workers from the main
313 class NotificationWorkerRunnable
: public MainThreadWorkerRunnable
{
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
);
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
;
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
;
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();
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
;
380 Notification
* mNotification
;
383 // Only useful for workers.
384 void Forget() { mNotification
= nullptr; }
387 explicit NotificationRef(Notification
* aNotification
)
388 : mNotification(aNotification
) {
389 MOZ_ASSERT(mNotification
);
390 if (mNotification
->mWorkerPrivate
) {
391 mNotification
->mWorkerPrivate
->AssertIsOnWorkerThread();
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
404 bool Initialized() { return mInited
; }
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());
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
{
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
) {}
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
,
474 NotificationPermissionRequest::Run() {
475 bool isSystem
= mPrincipal
->IsSystemPrincipal();
476 bool blocked
= false;
478 mPermission
= NotificationPermission::Granted
;
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
;
488 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
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();
504 case PromptResult::Granted
:
505 mPermission
= NotificationPermission::Granted
;
507 case PromptResult::Denied
:
508 mPermission
= NotificationPermission::Denied
;
515 if (!mHasValidTransientUserGestureActivation
&&
516 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
517 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
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
;
530 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
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
);
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
552 mPermission
= NotificationPermission::Default
;
553 return DispatchResolvePromise();
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() {
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();
587 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
,
588 doc
, nsContentUtils::eDOM_PROPERTIES
,
589 "NotificationsRequireUserGesture");
593 mPermission
= Notification::TestPermission(mPrincipal
);
597 RefPtr
<NotificationPermissionCallback
> callback(mCallback
);
598 callback
->Call(mPermission
, error
);
599 rv
= error
.StealNSResult();
601 mPromise
->MaybeResolve(mPermission
);
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
608 class NotificationObserver final
: public nsIObserver
{
610 nsCOMPtr
<nsIObserver
> mObserver
;
611 nsCOMPtr
<nsIPrincipal
> mPrincipal
;
612 bool mInPrivateBrowsing
;
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
);
627 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
629 nsresult
AdjustPushQuota(const char* aTopic
);
632 NS_IMPL_ISUPPORTS(NotificationObserver
, nsIObserver
)
634 class MainThreadNotificationObserver
: public nsIObserver
{
636 UniquePtr
<NotificationRef
> mNotificationRef
;
640 explicit MainThreadNotificationObserver(UniquePtr
<NotificationRef
> aRef
)
641 : mNotificationRef(std::move(aRef
)) {
642 AssertIsOnMainThread();
646 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
649 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver
, nsIObserver
)
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();
665 MOZ_CRASH("Invalid action");
668 MOZ_ASSERT(!mNotificationRef
);
673 bool Notification::PrefEnabled(JSContext
* aCx
, JSObject
* aObj
) {
674 if (!NS_IsMainThread()) {
675 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(aCx
);
676 if (!workerPrivate
) {
680 if (workerPrivate
->IsServiceWorker()) {
681 return StaticPrefs::dom_webnotifications_serviceworker_enabled();
685 return StaticPrefs::dom_webnotifications_enabled();
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),
710 mRequireInteraction(aRequireInteraction
),
712 mVibrate(std::move(aVibrate
)),
713 mBehavior(aBehavior
),
714 mData(JS::NullValue()),
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
);
739 void Notification::SetAlertName() {
740 AssertIsOnMainThread();
741 if (!mAlertName
.IsEmpty()) {
745 nsAutoString alertName
;
746 nsresult rv
= GetOrigin(GetPrincipal(), alertName
);
747 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
758 alertName
.AppendLiteral("notag:");
759 alertName
.Append(mID
);
762 mAlertName
= alertName
;
765 // May be called on any thread.
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
);
775 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
776 "Use registration.showNotification() instead.");
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())) {
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();
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
) {
800 RootedDictionary
<NotificationOptions
> options(RootingCx());
801 options
.mDir
= Notification::StringToDirection(nsString(aDir
));
802 options
.mLang
= aLang
;
803 options
.mBody
= aBody
;
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())) {
814 notification
->SetScope(aServiceWorkerRegistrationScope
);
816 return notification
.forget();
819 nsresult
Notification::PersistNotification() {
820 AssertIsOnMainThread();
822 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
823 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
829 rv
= GetOrigin(GetPrincipal(), origin
);
830 if (NS_WARN_IF(NS_FAILED(rv
))) {
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
);
853 SetStoredState(true);
857 void Notification::UnpersistNotification() {
858 AssertIsOnMainThread();
860 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
861 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
);
862 if (notificationStorage
) {
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
) {
878 if (!aID
.IsEmpty()) {
881 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
882 do_GetService("@mozilla.org/uuid-generator;1");
883 NS_ENSURE_TRUE(uuidgen
, nullptr);
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
);
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()) {
904 "Silent notifications must not specify vibration patterns.");
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
);
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();
964 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(GetOwner());
965 NS_ENSURE_TRUE(sop
, nullptr);
966 return sop
->GetPrincipal();
970 class WorkerNotificationObserver final
: public MainThreadNotificationObserver
{
972 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver
,
973 MainThreadNotificationObserver
)
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();
988 virtual ~WorkerNotificationObserver() {
989 AssertIsOnMainThread();
991 MOZ_ASSERT(mNotificationRef
);
992 Notification
* notification
= mNotificationRef
->GetNotification();
994 notification
->mObserver
= nullptr;
999 class ServiceWorkerNotificationObserver final
: public 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
)
1011 mPrincipal(aPrincipal
),
1019 mBehavior(aBehavior
) {
1020 AssertIsOnMainThread();
1021 MOZ_ASSERT(aPrincipal
);
1025 ~ServiceWorkerNotificationObserver() = default;
1027 const nsString mScope
;
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
;
1061 NotificationClickWorkerRunnable(
1062 Notification
* aNotification
,
1063 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
1064 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
),
1065 mNotification(aNotification
),
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());
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
));
1095 } else if (!strcmp("alertsettingscallback", aTopic
)) {
1096 if (XRE_IsParentProcess()) {
1097 return Notification::OpenSettings(mPrincipal
);
1099 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1101 ContentChild::GetSingleton()->SendOpenNotificationSettings(
1102 IPC::Principal(mPrincipal
));
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
)) {
1124 if (!strcmp("alertshow", aTopic
)) {
1125 return pushQuotaManager
->NotificationForOriginShown(origin
.get());
1127 return pushQuotaManager
->NotificationForOriginClosed(origin
.get());
1131 MainThreadNotificationObserver::Observe(nsISupports
* aSubject
,
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
);
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
);
1206 if (!r
->Dispatch()) {
1207 NS_WARNING("Could not dispatch event to worker notification");
1213 ServiceWorkerNotificationObserver::Observe(nsISupports
* aSubject
,
1215 const char16_t
* aData
) {
1216 AssertIsOnMainThread();
1218 nsAutoCString originSuffix
;
1219 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
1220 if (NS_WARN_IF(NS_FAILED(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
));
1237 auto* cc
= ContentChild::GetSingleton();
1238 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1239 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1241 Unused
<< cc
->SendNotificationEvent(u
"click"_ns
, data
);
1246 if (!strcmp("alertfinished", aTopic
)) {
1248 nsresult rv
= Notification::GetOrigin(mPrincipal
, origin
);
1249 if (NS_WARN_IF(NS_FAILED(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
));
1272 auto* cc
= ContentChild::GetSingleton();
1273 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1274 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1276 Unused
<< cc
->SendNotificationEvent(u
"close"_ns
, data
);
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();
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?
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()));
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();
1348 NotificationPermission permission
= NotificationPermission::Denied
;
1349 if (mWorkerPrivate
) {
1350 permission
= GetPermissionInternal(mWorkerPrivate
->GetPrincipal(), result
);
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");
1365 DispatchTrustedEvent(u
"error"_ns
);
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
;
1386 observer
= new MainThreadNotificationObserver(std::move(ownership
));
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
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();
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
);
1431 nsAutoString persistentData
;
1433 JSONWriter
w(MakeUnique
<StringWriteFunc
>(persistentData
));
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
);
1448 alertService
->ShowPersistentNotification(persistentData
, alert
,
1451 alertService
->ShowAlert(alert
, alertObserver
);
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();
1466 already_AddRefed
<Promise
> Notification::RequestPermission(
1467 const GlobalObject
& aGlobal
,
1468 const Optional
<OwningNonNull
<NotificationPermissionCallback
> >& aCallback
,
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
);
1481 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1483 RefPtr
<Promise
> promise
= Promise::Create(window
->AsGlobal(), aRv
);
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();
1500 NotificationPermission
Notification::GetPermission(const GlobalObject
& aGlobal
,
1502 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1503 return GetPermission(global
, aRv
);
1507 NotificationPermission
Notification::GetPermission(nsIGlobalObject
* aGlobal
,
1509 if (NS_IsMainThread()) {
1510 return GetPermissionInternal(aGlobal
, aRv
);
1512 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1514 RefPtr
<GetPermissionRunnable
> r
= new GetPermissionRunnable(worker
);
1515 r
->Dispatch(Canceling
, aRv
);
1517 return NotificationPermission::Denied
;
1520 return r
->GetPermission();
1525 NotificationPermission
Notification::GetPermissionInternal(nsISupports
* aGlobal
,
1527 // Get principal from global to check permission for notifications.
1528 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aGlobal
);
1530 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1531 return NotificationPermission::Denied
;
1534 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1535 return GetPermissionInternal(principal
, aRv
);
1539 NotificationPermission
Notification::GetPermissionInternal(
1540 nsIPrincipal
* aPrincipal
, ErrorResult
& aRv
) {
1541 AssertIsOnMainThread();
1542 MOZ_ASSERT(aPrincipal
);
1544 if (aPrincipal
->IsSystemPrincipal()) {
1545 return NotificationPermission::Granted
;
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
;
1558 return NotificationPermission::Denied
;
1562 return TestPermission(aPrincipal
);
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
;
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
1606 auto encoding
= UTF_8_ENCODING
;
1608 if (mWorkerPrivate
) {
1609 baseUri
= mWorkerPrivate
->GetBaseURI();
1611 Document
* doc
= GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1613 baseUri
= doc
->GetBaseURI();
1614 encoding
= doc
->GetDocumentCharacterSet();
1616 NS_WARNING("No document found for main thread notification!");
1617 return NS_ERROR_FAILURE
;
1622 if (mIconUrl
.Length() > 0) {
1623 nsCOMPtr
<nsIURI
> srcUri
;
1624 rv
= NS_NewURI(getter_AddRefs(srcUri
), mIconUrl
, encoding
, baseUri
);
1625 if (NS_SUCCEEDED(rv
)) {
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
,
1635 if (NS_SUCCEEDED(rv
)) {
1637 srcUri
->GetSpec(src
);
1638 CopyUTF8toUTF16(src
, soundUrl
);
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();
1654 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1659 aRv
= GetOrigin(doc
->NodePrincipal(), origin
);
1664 RefPtr
<Promise
> promise
= Promise::Create(aWindow
->AsGlobal(), aRv
);
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())) {
1680 return promise
.forget();
1683 already_AddRefed
<Promise
> Notification::Get(
1684 const GlobalObject
& aGlobal
, const GetNotificationOptions
& aFilter
,
1686 AssertIsOnMainThread();
1687 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
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
;
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();
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
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
;
1738 WorkerGetCallback(PromiseWorkerProxy
* aProxy
, const nsAString
& aScope
)
1739 : ScopeCheckingGetCallback(aScope
), mPromiseProxy(aProxy
) {
1740 AssertIsOnMainThread();
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()) {
1754 RefPtr
<WorkerGetResultRunnable
> r
= new WorkerGetResultRunnable(
1755 proxy
->GetWorkerPrivate(), proxy
, std::move(mStrings
));
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
;
1773 WorkerGetRunnable(PromiseWorkerProxy
* aProxy
, const nsAString
& aTag
,
1774 const nsAString
& aScope
)
1775 : Runnable("WorkerGetRunnable"),
1776 mPromiseProxy(aProxy
),
1779 MOZ_ASSERT(mPromiseProxy
);
1784 AssertIsOnMainThread();
1785 nsCOMPtr
<nsINotificationStorageCallback
> callback
=
1786 new WorkerGetCallback(mPromiseProxy
, mScope
);
1789 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
1790 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
1791 if (NS_WARN_IF(NS_FAILED(rv
))) {
1796 MutexAutoLock
lock(mPromiseProxy
->Lock());
1797 if (mPromiseProxy
->CleanedUp()) {
1802 rv
= Notification::GetOrigin(
1803 mPromiseProxy
->GetWorkerPrivate()->GetPrincipal(), origin
);
1804 if (NS_WARN_IF(NS_FAILED(rv
))) {
1809 rv
= notificationStorage
->Get(origin
, mTag
, callback
);
1810 if (NS_WARN_IF(NS_FAILED(rv
))) {
1819 ~WorkerGetRunnable() = default;
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())) {
1833 RefPtr
<PromiseWorkerProxy
> proxy
=
1834 PromiseWorkerProxy::Create(aWorkerPrivate
, p
);
1836 aRv
.Throw(NS_ERROR_DOM_ABORT_ERR
);
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()));
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()) {
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
);
1880 UnpersistNotification();
1882 nsCOMPtr
<nsIAlertsService
> alertService
= components::Alerts::Service();
1884 nsAutoString alertName
;
1885 GetAlertName(alertName
);
1886 alertService
->CloseAlert(alertName
);
1891 nsresult
Notification::GetOrigin(nsIPrincipal
* aPrincipal
, nsString
& aOrigin
) {
1893 return NS_ERROR_FAILURE
;
1896 nsresult rv
= nsContentUtils::GetUTFOrigin(aPrincipal
, aOrigin
);
1897 NS_ENSURE_SUCCESS(rv
, rv
);
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()) {
1914 RefPtr
<nsStructuredCloneContainer
> container
=
1915 new nsStructuredCloneContainer();
1916 rv
= container
->InitFromBase64(mDataAsBase64
, JS_STRUCTURED_CLONE_VERSION
);
1917 if (NS_WARN_IF(NS_FAILED(rv
))) {
1922 JS::Rooted
<JS::Value
> data(aCx
);
1923 rv
= container
->DeserializeToJsval(aCx
, &data
);
1924 if (NS_WARN_IF(NS_FAILED(rv
))) {
1929 if (data
.isGCThing()) {
1930 mozilla::HoldJSObjects(this);
1934 if (mData
.isNull()) {
1942 void Notification::InitFromJSVal(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
1944 if (!mDataAsBase64
.IsEmpty() || aData
.isNull()) {
1947 RefPtr
<nsStructuredCloneContainer
> dataObjectContainer
=
1948 new nsStructuredCloneContainer();
1949 aRv
= dataObjectContainer
->InitFromJSVal(aData
, aCx
);
1950 if (NS_WARN_IF(aRv
.Failed())) {
1954 aRv
= dataObjectContainer
->GetDataAsBase64(mDataAsBase64
);
1955 if (NS_WARN_IF(aRv
.Failed())) {
1960 void Notification::InitFromBase64(const nsAString
& aData
, ErrorResult
& aRv
) {
1961 if (!mDataAsBase64
.IsEmpty() || aData
.IsEmpty()) {
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())) {
1973 aRv
= container
->GetDataAsBase64(mDataAsBase64
);
1974 if (NS_WARN_IF(aRv
.Failed())) {
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()) {
1993 void Notification::ReleaseObject() {
1994 AssertIsOnTargetThread();
1995 MOZ_ASSERT(mTaskCount
> 0);
1996 MOZ_ASSERT_IF(mWorkerPrivate
, mWorkerRef
);
1999 if (mWorkerPrivate
&& mTaskCount
== 0) {
2000 MOZ_ASSERT(mWorkerRef
);
2001 mWorkerRef
= nullptr;
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
;
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();
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;
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
);
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
)) {
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
{
2098 ServiceWorkerRegistrationDescriptor mDescriptor
;
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
),
2107 mDescriptor(aDescriptor
) {}
2109 bool MainThreadRun() override
{
2110 nsIPrincipal
* principal
= mWorkerPrivate
->GetPrincipal();
2111 mRv
= CheckScope(principal
, mScope
, mWorkerPrivate
->WindowID());
2113 if (NS_FAILED(mRv
)) {
2117 auto activeWorker
= mDescriptor
.GetActive();
2119 if (!activeWorker
||
2120 activeWorker
.ref().Id() != mWorkerPrivate
->ServiceWorkerID()) {
2121 mRv
= NS_ERROR_NOT_AVAILABLE
;
2127 nsresult
Result() { return mRv
; }
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
);
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
);
2150 nsIPrincipal
* principal
= sop
->GetPrincipal();
2151 if (NS_WARN_IF(!principal
)) {
2152 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2156 uint64_t windowID
= 0;
2157 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
);
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
);
2168 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
2170 worker
->AssertIsOnWorkerThread();
2172 RefPtr
<CheckLoadRunnable
> loadChecker
= new CheckLoadRunnable(
2173 worker
, NS_ConvertUTF16toUTF8(aScope
), aDescriptor
);
2174 loadChecker
->Dispatch(Canceling
, aRv
);
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
));
2183 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2189 RefPtr
<Promise
> p
= Promise::Create(aGlobal
, aRv
);
2190 if (NS_WARN_IF(aRv
.Failed())) {
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.");
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())) {
2221 already_AddRefed
<Notification
> Notification::CreateAndShow(
2222 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aTitle
,
2223 const NotificationOptions
& aOptions
, const nsAString
& aScope
,
2225 MOZ_ASSERT(aGlobal
);
2227 RefPtr
<Notification
> notification
=
2228 CreateInternal(aGlobal
, u
""_ns
, aTitle
, aOptions
, aRv
);
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())) {
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
);
2248 // Queue a task to show the notification.
2249 nsCOMPtr
<nsIRunnable
> showNotificationTask
= new NotificationTask(
2250 "Notification::CreateAndShow", std::move(ref
), NotificationTask::eShow
);
2253 notification
->DispatchToMainThread(showNotificationTask
.forget());
2255 if (NS_WARN_IF(NS_FAILED(rv
))) {
2256 notification
->DispatchTrustedEvent(u
"error"_ns
);
2259 return notification
.forget();
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
);
2275 nsresult
Notification::OpenSettings(nsIPrincipal
* aPrincipal
) {
2276 MOZ_ASSERT(XRE_IsParentProcess());
2277 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
2279 return NS_ERROR_FAILURE
;
2281 // Notify other observers so they can show settings UI.
2282 obs
->NotifyObservers(aPrincipal
, "notifications-open-settings", nullptr);
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();
2298 obs
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
2299 obs
->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC
);
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