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/JSONStringWriteFuncs.h"
17 #include "mozilla/OwningNonNull.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/StaticPrefs_dom.h"
20 #include "mozilla/Unused.h"
21 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
22 #include "mozilla/dom/BindingUtils.h"
23 #include "mozilla/dom/ContentChild.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/Promise.h"
26 #include "mozilla/dom/PromiseWorkerProxy.h"
27 #include "mozilla/dom/QMResult.h"
28 #include "mozilla/dom/RootedDictionary.h"
29 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
30 #include "mozilla/dom/ServiceWorkerUtils.h"
31 #include "mozilla/dom/WorkerRunnable.h"
32 #include "mozilla/dom/WorkerScope.h"
33 #include "mozilla/dom/quota/ResultExtensions.h"
34 #include "Navigator.h"
35 #include "nsComponentManagerUtils.h"
36 #include "nsContentPermissionHelper.h"
37 #include "nsContentUtils.h"
38 #include "nsFocusManager.h"
39 #include "nsIAlertsService.h"
40 #include "nsIContentPermissionPrompt.h"
41 #include "nsILoadContext.h"
42 #include "nsINotificationStorage.h"
43 #include "nsIPermission.h"
44 #include "nsIPermissionManager.h"
45 #include "nsIPushService.h"
46 #include "nsIScriptError.h"
47 #include "nsIServiceWorkerManager.h"
48 #include "nsIUUIDGenerator.h"
49 #include "nsNetUtil.h"
50 #include "nsProxyRelease.h"
51 #include "nsServiceManagerUtils.h"
52 #include "nsStructuredCloneContainer.h"
53 #include "nsThreadUtils.h"
54 #include "nsXULAppAPI.h"
56 namespace mozilla::dom
{
58 struct NotificationStrings
{
60 const nsString mTitle
;
67 const nsString mBehavior
;
68 const nsString mServiceWorkerRegistrationScope
;
71 class ScopeCheckingGetCallback
: public nsINotificationStorageCallback
{
72 const nsString mScope
;
75 explicit ScopeCheckingGetCallback(const nsAString
& aScope
) : mScope(aScope
) {}
77 NS_IMETHOD
Handle(const nsAString
& aID
, const nsAString
& aTitle
,
78 const nsAString
& aDir
, const nsAString
& aLang
,
79 const nsAString
& aBody
, const nsAString
& aTag
,
80 const nsAString
& aIcon
, const nsAString
& aData
,
81 const nsAString
& aBehavior
,
82 const nsAString
& aServiceWorkerRegistrationScope
) final
{
83 AssertIsOnMainThread();
84 MOZ_ASSERT(!aID
.IsEmpty());
86 // Skip scopes that don't match when called from getNotifications().
87 if (!mScope
.IsEmpty() && !mScope
.Equals(aServiceWorkerRegistrationScope
)) {
91 NotificationStrings strings
= {
92 nsString(aID
), nsString(aTitle
),
93 nsString(aDir
), nsString(aLang
),
94 nsString(aBody
), nsString(aTag
),
95 nsString(aIcon
), nsString(aData
),
96 nsString(aBehavior
), nsString(aServiceWorkerRegistrationScope
),
99 mStrings
.AppendElement(std::move(strings
));
103 NS_IMETHOD
Done() override
= 0;
106 virtual ~ScopeCheckingGetCallback() = default;
108 nsTArray
<NotificationStrings
> mStrings
;
111 class NotificationStorageCallback final
: public ScopeCheckingGetCallback
{
113 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
114 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback
)
116 NotificationStorageCallback(nsIGlobalObject
* aWindow
, const nsAString
& aScope
,
118 : ScopeCheckingGetCallback(aScope
), mWindow(aWindow
), mPromise(aPromise
) {
119 AssertIsOnMainThread();
121 MOZ_ASSERT(aPromise
);
124 NS_IMETHOD
Done() final
{
125 AutoTArray
<RefPtr
<Notification
>, 5> notifications
;
127 for (uint32_t i
= 0; i
< mStrings
.Length(); ++i
) {
128 auto result
= Notification::ConstructFromFields(
129 mWindow
, mStrings
[i
].mID
, mStrings
[i
].mTitle
, mStrings
[i
].mDir
,
130 mStrings
[i
].mLang
, mStrings
[i
].mBody
, mStrings
[i
].mTag
,
131 mStrings
[i
].mIcon
, mStrings
[i
].mData
,
132 /* mStrings[i].mBehavior, not
134 mStrings
[i
].mServiceWorkerRegistrationScope
);
135 if (result
.isErr()) {
138 RefPtr
<Notification
> n
= result
.unwrap();
139 n
->SetStoredState(true);
140 notifications
.AppendElement(n
.forget());
143 mPromise
->MaybeResolve(notifications
);
148 virtual ~NotificationStorageCallback() = default;
150 nsCOMPtr
<nsIGlobalObject
> mWindow
;
151 RefPtr
<Promise
> mPromise
;
152 const nsString mScope
;
155 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback
)
156 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback
)
157 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback
, mWindow
, mPromise
);
159 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback
)
160 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback
)
161 NS_INTERFACE_MAP_ENTRY(nsISupports
)
164 class NotificationGetRunnable final
: public Runnable
{
165 const nsString mOrigin
;
167 nsCOMPtr
<nsINotificationStorageCallback
> mCallback
;
170 NotificationGetRunnable(const nsAString
& aOrigin
, const nsAString
& aTag
,
171 nsINotificationStorageCallback
* aCallback
)
172 : Runnable("NotificationGetRunnable"),
175 mCallback(aCallback
) {}
180 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
181 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
182 if (NS_WARN_IF(NS_FAILED(rv
))) {
186 rv
= notificationStorage
->Get(mOrigin
, mTag
, mCallback
);
187 // XXXnsm Is it guaranteed mCallback will be called in case of failure?
188 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
193 class NotificationPermissionRequest
: public ContentPermissionRequestBase
,
198 NS_DECL_ISUPPORTS_INHERITED
199 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest
,
200 ContentPermissionRequestBase
)
202 // nsIContentPermissionRequest
203 NS_IMETHOD
Cancel(void) override
;
204 NS_IMETHOD
Allow(JS::Handle
<JS::Value
> choices
) override
;
206 NotificationPermissionRequest(nsIPrincipal
* aPrincipal
,
207 nsPIDOMWindowInner
* aWindow
, Promise
* aPromise
,
208 NotificationPermissionCallback
* aCallback
)
209 : ContentPermissionRequestBase(aPrincipal
, aWindow
, "notification"_ns
,
210 "desktop-notification"_ns
),
211 mPermission(NotificationPermission::Default
),
213 mCallback(aCallback
) {
214 MOZ_ASSERT(aPromise
);
217 NS_IMETHOD
GetName(nsACString
& aName
) override
{
218 aName
.AssignLiteral("NotificationPermissionRequest");
223 ~NotificationPermissionRequest() = default;
225 MOZ_CAN_RUN_SCRIPT nsresult
ResolvePromise();
226 nsresult
DispatchResolvePromise();
227 NotificationPermission mPermission
;
228 RefPtr
<Promise
> mPromise
;
229 RefPtr
<NotificationPermissionCallback
> mCallback
;
233 class ReleaseNotificationControlRunnable final
234 : public MainThreadWorkerControlRunnable
{
235 Notification
* mNotification
;
238 explicit ReleaseNotificationControlRunnable(Notification
* aNotification
)
239 : MainThreadWorkerControlRunnable(aNotification
->mWorkerPrivate
),
240 mNotification(aNotification
) {}
242 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
243 mNotification
->ReleaseObject();
248 class GetPermissionRunnable final
: public WorkerMainThreadRunnable
{
249 NotificationPermission mPermission
;
252 explicit GetPermissionRunnable(WorkerPrivate
* aWorker
)
253 : WorkerMainThreadRunnable(aWorker
, "Notification :: Get Permission"_ns
),
254 mPermission(NotificationPermission::Denied
) {}
256 bool MainThreadRun() override
{
258 mPermission
= Notification::GetPermissionInternal(
259 mWorkerPrivate
->GetPrincipal(), result
);
263 NotificationPermission
GetPermission() { return mPermission
; }
266 class FocusWindowRunnable final
: public Runnable
{
267 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> mWindow
;
270 explicit FocusWindowRunnable(
271 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
272 : Runnable("FocusWindowRunnable"), mWindow(aWindow
) {}
274 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
276 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
277 AssertIsOnMainThread();
278 if (!mWindow
->IsCurrentInnerWindow()) {
279 // Window has been closed, this observer is not valid anymore
283 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= mWindow
->GetOuterWindow();
284 nsFocusManager::FocusWindow(outerWindow
, CallerType::System
);
289 nsresult
CheckScope(nsIPrincipal
* aPrincipal
, const nsACString
& aScope
,
290 uint64_t aWindowID
) {
291 AssertIsOnMainThread();
292 MOZ_ASSERT(aPrincipal
);
294 nsCOMPtr
<nsIURI
> scopeURI
;
295 nsresult rv
= NS_NewURI(getter_AddRefs(scopeURI
), aScope
);
296 if (NS_WARN_IF(NS_FAILED(rv
))) {
300 return aPrincipal
->CheckMayLoadWithReporting(
302 /* allowIfInheritsPrincipal = */ false, aWindowID
);
304 } // anonymous namespace
306 // Subclass that can be directly dispatched to child workers from the main
308 class NotificationWorkerRunnable
: public MainThreadWorkerRunnable
{
310 explicit NotificationWorkerRunnable(
311 WorkerPrivate
* aWorkerPrivate
,
312 const char* aName
= "NotificationWorkerRunnable")
313 : MainThreadWorkerRunnable(aWorkerPrivate
, aName
) {}
315 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
316 aWorkerPrivate
->AssertIsOnWorkerThread();
317 // WorkerScope might start dying at the moment. And WorkerRunInternal()
318 // should not be executed once WorkerScope is dying, since
319 // WorkerRunInternal() might access resources which already been freed
320 // during WorkerRef::Notify().
321 if (aWorkerPrivate
->GlobalScope() &&
322 !aWorkerPrivate
->GlobalScope()->IsDying()) {
323 WorkerRunInternal(aWorkerPrivate
);
328 virtual void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) = 0;
331 // Overrides dispatch and run handlers so we can directly dispatch from main
332 // thread to child workers.
333 class NotificationEventWorkerRunnable final
334 : public NotificationWorkerRunnable
{
335 Notification
* mNotification
;
336 const nsString mEventName
;
339 NotificationEventWorkerRunnable(Notification
* aNotification
,
340 const nsString
& aEventName
)
341 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
,
342 "NotificationEventWorkerRunnable"),
343 mNotification(aNotification
),
344 mEventName(aEventName
) {}
346 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
347 mNotification
->DispatchTrustedEvent(mEventName
);
351 class ReleaseNotificationRunnable final
: public NotificationWorkerRunnable
{
352 Notification
* mNotification
;
355 explicit ReleaseNotificationRunnable(Notification
* aNotification
)
356 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
,
357 "ReleaseNotificationRunnable"),
358 mNotification(aNotification
) {}
360 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
361 aWorkerPrivate
->AssertIsOnWorkerThread();
362 // ReleaseNotificationRunnable is only used in StrongWorkerRef's shutdown
363 // callback. At the moment, it is supposed to executing
364 // mNotification->ReleaseObject() safely even though the corresponding
365 // WorkerScope::IsDying() is true. It is unlike other
366 // NotificationWorkerRunnable.
367 WorkerRunInternal(aWorkerPrivate
);
371 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
372 mNotification
->ReleaseObject();
375 nsresult
Cancel() override
{
376 mNotification
->ReleaseObject();
381 // Create one whenever you require ownership of the notification. Use with
382 // UniquePtr<>. See Notification.h for details.
383 class NotificationRef final
{
384 friend class WorkerNotificationObserver
;
387 Notification
* mNotification
;
390 // Only useful for workers.
391 void Forget() { mNotification
= nullptr; }
394 explicit NotificationRef(Notification
* aNotification
)
395 : mNotification(aNotification
) {
396 MOZ_ASSERT(mNotification
);
397 if (mNotification
->mWorkerPrivate
) {
398 mNotification
->mWorkerPrivate
->AssertIsOnWorkerThread();
400 AssertIsOnMainThread();
403 mInited
= mNotification
->AddRefObject();
406 // This is only required because Gecko runs script in a worker's onclose
407 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
408 // fail. Due to non-standardness and added complications if we decide to
409 // support this, attempts to create a Notification in onclose just throw
411 bool Initialized() { return mInited
; }
414 if (Initialized() && mNotification
) {
415 Notification
* notification
= mNotification
;
416 mNotification
= nullptr;
417 if (notification
->mWorkerPrivate
&& NS_IsMainThread()) {
418 // Try to pass ownership back to the worker. If the dispatch succeeds we
419 // are guaranteed this runnable will run, and that it will run after
420 // queued event runnables, so event runnables will have a safe pointer
421 // to the Notification.
423 // If the dispatch fails, the worker isn't running anymore and the event
424 // runnables have already run or been canceled. We can use a control
425 // runnable to release the reference.
426 RefPtr
<ReleaseNotificationRunnable
> r
=
427 new ReleaseNotificationRunnable(notification
);
429 if (!r
->Dispatch()) {
430 RefPtr
<ReleaseNotificationControlRunnable
> r
=
431 new ReleaseNotificationControlRunnable(notification
);
432 MOZ_ALWAYS_TRUE(r
->Dispatch());
435 notification
->AssertIsOnTargetThread();
436 notification
->ReleaseObject();
441 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
442 // a rawptr that the NotificationRef can invalidate?
443 Notification
* GetNotification() {
444 MOZ_ASSERT(Initialized());
445 return mNotification
;
449 class NotificationTask
: public Runnable
{
451 enum NotificationAction
{ eShow
, eClose
};
453 NotificationTask(const char* aName
, UniquePtr
<NotificationRef
> aRef
,
454 NotificationAction aAction
)
455 : Runnable(aName
), mNotificationRef(std::move(aRef
)), mAction(aAction
) {}
461 virtual ~NotificationTask() = default;
463 UniquePtr
<NotificationRef
> mNotificationRef
;
464 NotificationAction mAction
;
467 uint32_t Notification::sCount
= 0;
469 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest
,
470 ContentPermissionRequestBase
, mCallback
)
471 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest
,
472 ContentPermissionRequestBase
)
473 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest
,
474 ContentPermissionRequestBase
)
476 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
477 NotificationPermissionRequest
, ContentPermissionRequestBase
, nsIRunnable
,
481 NotificationPermissionRequest::Run() {
482 bool isSystem
= mPrincipal
->IsSystemPrincipal();
483 bool blocked
= false;
485 mPermission
= NotificationPermission::Granted
;
487 mPrincipal
->GetPrivateBrowsingId() != 0 &&
489 dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
490 mPermission
= NotificationPermission::Denied
;
493 // File are automatically granted permission.
495 if (mPrincipal
->SchemeIs("file")) {
496 mPermission
= NotificationPermission::Granted
;
497 } else if (!mWindow
->IsSecureContext()) {
498 mPermission
= NotificationPermission::Denied
;
500 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
502 nsContentUtils::ReportToConsole(
503 nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
504 nsContentUtils::eDOM_PROPERTIES
,
505 "NotificationsInsecureRequestIsForbidden");
510 // We can't call ShowPrompt() directly here since our logic for determining
511 // whether to display a prompt depends on the checks above as well as the
512 // result of CheckPromptPrefs(). So we have to manually check the prompt
513 // prefs and decide what to do based on that.
514 PromptResult pr
= CheckPromptPrefs();
516 case PromptResult::Granted
:
517 mPermission
= NotificationPermission::Granted
;
519 case PromptResult::Denied
:
520 mPermission
= NotificationPermission::Denied
;
527 if (!mHasValidTransientUserGestureActivation
&&
528 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
529 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
531 doc
->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation
);
535 // Check this after checking the prompt prefs to make sure this pref overrides
536 // those. We rely on this for testing purposes.
537 if (!isSystem
&& !blocked
&&
538 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
539 !mPrincipal
->Subsumes(mTopLevelPrincipal
)) {
540 mPermission
= NotificationPermission::Denied
;
542 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
544 nsContentUtils::ReportToConsole(
545 nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
546 nsContentUtils::eDOM_PROPERTIES
,
547 "NotificationsCrossOriginIframeRequestIsForbidden");
551 if (mPermission
!= NotificationPermission::Default
) {
552 return DispatchResolvePromise();
555 return nsContentPermissionUtils::AskPermission(this, mWindow
);
559 NotificationPermissionRequest::Cancel() {
560 // `Cancel` is called if the user denied permission or dismissed the
561 // permission request. To distinguish between the two, we set the
562 // permission to "default" and query the permission manager in
564 mPermission
= NotificationPermission::Default
;
565 return DispatchResolvePromise();
569 NotificationPermissionRequest::Allow(JS::Handle
<JS::Value
> aChoices
) {
570 MOZ_ASSERT(aChoices
.isUndefined());
572 mPermission
= NotificationPermission::Granted
;
573 return DispatchResolvePromise();
576 inline nsresult
NotificationPermissionRequest::DispatchResolvePromise() {
577 nsCOMPtr
<nsIRunnable
> resolver
=
578 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
579 this, &NotificationPermissionRequest::ResolvePromise
);
580 return nsGlobalWindowInner::Cast(mWindow
.get())->Dispatch(resolver
.forget());
583 nsresult
NotificationPermissionRequest::ResolvePromise() {
585 // This will still be "default" if the user dismissed the doorhanger,
586 // or "denied" otherwise.
587 if (mPermission
== NotificationPermission::Default
) {
588 // When the front-end has decided to deny the permission request
589 // automatically and we are not handling user input, then log a
590 // warning in the current document that this happened because
591 // Notifications require a user gesture.
592 if (!mHasValidTransientUserGestureActivation
&&
593 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
594 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
596 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
,
597 doc
, nsContentUtils::eDOM_PROPERTIES
,
598 "NotificationsRequireUserGesture");
602 mPermission
= Notification::TestPermission(mPrincipal
);
606 RefPtr
<NotificationPermissionCallback
> callback(mCallback
);
607 callback
->Call(mPermission
, error
);
608 rv
= error
.StealNSResult();
610 mPromise
->MaybeResolve(mPermission
);
614 // Observer that the alert service calls to do common tasks and/or dispatch to
615 // the specific observer for the context e.g. main thread, worker, or service
617 class NotificationObserver final
: public nsIObserver
{
619 nsCOMPtr
<nsIObserver
> mObserver
;
620 nsCOMPtr
<nsIPrincipal
> mPrincipal
;
621 bool mInPrivateBrowsing
;
625 NotificationObserver(nsIObserver
* aObserver
, nsIPrincipal
* aPrincipal
,
626 bool aInPrivateBrowsing
)
627 : mObserver(aObserver
),
628 mPrincipal(aPrincipal
),
629 mInPrivateBrowsing(aInPrivateBrowsing
) {
630 AssertIsOnMainThread();
631 MOZ_ASSERT(mObserver
);
632 MOZ_ASSERT(mPrincipal
);
636 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
638 nsresult
AdjustPushQuota(const char* aTopic
);
641 NS_IMPL_ISUPPORTS(NotificationObserver
, nsIObserver
)
643 class MainThreadNotificationObserver
: public nsIObserver
{
645 UniquePtr
<NotificationRef
> mNotificationRef
;
649 explicit MainThreadNotificationObserver(UniquePtr
<NotificationRef
> aRef
)
650 : mNotificationRef(std::move(aRef
)) {
651 AssertIsOnMainThread();
655 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
658 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver
, nsIObserver
)
661 NotificationTask::Run() {
662 AssertIsOnMainThread();
664 // Get a pointer to notification before the notification takes ownership of
665 // the ref (it owns itself temporarily, with ShowInternal() and
666 // CloseInternal() passing on the ownership appropriately.)
667 Notification
* notif
= mNotificationRef
->GetNotification();
668 notif
->mTempRef
.swap(mNotificationRef
);
669 if (mAction
== eShow
) {
670 notif
->ShowInternal();
671 } else if (mAction
== eClose
) {
672 notif
->CloseInternal();
674 MOZ_CRASH("Invalid action");
677 MOZ_ASSERT(!mNotificationRef
);
682 bool Notification::PrefEnabled(JSContext
* aCx
, JSObject
* aObj
) {
683 return StaticPrefs::dom_webnotifications_enabled();
686 Notification::Notification(nsIGlobalObject
* aGlobal
, const nsAString
& aID
,
687 const nsAString
& aTitle
, const nsAString
& aBody
,
688 NotificationDirection aDir
, const nsAString
& aLang
,
689 const nsAString
& aTag
, const nsAString
& aIconUrl
,
690 bool aRequireInteraction
, bool aSilent
,
691 nsTArray
<uint32_t>&& aVibrate
,
692 const NotificationBehavior
& aBehavior
)
693 : DOMEventTargetHelper(aGlobal
),
694 mWorkerPrivate(nullptr),
703 mRequireInteraction(aRequireInteraction
),
705 mVibrate(std::move(aVibrate
)),
706 mBehavior(aBehavior
),
707 mData(JS::NullValue()),
711 if (!NS_IsMainThread()) {
712 mWorkerPrivate
= GetCurrentThreadWorkerPrivate();
713 MOZ_ASSERT(mWorkerPrivate
);
717 nsresult
Notification::MaybeObserveWindowFrozenOrDestroyed() {
718 // NOTE: Non-persistent notifications can also be opened from workers, but we
719 // don't care and nobody else cares. And it's not clear whether we even should
720 // do this for window at all, see
721 // https://github.com/whatwg/notifications/issues/204.
722 // TODO: Somehow extend GlobalTeardownObserver to deal with FROZEN_TOPIC?
723 if (!mWorkerPrivate
) {
724 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
725 NS_ENSURE_TRUE(obs
, NS_ERROR_FAILURE
);
727 nsresult rv
= obs
->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC
, true);
728 NS_ENSURE_SUCCESS(rv
, rv
);
730 rv
= obs
->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC
, true);
731 NS_ENSURE_SUCCESS(rv
, rv
);
737 void Notification::SetAlertName() {
738 AssertIsOnMainThread();
739 if (!mAlertName
.IsEmpty()) {
743 nsAutoString alertName
;
744 nsresult rv
= GetOrigin(GetPrincipal(), alertName
);
745 if (NS_WARN_IF(NS_FAILED(rv
))) {
749 // Get the notification name that is unique per origin + tag/ID.
750 // The name of the alert is of the form origin#tag/ID.
751 alertName
.Append('#');
752 if (!mTag
.IsEmpty()) {
753 alertName
.AppendLiteral("tag:");
754 alertName
.Append(mTag
);
756 alertName
.AppendLiteral("notag:");
757 alertName
.Append(mID
);
760 mAlertName
= alertName
;
763 // May be called on any thread.
765 already_AddRefed
<Notification
> Notification::Constructor(
766 const GlobalObject
& aGlobal
, const nsAString
& aTitle
,
767 const NotificationOptions
& aOptions
, ErrorResult
& aRv
) {
768 // FIXME(nsm): If the sticky flag is set, throw an error.
769 RefPtr
<ServiceWorkerGlobalScope
> scope
;
770 UNWRAP_OBJECT(ServiceWorkerGlobalScope
, aGlobal
.Get(), scope
);
773 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
774 "Use registration.showNotification() instead.");
778 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
779 RefPtr
<Notification
> notification
=
780 CreateAndShow(aGlobal
.Context(), global
, aTitle
, aOptions
, u
""_ns
, aRv
);
781 if (NS_WARN_IF(aRv
.Failed())) {
785 NS_FAILED(notification
->MaybeObserveWindowFrozenOrDestroyed()))) {
789 // This is be ok since we are on the worker thread where this function will
790 // run to completion before the Notification has a chance to go away.
791 return notification
.forget();
795 Result
<already_AddRefed
<Notification
>, QMResult
>
796 Notification::ConstructFromFields(
797 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
798 const nsAString
& aDir
, const nsAString
& aLang
, const nsAString
& aBody
,
799 const nsAString
& aTag
, const nsAString
& aIcon
, const nsAString
& aData
,
800 const nsAString
& aServiceWorkerRegistrationScope
) {
803 RootedDictionary
<NotificationOptions
> options(RootingCx());
804 options
.mDir
= Notification::StringToDirection(nsString(aDir
));
805 options
.mLang
= aLang
;
806 options
.mBody
= aBody
;
808 options
.mIcon
= aIcon
;
809 IgnoredErrorResult rv
;
810 RefPtr
<Notification
> notification
=
811 CreateInternal(aGlobal
, aID
, aTitle
, options
, rv
);
812 if (NS_WARN_IF(rv
.Failed())) {
813 return Err(ToQMResult(NS_ERROR_FAILURE
));
816 QM_TRY(notification
->InitFromBase64(aData
));
818 notification
->SetScope(aServiceWorkerRegistrationScope
);
820 return notification
.forget();
823 nsresult
Notification::PersistNotification() {
824 AssertIsOnMainThread();
826 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
827 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
833 rv
= GetOrigin(GetPrincipal(), origin
);
834 if (NS_WARN_IF(NS_FAILED(rv
))) {
842 GetAlertName(alertName
);
844 nsAutoString behavior
;
845 if (!mBehavior
.ToJSON(behavior
)) {
846 return NS_ERROR_FAILURE
;
849 rv
= notificationStorage
->Put(origin
, id
, mTitle
, DirectionToString(mDir
),
850 mLang
, mBody
, mTag
, mIconUrl
, alertName
,
851 mDataAsBase64
, behavior
, mScope
);
857 SetStoredState(true);
861 void Notification::UnpersistNotification() {
862 AssertIsOnMainThread();
864 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
865 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
);
866 if (notificationStorage
) {
868 nsresult rv
= GetOrigin(GetPrincipal(), origin
);
869 if (NS_SUCCEEDED(rv
)) {
870 notificationStorage
->Delete(origin
, mID
);
873 SetStoredState(false);
877 already_AddRefed
<Notification
> Notification::CreateInternal(
878 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
879 const NotificationOptions
& aOptions
, ErrorResult
& aRv
) {
882 if (!aID
.IsEmpty()) {
885 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
886 do_GetService("@mozilla.org/uuid-generator;1");
887 NS_ENSURE_TRUE(uuidgen
, nullptr);
889 rv
= uuidgen
->GenerateUUIDInPlace(&uuid
);
890 NS_ENSURE_SUCCESS(rv
, nullptr);
892 char buffer
[NSID_LENGTH
];
893 uuid
.ToProvidedString(buffer
);
894 NS_ConvertASCIItoUTF16
convertedID(buffer
);
899 if (StaticPrefs::dom_webnotifications_silent_enabled()) {
900 silent
= aOptions
.mSilent
;
903 nsTArray
<uint32_t> vibrate
;
904 if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
905 aOptions
.mVibrate
.WasPassed()) {
908 "Silent notifications must not specify vibration patterns.");
912 const OwningUnsignedLongOrUnsignedLongSequence
& value
=
913 aOptions
.mVibrate
.Value();
914 if (value
.IsUnsignedLong()) {
915 AutoTArray
<uint32_t, 1> array
;
916 array
.AppendElement(value
.GetAsUnsignedLong());
917 vibrate
= SanitizeVibratePattern(array
);
919 vibrate
= SanitizeVibratePattern(value
.GetAsUnsignedLongSequence());
923 RefPtr
<Notification
> notification
= new Notification(
924 aGlobal
, id
, aTitle
, aOptions
.mBody
, aOptions
.mDir
, aOptions
.mLang
,
925 aOptions
.mTag
, aOptions
.mIcon
, aOptions
.mRequireInteraction
, silent
,
926 std::move(vibrate
), aOptions
.mMozbehavior
);
927 return notification
.forget();
930 Notification::~Notification() {
931 mozilla::DropJSObjects(this);
932 AssertIsOnTargetThread();
933 MOZ_ASSERT(!mWorkerRef
);
934 MOZ_ASSERT(!mTempRef
);
937 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification
)
938 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification
,
939 DOMEventTargetHelper
)
940 tmp
->mData
.setUndefined();
941 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
942 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
944 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification
,
945 DOMEventTargetHelper
)
946 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
948 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification
,
949 DOMEventTargetHelper
)
950 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData
)
951 NS_IMPL_CYCLE_COLLECTION_TRACE_END
953 NS_IMPL_ADDREF_INHERITED(Notification
, DOMEventTargetHelper
)
954 NS_IMPL_RELEASE_INHERITED(Notification
, DOMEventTargetHelper
)
956 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification
)
957 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
958 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
959 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
961 nsIPrincipal
* Notification::GetPrincipal() {
962 AssertIsOnMainThread();
963 if (mWorkerPrivate
) {
964 return mWorkerPrivate
->GetPrincipal();
966 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(GetOwner());
967 NS_ENSURE_TRUE(sop
, nullptr);
968 return sop
->GetPrincipal();
972 class WorkerNotificationObserver final
: public MainThreadNotificationObserver
{
974 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver
,
975 MainThreadNotificationObserver
)
978 explicit WorkerNotificationObserver(UniquePtr
<NotificationRef
> aRef
)
979 : MainThreadNotificationObserver(std::move(aRef
)) {
980 AssertIsOnMainThread();
981 MOZ_ASSERT(mNotificationRef
->GetNotification()->mWorkerPrivate
);
984 void ForgetNotification() {
985 AssertIsOnMainThread();
986 mNotificationRef
->Forget();
990 virtual ~WorkerNotificationObserver() {
991 AssertIsOnMainThread();
993 MOZ_ASSERT(mNotificationRef
);
994 Notification
* notification
= mNotificationRef
->GetNotification();
996 notification
->mObserver
= nullptr;
1001 class ServiceWorkerNotificationObserver final
: public nsIObserver
{
1006 ServiceWorkerNotificationObserver(
1007 const nsAString
& aScope
, nsIPrincipal
* aPrincipal
, const nsAString
& aID
,
1008 const nsAString
& aTitle
, const nsAString
& aDir
, const nsAString
& aLang
,
1009 const nsAString
& aBody
, const nsAString
& aTag
, const nsAString
& aIcon
,
1010 const nsAString
& aData
, const nsAString
& aBehavior
)
1013 mPrincipal(aPrincipal
),
1021 mBehavior(aBehavior
) {
1022 AssertIsOnMainThread();
1023 MOZ_ASSERT(aPrincipal
);
1027 ~ServiceWorkerNotificationObserver() = default;
1029 const nsString mScope
;
1031 nsCOMPtr
<nsIPrincipal
> mPrincipal
;
1032 const nsString mTitle
;
1033 const nsString mDir
;
1034 const nsString mLang
;
1035 const nsString mBody
;
1036 const nsString mTag
;
1037 const nsString mIcon
;
1038 const nsString mData
;
1039 const nsString mBehavior
;
1042 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver
, nsIObserver
)
1044 bool Notification::DispatchClickEvent() {
1045 AssertIsOnTargetThread();
1046 RefPtr
<Event
> event
= NS_NewDOMEvent(this, nullptr, nullptr);
1047 event
->InitEvent(u
"click"_ns
, false, true);
1048 event
->SetTrusted(true);
1049 WantsPopupControlCheck
popupControlCheck(event
);
1050 return DispatchEvent(*event
, CallerType::System
, IgnoreErrors());
1053 // Overrides dispatch and run handlers so we can directly dispatch from main
1054 // thread to child workers.
1055 class NotificationClickWorkerRunnable final
1056 : public NotificationWorkerRunnable
{
1057 Notification
* mNotification
;
1058 // Optional window that gets focused if click event is not
1059 // preventDefault()ed.
1060 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> mWindow
;
1063 NotificationClickWorkerRunnable(
1064 Notification
* aNotification
,
1065 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
1066 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
,
1067 "NotificationClickWorkerRunnable"),
1068 mNotification(aNotification
),
1070 MOZ_ASSERT_IF(mWorkerPrivate
->IsServiceWorker(), !mWindow
);
1073 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
1074 bool doDefaultAction
= mNotification
->DispatchClickEvent();
1075 MOZ_ASSERT_IF(mWorkerPrivate
->IsServiceWorker(), !doDefaultAction
);
1076 if (doDefaultAction
) {
1077 RefPtr
<FocusWindowRunnable
> r
= new FocusWindowRunnable(mWindow
);
1078 mWorkerPrivate
->DispatchToMainThread(r
.forget());
1084 NotificationObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
1085 const char16_t
* aData
) {
1086 AssertIsOnMainThread();
1088 if (!strcmp("alertdisablecallback", aTopic
)) {
1089 if (XRE_IsParentProcess()) {
1090 return Notification::RemovePermission(mPrincipal
);
1092 // Permissions can't be removed from the content process. Send a message
1093 // to the parent; `ContentParent::RecvDisableNotifications` will call
1094 // `RemovePermission`.
1095 ContentChild::GetSingleton()->SendDisableNotifications(mPrincipal
);
1097 } else if (!strcmp("alertsettingscallback", aTopic
)) {
1098 if (XRE_IsParentProcess()) {
1099 return Notification::OpenSettings(mPrincipal
);
1101 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1103 ContentChild::GetSingleton()->SendOpenNotificationSettings(mPrincipal
);
1105 } else if (!strcmp("alertshow", aTopic
) || !strcmp("alertfinished", aTopic
)) {
1106 Unused
<< NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic
)));
1109 return mObserver
->Observe(aSubject
, aTopic
, aData
);
1112 nsresult
NotificationObserver::AdjustPushQuota(const char* aTopic
) {
1113 nsCOMPtr
<nsIPushQuotaManager
> pushQuotaManager
=
1114 do_GetService("@mozilla.org/push/Service;1");
1115 if (!pushQuotaManager
) {
1116 return NS_ERROR_FAILURE
;
1119 nsAutoCString origin
;
1120 nsresult rv
= mPrincipal
->GetOrigin(origin
);
1121 if (NS_FAILED(rv
)) {
1125 if (!strcmp("alertshow", aTopic
)) {
1126 return pushQuotaManager
->NotificationForOriginShown(origin
.get());
1128 return pushQuotaManager
->NotificationForOriginClosed(origin
.get());
1131 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
1133 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1134 MainThreadNotificationObserver::Observe(nsISupports
* aSubject
,
1136 const char16_t
* aData
) {
1137 AssertIsOnMainThread();
1138 MOZ_ASSERT(mNotificationRef
);
1139 Notification
* notification
= mNotificationRef
->GetNotification();
1140 MOZ_ASSERT(notification
);
1141 if (!strcmp("alertclickcallback", aTopic
)) {
1142 nsCOMPtr
<nsPIDOMWindowInner
> window
= notification
->GetOwner();
1143 if (NS_WARN_IF(!window
|| !window
->IsCurrentInnerWindow())) {
1144 // Window has been closed, this observer is not valid anymore
1145 return NS_ERROR_FAILURE
;
1148 bool doDefaultAction
= notification
->DispatchClickEvent();
1149 if (doDefaultAction
) {
1150 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= window
->GetOuterWindow();
1151 nsFocusManager::FocusWindow(outerWindow
, CallerType::System
);
1153 } else if (!strcmp("alertfinished", aTopic
)) {
1154 notification
->UnpersistNotification();
1155 notification
->mIsClosed
= true;
1156 notification
->DispatchTrustedEvent(u
"close"_ns
);
1157 } else if (!strcmp("alertshow", aTopic
)) {
1158 notification
->DispatchTrustedEvent(u
"show"_ns
);
1164 WorkerNotificationObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
1165 const char16_t
* aData
) {
1166 AssertIsOnMainThread();
1167 MOZ_ASSERT(mNotificationRef
);
1168 // For an explanation of why it is OK to pass this rawptr to the event
1169 // runnables, see the Notification class comment.
1170 Notification
* notification
= mNotificationRef
->GetNotification();
1171 // We can't assert notification here since the feature could've unset it.
1172 if (NS_WARN_IF(!notification
)) {
1173 return NS_ERROR_FAILURE
;
1176 MOZ_ASSERT(notification
->mWorkerPrivate
);
1178 RefPtr
<WorkerRunnable
> r
;
1179 if (!strcmp("alertclickcallback", aTopic
)) {
1180 nsPIDOMWindowInner
* window
= nullptr;
1181 if (!notification
->mWorkerPrivate
->IsServiceWorker()) {
1182 WorkerPrivate
* top
= notification
->mWorkerPrivate
;
1183 while (top
->GetParent()) {
1184 top
= top
->GetParent();
1187 window
= top
->GetWindow();
1188 if (NS_WARN_IF(!window
|| !window
->IsCurrentInnerWindow())) {
1189 // Window has been closed, this observer is not valid anymore
1190 return NS_ERROR_FAILURE
;
1194 // Instead of bothering with adding features and other worker lifecycle
1195 // management, we simply hold strongrefs to the window and document.
1196 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> windowHandle(
1197 new nsMainThreadPtrHolder
<nsPIDOMWindowInner
>(
1198 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window
));
1200 r
= new NotificationClickWorkerRunnable(notification
, windowHandle
);
1201 } else if (!strcmp("alertfinished", aTopic
)) {
1202 notification
->UnpersistNotification();
1203 notification
->mIsClosed
= true;
1204 r
= new NotificationEventWorkerRunnable(notification
, u
"close"_ns
);
1205 } else if (!strcmp("alertshow", aTopic
)) {
1206 r
= new NotificationEventWorkerRunnable(notification
, u
"show"_ns
);
1210 if (!r
->Dispatch()) {
1211 NS_WARNING("Could not dispatch event to worker notification");
1217 ServiceWorkerNotificationObserver::Observe(nsISupports
* aSubject
,
1219 const char16_t
* aData
) {
1220 AssertIsOnMainThread();
1222 nsAutoCString originSuffix
;
1223 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
1224 if (NS_WARN_IF(NS_FAILED(rv
))) {
1228 if (!strcmp("alertclickcallback", aTopic
)) {
1229 if (XRE_IsParentProcess()) {
1230 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
1231 mozilla::components::ServiceWorkerManager::Service();
1232 if (NS_WARN_IF(!swm
)) {
1233 return NS_ERROR_FAILURE
;
1236 rv
= swm
->SendNotificationClickEvent(
1237 originSuffix
, NS_ConvertUTF16toUTF8(mScope
), mID
, mTitle
, mDir
, mLang
,
1238 mBody
, mTag
, mIcon
, mData
, mBehavior
);
1239 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1241 auto* cc
= ContentChild::GetSingleton();
1242 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1243 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1245 Unused
<< cc
->SendNotificationEvent(u
"click"_ns
, data
);
1250 if (!strcmp("alertfinished", aTopic
)) {
1252 nsresult rv
= Notification::GetOrigin(mPrincipal
, origin
);
1253 if (NS_WARN_IF(NS_FAILED(rv
))) {
1257 // Remove closed or dismissed persistent notifications.
1258 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
1259 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
);
1260 if (notificationStorage
) {
1261 notificationStorage
->Delete(origin
, mID
);
1264 if (XRE_IsParentProcess()) {
1265 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
1266 mozilla::components::ServiceWorkerManager::Service();
1267 if (NS_WARN_IF(!swm
)) {
1268 return NS_ERROR_FAILURE
;
1271 rv
= swm
->SendNotificationCloseEvent(
1272 originSuffix
, NS_ConvertUTF16toUTF8(mScope
), mID
, mTitle
, mDir
, mLang
,
1273 mBody
, mTag
, mIcon
, mData
, mBehavior
);
1274 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1276 auto* cc
= ContentChild::GetSingleton();
1277 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1278 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1280 Unused
<< cc
->SendNotificationEvent(u
"close"_ns
, data
);
1288 bool Notification::IsInPrivateBrowsing() {
1289 AssertIsOnMainThread();
1291 Document
* doc
= nullptr;
1293 if (mWorkerPrivate
) {
1294 doc
= mWorkerPrivate
->GetDocument();
1295 } else if (GetOwner()) {
1296 doc
= GetOwner()->GetExtantDoc();
1300 nsCOMPtr
<nsILoadContext
> loadContext
= doc
->GetLoadContext();
1301 return loadContext
&& loadContext
->UsePrivateBrowsing();
1304 if (mWorkerPrivate
) {
1305 // Not all workers may have a document, but with Bug 1107516 fixed, they
1306 // should all have a loadcontext.
1307 nsCOMPtr
<nsILoadGroup
> loadGroup
= mWorkerPrivate
->GetLoadGroup();
1308 nsCOMPtr
<nsILoadContext
> loadContext
;
1309 NS_QueryNotificationCallbacks(nullptr, loadGroup
,
1310 NS_GET_IID(nsILoadContext
),
1311 getter_AddRefs(loadContext
));
1312 return loadContext
&& loadContext
->UsePrivateBrowsing();
1315 // XXXnsm Should this default to true?
1319 void Notification::ShowInternal() {
1320 AssertIsOnMainThread();
1321 MOZ_ASSERT(mTempRef
,
1322 "Notification should take ownership of itself before"
1323 "calling ShowInternal!");
1324 // A notification can only have one observer and one call to ShowInternal.
1325 MOZ_ASSERT(!mObserver
);
1327 // Transfer ownership to local scope so we can either release it at the end
1328 // of this function or transfer it to the observer.
1329 UniquePtr
<NotificationRef
> ownership
;
1330 std::swap(ownership
, mTempRef
);
1331 MOZ_ASSERT(ownership
->GetNotification() == this);
1333 nsresult rv
= PersistNotification();
1334 if (NS_FAILED(rv
)) {
1335 NS_WARNING("Could not persist Notification");
1338 nsCOMPtr
<nsIAlertsService
> alertService
= components::Alerts::Service();
1341 NotificationPermission permission
= NotificationPermission::Denied
;
1342 if (mWorkerPrivate
) {
1343 permission
= GetPermissionInternal(mWorkerPrivate
->GetPrincipal(), result
);
1345 permission
= GetPermissionInternal(GetOwner(), result
);
1347 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1348 MOZ_ASSERT_IF(result
.Failed(), permission
== NotificationPermission::Denied
);
1349 result
.SuppressException();
1350 if (permission
!= NotificationPermission::Granted
|| !alertService
) {
1351 if (mWorkerPrivate
) {
1352 RefPtr
<NotificationEventWorkerRunnable
> r
=
1353 new NotificationEventWorkerRunnable(this, u
"error"_ns
);
1354 if (!r
->Dispatch()) {
1355 NS_WARNING("Could not dispatch event to worker notification");
1358 DispatchTrustedEvent(u
"error"_ns
);
1363 nsAutoString iconUrl
;
1364 nsAutoString soundUrl
;
1365 ResolveIconAndSoundURL(iconUrl
, soundUrl
);
1367 bool isPersistent
= false;
1368 nsCOMPtr
<nsIObserver
> observer
;
1369 if (mScope
.IsEmpty()) {
1370 // Ownership passed to observer.
1371 if (mWorkerPrivate
) {
1372 // Scope better be set on ServiceWorker initiated requests.
1373 MOZ_ASSERT(!mWorkerPrivate
->IsServiceWorker());
1374 // Keep a pointer so that the feature can tell the observer not to release
1375 // the notification.
1376 mObserver
= new WorkerNotificationObserver(std::move(ownership
));
1377 observer
= mObserver
;
1379 observer
= new MainThreadNotificationObserver(std::move(ownership
));
1382 isPersistent
= true;
1383 // This observer does not care about the Notification. It will be released
1384 // at the end of this function.
1386 // The observer is wholly owned by the NotificationObserver passed to the
1388 nsAutoString behavior
;
1389 if (NS_WARN_IF(!mBehavior
.ToJSON(behavior
))) {
1390 behavior
.Truncate();
1392 observer
= new ServiceWorkerNotificationObserver(
1393 mScope
, GetPrincipal(), mID
, mTitle
, DirectionToString(mDir
), mLang
,
1394 mBody
, mTag
, iconUrl
, mDataAsBase64
, behavior
);
1396 MOZ_ASSERT(observer
);
1397 nsCOMPtr
<nsIObserver
> alertObserver
=
1398 new NotificationObserver(observer
, GetPrincipal(), IsInPrivateBrowsing());
1400 // In the case of IPC, the parent process uses the cookie to map to
1401 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1402 nsString uniqueCookie
= u
"notification:"_ns
;
1403 uniqueCookie
.AppendInt(sCount
++);
1404 bool inPrivateBrowsing
= IsInPrivateBrowsing();
1406 bool requireInteraction
= mRequireInteraction
;
1407 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1408 requireInteraction
= false;
1411 nsAutoString alertName
;
1412 GetAlertName(alertName
);
1413 nsCOMPtr
<nsIAlertNotification
> alert
=
1414 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID
);
1415 NS_ENSURE_TRUE_VOID(alert
);
1416 nsIPrincipal
* principal
= GetPrincipal();
1418 alert
->Init(alertName
, iconUrl
, mTitle
, mBody
, true, uniqueCookie
,
1419 DirectionToString(mDir
), mLang
, mDataAsBase64
, GetPrincipal(),
1420 inPrivateBrowsing
, requireInteraction
, mSilent
, mVibrate
);
1421 NS_ENSURE_SUCCESS_VOID(rv
);
1424 JSONStringWriteFunc
<nsAutoCString
> persistentData
;
1425 JSONWriter
w(persistentData
);
1428 nsAutoString origin
;
1429 Notification::GetOrigin(principal
, origin
);
1430 w
.StringProperty("origin", NS_ConvertUTF16toUTF8(origin
));
1432 w
.StringProperty("id", NS_ConvertUTF16toUTF8(mID
));
1434 nsAutoCString originSuffix
;
1435 principal
->GetOriginSuffix(originSuffix
);
1436 w
.StringProperty("originSuffix", originSuffix
);
1440 alertService
->ShowPersistentNotification(
1441 NS_ConvertUTF8toUTF16(persistentData
.StringCRef()), alert
,
1444 alertService
->ShowAlert(alert
, alertObserver
);
1449 bool Notification::RequestPermissionEnabledForScope(JSContext
* aCx
,
1450 JSObject
* /* unused */) {
1451 // requestPermission() is not allowed on workers. The calling page should ask
1452 // for permission on the worker's behalf. This is to prevent 'which window
1453 // should show the browser pop-up'. See discussion:
1454 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1455 return NS_IsMainThread();
1459 already_AddRefed
<Promise
> Notification::RequestPermission(
1460 const GlobalObject
& aGlobal
,
1461 const Optional
<OwningNonNull
<NotificationPermissionCallback
> >& aCallback
,
1463 AssertIsOnMainThread();
1465 // Get principal from global to make permission request for notifications.
1466 nsCOMPtr
<nsPIDOMWindowInner
> window
=
1467 do_QueryInterface(aGlobal
.GetAsSupports());
1468 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
=
1469 do_QueryInterface(aGlobal
.GetAsSupports());
1470 if (!sop
|| !window
) {
1471 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1475 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1477 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1481 RefPtr
<Promise
> promise
= Promise::Create(window
->AsGlobal(), aRv
);
1485 NotificationPermissionCallback
* permissionCallback
= nullptr;
1486 if (aCallback
.WasPassed()) {
1487 permissionCallback
= &aCallback
.Value();
1489 nsCOMPtr
<nsIRunnable
> request
= new NotificationPermissionRequest(
1490 principal
, window
, promise
, permissionCallback
);
1492 window
->AsGlobal()->Dispatch(request
.forget());
1494 return promise
.forget();
1498 NotificationPermission
Notification::GetPermission(const GlobalObject
& aGlobal
,
1500 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1501 return GetPermission(global
, aRv
);
1505 NotificationPermission
Notification::GetPermission(nsIGlobalObject
* aGlobal
,
1507 if (NS_IsMainThread()) {
1508 return GetPermissionInternal(aGlobal
->GetAsInnerWindow(), aRv
);
1510 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1512 RefPtr
<GetPermissionRunnable
> r
= new GetPermissionRunnable(worker
);
1513 r
->Dispatch(Canceling
, aRv
);
1515 return NotificationPermission::Denied
;
1518 return r
->GetPermission();
1523 NotificationPermission
Notification::GetPermissionInternal(
1524 nsPIDOMWindowInner
* aWindow
, ErrorResult
& aRv
) {
1525 // Get principal from global to check permission for notifications.
1526 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
1528 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1529 return NotificationPermission::Denied
;
1532 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1534 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1535 return NotificationPermission::Denied
;
1538 if (principal
->GetPrivateBrowsingId() != 0 &&
1540 dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
1541 return NotificationPermission::Denied
;
1543 // Disallow showing notification if our origin is not the same origin as the
1544 // toplevel one, see https://github.com/whatwg/notifications/issues/177.
1545 if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) {
1546 nsCOMPtr
<nsIScriptObjectPrincipal
> topSop
=
1547 do_QueryInterface(aWindow
->GetBrowsingContext()->Top()->GetDOMWindow());
1548 nsIPrincipal
* topPrincipal
= topSop
? topSop
->GetPrincipal() : nullptr;
1549 if (!topPrincipal
|| !principal
->Subsumes(topPrincipal
)) {
1550 return NotificationPermission::Denied
;
1554 return GetPermissionInternal(principal
, aRv
);
1558 NotificationPermission
Notification::GetPermissionInternal(
1559 nsIPrincipal
* aPrincipal
, ErrorResult
& aRv
) {
1560 AssertIsOnMainThread();
1561 MOZ_ASSERT(aPrincipal
);
1563 if (aPrincipal
->IsSystemPrincipal()) {
1564 return NotificationPermission::Granted
;
1566 // Allow files to show notifications by default.
1567 if (aPrincipal
->SchemeIs("file")) {
1568 return NotificationPermission::Granted
;
1572 // We also allow notifications is they are pref'ed on.
1573 if (Preferences::GetBool("notification.prompt.testing", false)) {
1574 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1575 return NotificationPermission::Granted
;
1577 return NotificationPermission::Denied
;
1581 return TestPermission(aPrincipal
);
1585 NotificationPermission
Notification::TestPermission(nsIPrincipal
* aPrincipal
) {
1586 AssertIsOnMainThread();
1588 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
1590 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
1591 components::PermissionManager::Service();
1592 if (!permissionManager
) {
1593 return NotificationPermission::Default
;
1596 permissionManager
->TestExactPermissionFromPrincipal(
1597 aPrincipal
, "desktop-notification"_ns
, &permission
);
1599 // Convert the result to one of the enum types.
1600 switch (permission
) {
1601 case nsIPermissionManager::ALLOW_ACTION
:
1602 return NotificationPermission::Granted
;
1603 case nsIPermissionManager::DENY_ACTION
:
1604 return NotificationPermission::Denied
;
1606 return NotificationPermission::Default
;
1610 nsresult
Notification::ResolveIconAndSoundURL(nsString
& iconUrl
,
1611 nsString
& soundUrl
) {
1612 AssertIsOnMainThread();
1613 nsresult rv
= NS_OK
;
1615 nsIURI
* baseUri
= nullptr;
1617 // XXXnsm If I understand correctly, the character encoding for resolving
1618 // URIs in new specs is dictated by the URL spec, which states that unless
1619 // the URL parser is passed an override encoding, the charset to be used is
1620 // UTF-8. The new Notification icon/sound specification just says to use the
1621 // Fetch API, where the Request constructor defers to URL parsing specifying
1622 // the API base URL and no override encoding. So we've to use UTF-8 on
1623 // workers, but for backwards compat keeping it document charset on main
1625 auto encoding
= UTF_8_ENCODING
;
1627 if (mWorkerPrivate
) {
1628 baseUri
= mWorkerPrivate
->GetBaseURI();
1630 Document
* doc
= GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1632 baseUri
= doc
->GetBaseURI();
1633 encoding
= doc
->GetDocumentCharacterSet();
1635 NS_WARNING("No document found for main thread notification!");
1636 return NS_ERROR_FAILURE
;
1641 if (mIconUrl
.Length() > 0) {
1642 nsCOMPtr
<nsIURI
> srcUri
;
1643 rv
= NS_NewURI(getter_AddRefs(srcUri
), mIconUrl
, encoding
, baseUri
);
1644 if (NS_SUCCEEDED(rv
)) {
1646 srcUri
->GetSpec(src
);
1647 CopyUTF8toUTF16(src
, iconUrl
);
1650 if (mBehavior
.mSoundFile
.Length() > 0) {
1651 nsCOMPtr
<nsIURI
> srcUri
;
1652 rv
= NS_NewURI(getter_AddRefs(srcUri
), mBehavior
.mSoundFile
, encoding
,
1654 if (NS_SUCCEEDED(rv
)) {
1656 srcUri
->GetSpec(src
);
1657 CopyUTF8toUTF16(src
, soundUrl
);
1665 already_AddRefed
<Promise
> Notification::Get(
1666 nsPIDOMWindowInner
* aWindow
, const GetNotificationOptions
& aFilter
,
1667 const nsAString
& aScope
, ErrorResult
& aRv
) {
1668 AssertIsOnMainThread();
1669 MOZ_ASSERT(aWindow
);
1671 nsCOMPtr
<Document
> doc
= aWindow
->GetExtantDoc();
1673 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1678 aRv
= GetOrigin(doc
->NodePrincipal(), origin
);
1683 RefPtr
<Promise
> promise
= Promise::Create(aWindow
->AsGlobal(), aRv
);
1688 nsCOMPtr
<nsINotificationStorageCallback
> callback
=
1689 new NotificationStorageCallback(aWindow
->AsGlobal(), aScope
, promise
);
1691 RefPtr
<NotificationGetRunnable
> r
=
1692 new NotificationGetRunnable(origin
, aFilter
.mTag
, callback
);
1694 aRv
= aWindow
->AsGlobal()->Dispatch(r
.forget());
1695 if (NS_WARN_IF(aRv
.Failed())) {
1699 return promise
.forget();
1702 class WorkerGetResultRunnable final
: public NotificationWorkerRunnable
{
1703 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1704 const nsTArray
<NotificationStrings
> mStrings
;
1707 WorkerGetResultRunnable(WorkerPrivate
* aWorkerPrivate
,
1708 PromiseWorkerProxy
* aPromiseProxy
,
1709 nsTArray
<NotificationStrings
>&& aStrings
)
1710 : NotificationWorkerRunnable(aWorkerPrivate
, "WorkerGetResultRunnable"),
1711 mPromiseProxy(aPromiseProxy
),
1712 mStrings(std::move(aStrings
)) {}
1714 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
1715 RefPtr
<Promise
> workerPromise
= mPromiseProxy
->GetWorkerPromise();
1716 // Once Worker had already started shutdown, workerPromise would be nullptr
1717 if (!workerPromise
) {
1721 AutoTArray
<RefPtr
<Notification
>, 5> notifications
;
1722 for (uint32_t i
= 0; i
< mStrings
.Length(); ++i
) {
1723 auto result
= Notification::ConstructFromFields(
1724 aWorkerPrivate
->GlobalScope(), mStrings
[i
].mID
, mStrings
[i
].mTitle
,
1725 mStrings
[i
].mDir
, mStrings
[i
].mLang
, mStrings
[i
].mBody
,
1726 mStrings
[i
].mTag
, mStrings
[i
].mIcon
, mStrings
[i
].mData
,
1727 /* mStrings[i].mBehavior, not
1729 mStrings
[i
].mServiceWorkerRegistrationScope
);
1730 if (result
.isErr()) {
1733 RefPtr
<Notification
> n
= result
.unwrap();
1734 n
->SetStoredState(true);
1735 notifications
.AppendElement(n
.forget());
1738 workerPromise
->MaybeResolve(notifications
);
1739 mPromiseProxy
->CleanUp();
1743 class WorkerGetCallback final
: public ScopeCheckingGetCallback
{
1744 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1749 WorkerGetCallback(PromiseWorkerProxy
* aProxy
, const nsAString
& aScope
)
1750 : ScopeCheckingGetCallback(aScope
), mPromiseProxy(aProxy
) {
1751 AssertIsOnMainThread();
1755 NS_IMETHOD
Done() final
{
1756 AssertIsOnMainThread();
1757 MOZ_ASSERT(mPromiseProxy
, "Was Done() called twice?");
1759 RefPtr
<PromiseWorkerProxy
> proxy
= std::move(mPromiseProxy
);
1760 MutexAutoLock
lock(proxy
->Lock());
1761 if (proxy
->CleanedUp()) {
1765 RefPtr
<WorkerGetResultRunnable
> r
= new WorkerGetResultRunnable(
1766 proxy
->GetWorkerPrivate(), proxy
, std::move(mStrings
));
1773 ~WorkerGetCallback() = default;
1776 NS_IMPL_ISUPPORTS(WorkerGetCallback
, nsINotificationStorageCallback
)
1778 class WorkerGetRunnable final
: public Runnable
{
1779 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1780 const nsString mTag
;
1781 const nsString mScope
;
1784 WorkerGetRunnable(PromiseWorkerProxy
* aProxy
, const nsAString
& aTag
,
1785 const nsAString
& aScope
)
1786 : Runnable("WorkerGetRunnable"),
1787 mPromiseProxy(aProxy
),
1790 MOZ_ASSERT(mPromiseProxy
);
1795 AssertIsOnMainThread();
1796 nsCOMPtr
<nsINotificationStorageCallback
> callback
=
1797 new WorkerGetCallback(mPromiseProxy
, mScope
);
1800 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
1801 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
1802 if (NS_WARN_IF(NS_FAILED(rv
))) {
1807 MutexAutoLock
lock(mPromiseProxy
->Lock());
1808 if (mPromiseProxy
->CleanedUp()) {
1813 rv
= Notification::GetOrigin(
1814 mPromiseProxy
->GetWorkerPrivate()->GetPrincipal(), origin
);
1815 if (NS_WARN_IF(NS_FAILED(rv
))) {
1820 rv
= notificationStorage
->Get(origin
, mTag
, callback
);
1821 if (NS_WARN_IF(NS_FAILED(rv
))) {
1830 ~WorkerGetRunnable() = default;
1834 already_AddRefed
<Promise
> Notification::WorkerGet(
1835 WorkerPrivate
* aWorkerPrivate
, const GetNotificationOptions
& aFilter
,
1836 const nsAString
& aScope
, ErrorResult
& aRv
) {
1837 MOZ_ASSERT(aWorkerPrivate
);
1838 aWorkerPrivate
->AssertIsOnWorkerThread();
1839 RefPtr
<Promise
> p
= Promise::Create(aWorkerPrivate
->GlobalScope(), aRv
);
1840 if (NS_WARN_IF(aRv
.Failed())) {
1844 RefPtr
<PromiseWorkerProxy
> proxy
=
1845 PromiseWorkerProxy::Create(aWorkerPrivate
, p
);
1847 aRv
.Throw(NS_ERROR_DOM_ABORT_ERR
);
1851 RefPtr
<WorkerGetRunnable
> r
=
1852 new WorkerGetRunnable(proxy
, aFilter
.mTag
, aScope
);
1853 // Since this is called from script via
1854 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1855 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate
->DispatchToMainThread(r
.forget()));
1859 JSObject
* Notification::WrapObject(JSContext
* aCx
,
1860 JS::Handle
<JSObject
*> aGivenProto
) {
1861 return mozilla::dom::Notification_Binding::Wrap(aCx
, this, aGivenProto
);
1864 void Notification::Close() {
1865 AssertIsOnTargetThread();
1866 auto ref
= MakeUnique
<NotificationRef
>(this);
1867 if (!ref
->Initialized()) {
1871 nsCOMPtr
<nsIRunnable
> closeNotificationTask
= new NotificationTask(
1872 "Notification::Close", std::move(ref
), NotificationTask::eClose
);
1873 nsresult rv
= DispatchToMainThread(closeNotificationTask
.forget());
1875 if (NS_FAILED(rv
)) {
1876 DispatchTrustedEvent(u
"error"_ns
);
1877 // If dispatch fails, NotificationTask will release the ref when it goes
1878 // out of scope at the end of this function.
1882 void Notification::CloseInternal(bool aContextClosed
) {
1883 AssertIsOnMainThread();
1884 // Transfer ownership (if any) to local scope so we can release it at the end
1885 // of this function. This is relevant when the call is from
1886 // NotificationTask::Run().
1887 UniquePtr
<NotificationRef
> ownership
;
1888 std::swap(ownership
, mTempRef
);
1891 UnpersistNotification();
1893 nsCOMPtr
<nsIAlertsService
> alertService
= components::Alerts::Service();
1895 nsAutoString alertName
;
1896 GetAlertName(alertName
);
1897 alertService
->CloseAlert(alertName
, aContextClosed
);
1902 nsresult
Notification::GetOrigin(nsIPrincipal
* aPrincipal
, nsString
& aOrigin
) {
1904 return NS_ERROR_FAILURE
;
1908 nsContentUtils::GetWebExposedOriginSerialization(aPrincipal
, aOrigin
);
1909 NS_ENSURE_SUCCESS(rv
, rv
);
1914 bool Notification::RequireInteraction() const { return mRequireInteraction
; }
1916 bool Notification::Silent() const { return mSilent
; }
1918 void Notification::GetVibrate(nsTArray
<uint32_t>& aRetval
) const {
1919 aRetval
= mVibrate
.Clone();
1922 void Notification::GetData(JSContext
* aCx
,
1923 JS::MutableHandle
<JS::Value
> aRetval
) {
1924 if (mData
.isNull() && !mDataAsBase64
.IsEmpty()) {
1926 RefPtr
<nsStructuredCloneContainer
> container
=
1927 new nsStructuredCloneContainer();
1928 rv
= container
->InitFromBase64(mDataAsBase64
, JS_STRUCTURED_CLONE_VERSION
);
1929 if (NS_WARN_IF(NS_FAILED(rv
))) {
1934 JS::Rooted
<JS::Value
> data(aCx
);
1935 rv
= container
->DeserializeToJsval(aCx
, &data
);
1936 if (NS_WARN_IF(NS_FAILED(rv
))) {
1941 if (data
.isGCThing()) {
1942 mozilla::HoldJSObjects(this);
1946 if (mData
.isNull()) {
1954 void Notification::InitFromJSVal(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
1956 if (!mDataAsBase64
.IsEmpty() || aData
.isNull()) {
1959 RefPtr
<nsStructuredCloneContainer
> dataObjectContainer
=
1960 new nsStructuredCloneContainer();
1961 aRv
= dataObjectContainer
->InitFromJSVal(aData
, aCx
);
1962 if (NS_WARN_IF(aRv
.Failed())) {
1966 aRv
= dataObjectContainer
->GetDataAsBase64(mDataAsBase64
);
1967 if (NS_WARN_IF(aRv
.Failed())) {
1972 Result
<Ok
, QMResult
> Notification::InitFromBase64(const nsAString
& aData
) {
1973 MOZ_ASSERT(mDataAsBase64
.IsEmpty());
1974 if (aData
.IsEmpty()) {
1975 // No data; skipping
1979 // To and fro to ensure it is valid base64.
1980 RefPtr
<nsStructuredCloneContainer
> container
=
1981 new nsStructuredCloneContainer();
1982 QM_TRY(QM_TO_RESULT(
1983 container
->InitFromBase64(aData
, JS_STRUCTURED_CLONE_VERSION
)));
1984 QM_TRY(QM_TO_RESULT(container
->GetDataAsBase64(mDataAsBase64
)));
1989 bool Notification::AddRefObject() {
1990 AssertIsOnTargetThread();
1991 MOZ_ASSERT_IF(mWorkerPrivate
&& !mWorkerRef
, mTaskCount
== 0);
1992 MOZ_ASSERT_IF(mWorkerPrivate
&& mWorkerRef
, mTaskCount
> 0);
1993 if (mWorkerPrivate
&& !mWorkerRef
) {
1994 if (!CreateWorkerRef()) {
2003 void Notification::ReleaseObject() {
2004 AssertIsOnTargetThread();
2005 MOZ_ASSERT(mTaskCount
> 0);
2006 MOZ_ASSERT_IF(mWorkerPrivate
, mWorkerRef
);
2009 if (mWorkerPrivate
&& mTaskCount
== 0) {
2010 MOZ_ASSERT(mWorkerRef
);
2011 mWorkerRef
= nullptr;
2017 * Called from the worker, runs on main thread, blocks worker.
2019 * We can freely access mNotification here because the feature supplied it and
2020 * the Notification owns the feature.
2022 class CloseNotificationRunnable final
: public WorkerMainThreadRunnable
{
2023 Notification
* mNotification
;
2027 explicit CloseNotificationRunnable(Notification
* aNotification
)
2028 : WorkerMainThreadRunnable(aNotification
->mWorkerPrivate
,
2029 "Notification :: Close Notification"_ns
),
2030 mNotification(aNotification
),
2031 mHadObserver(false) {}
2033 bool MainThreadRun() override
{
2034 if (mNotification
->mObserver
) {
2035 // The Notify() take's responsibility of releasing the Notification.
2036 mNotification
->mObserver
->ForgetNotification();
2037 mNotification
->mObserver
= nullptr;
2038 mHadObserver
= true;
2040 mNotification
->CloseInternal();
2044 bool HadObserver() { return mHadObserver
; }
2047 bool Notification::CreateWorkerRef() {
2048 MOZ_ASSERT(mWorkerPrivate
);
2049 mWorkerPrivate
->AssertIsOnWorkerThread();
2050 MOZ_ASSERT(!mWorkerRef
);
2052 RefPtr
<Notification
> self
= this;
2054 StrongWorkerRef::Create(mWorkerPrivate
, "Notification", [self
]() {
2055 // CloseNotificationRunnable blocks the worker by pushing a sync event
2056 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2057 // the worker can still continue running. One of these is
2058 // ReleaseNotificationControlRunnable that releases the notification,
2059 // invalidating the notification and this feature. We hold this
2060 // reference to keep the notification valid until we are done with it.
2062 // An example of when the control runnable could get dispatched to the
2063 // worker is if a Notification is created and the worker is immediately
2064 // closed, but there is no permission to show it so that the main thread
2065 // immediately drops the NotificationRef. In this case, this function
2066 // blocks on the main thread, but the main thread dispatches the control
2067 // runnable, invalidating mNotification.
2069 // Dispatched to main thread, blocks on closing the Notification.
2070 RefPtr
<CloseNotificationRunnable
> r
=
2071 new CloseNotificationRunnable(self
);
2073 r
->Dispatch(Killing
, rv
);
2074 // XXXbz I'm told throwing and returning false from here is pointless
2075 // (and also that doing sync stuff from here is really weird), so I
2076 // guess we just suppress the exception on rv, if any.
2077 rv
.SuppressException();
2079 // Only call ReleaseObject() to match the observer's NotificationRef
2080 // ownership (since CloseNotificationRunnable asked the observer to drop
2081 // the reference to the notification).
2082 if (r
->HadObserver()) {
2083 self
->ReleaseObject();
2086 // From this point we cannot touch properties of this feature because
2087 // ReleaseObject() may have led to the notification going away and the
2088 // notification owns this feature!
2091 if (NS_WARN_IF(!mWorkerRef
)) {
2100 * 1) Is aWorker allowed to show a notification for scope?
2101 * 2) Is aWorker an active worker?
2103 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2105 class CheckLoadRunnable final
: public WorkerMainThreadRunnable
{
2108 ServiceWorkerRegistrationDescriptor mDescriptor
;
2111 explicit CheckLoadRunnable(
2112 WorkerPrivate
* aWorker
, const nsACString
& aScope
,
2113 const ServiceWorkerRegistrationDescriptor
& aDescriptor
)
2114 : WorkerMainThreadRunnable(aWorker
, "Notification :: Check Load"_ns
),
2115 mRv(NS_ERROR_DOM_SECURITY_ERR
),
2117 mDescriptor(aDescriptor
) {}
2119 bool MainThreadRun() override
{
2120 nsIPrincipal
* principal
= mWorkerPrivate
->GetPrincipal();
2121 mRv
= CheckScope(principal
, mScope
, mWorkerPrivate
->WindowID());
2123 if (NS_FAILED(mRv
)) {
2127 auto activeWorker
= mDescriptor
.GetActive();
2129 if (!activeWorker
||
2130 activeWorker
.ref().Id() != mWorkerPrivate
->ServiceWorkerID()) {
2131 mRv
= NS_ERROR_NOT_AVAILABLE
;
2137 nsresult
Result() { return mRv
; }
2141 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification
2143 already_AddRefed
<Promise
> Notification::ShowPersistentNotification(
2144 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aScope
,
2145 const nsAString
& aTitle
, const NotificationOptions
& aOptions
,
2146 const ServiceWorkerRegistrationDescriptor
& aDescriptor
, ErrorResult
& aRv
) {
2147 MOZ_ASSERT(aGlobal
);
2150 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2151 // thread. On calls from content, we can be sure the scope is valid since
2152 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2153 // debug only? The problem is that there would be different semantics in
2154 // debug and non-debug builds in such a case.
2155 if (NS_IsMainThread()) {
2156 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aGlobal
);
2157 if (NS_WARN_IF(!sop
)) {
2158 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2162 nsIPrincipal
* principal
= sop
->GetPrincipal();
2163 if (NS_WARN_IF(!principal
)) {
2164 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2168 uint64_t windowID
= 0;
2169 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
);
2171 windowID
= win
->WindowID();
2174 aRv
= CheckScope(principal
, NS_ConvertUTF16toUTF8(aScope
), windowID
);
2175 if (NS_WARN_IF(aRv
.Failed())) {
2176 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2180 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
2182 worker
->AssertIsOnWorkerThread();
2184 RefPtr
<CheckLoadRunnable
> loadChecker
= new CheckLoadRunnable(
2185 worker
, NS_ConvertUTF16toUTF8(aScope
), aDescriptor
);
2186 loadChecker
->Dispatch(Canceling
, aRv
);
2191 if (NS_WARN_IF(NS_FAILED(loadChecker
->Result()))) {
2192 if (loadChecker
->Result() == NS_ERROR_NOT_AVAILABLE
) {
2193 aRv
.ThrowTypeError
<MSG_NO_ACTIVE_WORKER
>(NS_ConvertUTF16toUTF8(aScope
));
2195 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2201 // Step 2: Let promise be a new promise in this’s relevant Realm.
2202 RefPtr
<Promise
> p
= Promise::Create(aGlobal
, aRv
);
2203 if (NS_WARN_IF(aRv
.Failed())) {
2207 // We check permission here rather than pass the Promise to NotificationTask
2208 // which leads to uglier code.
2209 // XXX: GetPermission is a synchronous blocking function on workers.
2210 NotificationPermission permission
= GetPermission(aGlobal
, aRv
);
2212 // Step 6.1: If the result of getting the notifications permission state is
2213 // not "granted", then queue a global task on the DOM manipulation task source
2214 // given global to reject promise with a TypeError, and abort these steps.
2215 if (NS_WARN_IF(aRv
.Failed()) ||
2216 permission
!= NotificationPermission::Granted
) {
2217 p
->MaybeRejectWithTypeError("Permission to show Notification denied.");
2221 // "Otherwise, resolve promise with undefined."
2222 // The Notification may still not be shown due to other errors, but the spec
2223 // is not concerned with those.
2224 p
->MaybeResolveWithUndefined();
2226 // Step 5: Let notification be the result of creating a notification given
2227 // title, options, this’s relevant settings object, and
2228 // serviceWorkerRegistration. If this threw an exception, then reject promise
2229 // with that exception and return promise.
2231 // XXX: This should happen before the permission check per the spec, as this
2232 // can throw errors too. This should be split into create and show.
2233 RefPtr
<Notification
> notification
=
2234 CreateAndShow(aCx
, aGlobal
, aTitle
, aOptions
, aScope
, aRv
);
2235 if (NS_WARN_IF(aRv
.Failed())) {
2243 already_AddRefed
<Notification
> Notification::CreateAndShow(
2244 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aTitle
,
2245 const NotificationOptions
& aOptions
, const nsAString
& aScope
,
2247 MOZ_ASSERT(aGlobal
);
2249 RefPtr
<Notification
> notification
=
2250 CreateInternal(aGlobal
, u
""_ns
, aTitle
, aOptions
, aRv
);
2255 // Make a structured clone of the aOptions.mData object
2256 JS::Rooted
<JS::Value
> data(aCx
, aOptions
.mData
);
2257 notification
->InitFromJSVal(aCx
, data
, aRv
);
2258 if (NS_WARN_IF(aRv
.Failed())) {
2262 notification
->SetScope(aScope
);
2264 auto ref
= MakeUnique
<NotificationRef
>(notification
);
2265 if (NS_WARN_IF(!ref
->Initialized())) {
2266 aRv
.Throw(NS_ERROR_DOM_ABORT_ERR
);
2270 // Queue a task to show the notification.
2271 nsCOMPtr
<nsIRunnable
> showNotificationTask
= new NotificationTask(
2272 "Notification::CreateAndShow", std::move(ref
), NotificationTask::eShow
);
2275 notification
->DispatchToMainThread(showNotificationTask
.forget());
2277 if (NS_WARN_IF(NS_FAILED(rv
))) {
2278 notification
->DispatchTrustedEvent(u
"error"_ns
);
2281 return notification
.forget();
2285 nsresult
Notification::RemovePermission(nsIPrincipal
* aPrincipal
) {
2286 MOZ_ASSERT(XRE_IsParentProcess());
2287 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
2288 mozilla::components::PermissionManager::Service();
2289 if (!permissionManager
) {
2290 return NS_ERROR_FAILURE
;
2292 permissionManager
->RemoveFromPrincipal(aPrincipal
, "desktop-notification"_ns
);
2297 nsresult
Notification::OpenSettings(nsIPrincipal
* aPrincipal
) {
2298 MOZ_ASSERT(XRE_IsParentProcess());
2299 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
2301 return NS_ERROR_FAILURE
;
2303 // Notify other observers so they can show settings UI.
2304 obs
->NotifyObservers(aPrincipal
, "notifications-open-settings", nullptr);
2309 Notification::Observe(nsISupports
* aSubject
, const char* aTopic
,
2310 const char16_t
* aData
) {
2311 AssertIsOnMainThread();
2313 if (!strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
) ||
2314 !strcmp(aTopic
, DOM_WINDOW_FROZEN_TOPIC
)) {
2315 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
2316 if (SameCOMIdentity(aSubject
, window
)) {
2317 nsCOMPtr
<nsIObserverService
> obs
=
2318 mozilla::services::GetObserverService();
2320 obs
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
2321 obs
->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC
);
2324 CloseInternal(true);
2331 nsresult
Notification::DispatchToMainThread(
2332 already_AddRefed
<nsIRunnable
>&& aRunnable
) {
2333 if (mWorkerPrivate
) {
2334 return mWorkerPrivate
->DispatchToMainThread(std::move(aRunnable
));
2336 AssertIsOnMainThread();
2337 return NS_DispatchToCurrentThread(std::move(aRunnable
));
2340 } // namespace mozilla::dom