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