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/Telemetry.h"
21 #include "mozilla/Unused.h"
22 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
23 #include "mozilla/dom/BindingUtils.h"
24 #include "mozilla/dom/ContentChild.h"
25 #include "mozilla/dom/Document.h"
26 #include "mozilla/dom/NotificationEvent.h"
27 #include "mozilla/dom/PermissionMessageUtils.h"
28 #include "mozilla/dom/Promise.h"
29 #include "mozilla/dom/PromiseWorkerProxy.h"
30 #include "mozilla/dom/RootedDictionary.h"
31 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
32 #include "mozilla/dom/ServiceWorkerManager.h"
33 #include "mozilla/dom/ServiceWorkerUtils.h"
34 #include "mozilla/dom/WorkerRunnable.h"
35 #include "mozilla/dom/WorkerScope.h"
36 #include "Navigator.h"
37 #include "nsAlertsUtils.h"
38 #include "nsCRTGlue.h"
39 #include "nsComponentManagerUtils.h"
40 #include "nsContentPermissionHelper.h"
41 #include "nsContentUtils.h"
42 #include "nsDOMJSUtils.h"
43 #include "nsFocusManager.h"
44 #include "nsIAlertsService.h"
45 #include "nsIContentPermissionPrompt.h"
46 #include "nsILoadContext.h"
47 #include "nsINotificationStorage.h"
48 #include "nsIPermission.h"
49 #include "nsIPermissionManager.h"
50 #include "nsIPushService.h"
51 #include "nsIScriptError.h"
52 #include "nsIServiceWorkerManager.h"
53 #include "nsISimpleEnumerator.h"
54 #include "nsIUUIDGenerator.h"
55 #include "nsNetUtil.h"
56 #include "nsProxyRelease.h"
57 #include "nsServiceManagerUtils.h"
58 #include "nsStructuredCloneContainer.h"
59 #include "nsThreadUtils.h"
60 #include "nsXULAppAPI.h"
62 namespace mozilla::dom
{
64 struct NotificationStrings
{
66 const nsString mTitle
;
73 const nsString mBehavior
;
74 const nsString mServiceWorkerRegistrationScope
;
77 class ScopeCheckingGetCallback
: public nsINotificationStorageCallback
{
78 const nsString mScope
;
81 explicit ScopeCheckingGetCallback(const nsAString
& aScope
) : mScope(aScope
) {}
83 NS_IMETHOD
Handle(const nsAString
& aID
, const nsAString
& aTitle
,
84 const nsAString
& aDir
, const nsAString
& aLang
,
85 const nsAString
& aBody
, const nsAString
& aTag
,
86 const nsAString
& aIcon
, const nsAString
& aData
,
87 const nsAString
& aBehavior
,
88 const nsAString
& aServiceWorkerRegistrationScope
) final
{
89 AssertIsOnMainThread();
90 MOZ_ASSERT(!aID
.IsEmpty());
92 // Skip scopes that don't match when called from getNotifications().
93 if (!mScope
.IsEmpty() && !mScope
.Equals(aServiceWorkerRegistrationScope
)) {
97 NotificationStrings strings
= {
98 nsString(aID
), nsString(aTitle
),
99 nsString(aDir
), nsString(aLang
),
100 nsString(aBody
), nsString(aTag
),
101 nsString(aIcon
), nsString(aData
),
102 nsString(aBehavior
), nsString(aServiceWorkerRegistrationScope
),
105 mStrings
.AppendElement(std::move(strings
));
109 NS_IMETHOD
Done() override
= 0;
112 virtual ~ScopeCheckingGetCallback() = default;
114 nsTArray
<NotificationStrings
> mStrings
;
117 class NotificationStorageCallback final
: public ScopeCheckingGetCallback
{
119 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
120 NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback
)
122 NotificationStorageCallback(nsIGlobalObject
* aWindow
, const nsAString
& aScope
,
124 : ScopeCheckingGetCallback(aScope
), mWindow(aWindow
), mPromise(aPromise
) {
125 AssertIsOnMainThread();
127 MOZ_ASSERT(aPromise
);
130 NS_IMETHOD
Done() final
{
132 AutoTArray
<RefPtr
<Notification
>, 5> notifications
;
134 for (uint32_t i
= 0; i
< mStrings
.Length(); ++i
) {
135 RefPtr
<Notification
> n
= Notification::ConstructFromFields(
136 mWindow
, mStrings
[i
].mID
, mStrings
[i
].mTitle
, mStrings
[i
].mDir
,
137 mStrings
[i
].mLang
, mStrings
[i
].mBody
, mStrings
[i
].mTag
,
138 mStrings
[i
].mIcon
, mStrings
[i
].mData
,
139 /* mStrings[i].mBehavior, not
141 mStrings
[i
].mServiceWorkerRegistrationScope
, result
);
143 n
->SetStoredState(true);
144 Unused
<< NS_WARN_IF(result
.Failed());
145 if (!result
.Failed()) {
146 notifications
.AppendElement(n
.forget());
150 mPromise
->MaybeResolve(notifications
);
155 virtual ~NotificationStorageCallback() = default;
157 nsCOMPtr
<nsIGlobalObject
> mWindow
;
158 RefPtr
<Promise
> mPromise
;
159 const nsString mScope
;
162 NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback
)
163 NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback
)
164 NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback
, mWindow
, mPromise
);
166 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback
)
167 NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback
)
168 NS_INTERFACE_MAP_ENTRY(nsISupports
)
171 class NotificationGetRunnable final
: public Runnable
{
172 const nsString mOrigin
;
174 nsCOMPtr
<nsINotificationStorageCallback
> mCallback
;
177 NotificationGetRunnable(const nsAString
& aOrigin
, const nsAString
& aTag
,
178 nsINotificationStorageCallback
* aCallback
)
179 : Runnable("NotificationGetRunnable"),
182 mCallback(aCallback
) {}
187 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
188 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
189 if (NS_WARN_IF(NS_FAILED(rv
))) {
193 rv
= notificationStorage
->Get(mOrigin
, mTag
, mCallback
);
194 // XXXnsm Is it guaranteed mCallback will be called in case of failure?
195 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
200 class NotificationPermissionRequest
: public ContentPermissionRequestBase
,
205 NS_DECL_ISUPPORTS_INHERITED
206 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest
,
207 ContentPermissionRequestBase
)
209 // nsIContentPermissionRequest
210 NS_IMETHOD
Cancel(void) override
;
211 NS_IMETHOD
Allow(JS::Handle
<JS::Value
> choices
) override
;
213 NotificationPermissionRequest(nsIPrincipal
* aPrincipal
,
214 nsPIDOMWindowInner
* aWindow
, Promise
* aPromise
,
215 NotificationPermissionCallback
* aCallback
)
216 : ContentPermissionRequestBase(aPrincipal
, aWindow
, "notification"_ns
,
217 "desktop-notification"_ns
),
218 mPermission(NotificationPermission::Default
),
220 mCallback(aCallback
) {
221 MOZ_ASSERT(aPromise
);
224 NS_IMETHOD
GetName(nsACString
& aName
) override
{
225 aName
.AssignLiteral("NotificationPermissionRequest");
230 ~NotificationPermissionRequest() = default;
232 MOZ_CAN_RUN_SCRIPT nsresult
ResolvePromise();
233 nsresult
DispatchResolvePromise();
234 NotificationPermission mPermission
;
235 RefPtr
<Promise
> mPromise
;
236 RefPtr
<NotificationPermissionCallback
> mCallback
;
240 class ReleaseNotificationControlRunnable final
241 : public MainThreadWorkerControlRunnable
{
242 Notification
* mNotification
;
245 explicit ReleaseNotificationControlRunnable(Notification
* aNotification
)
246 : MainThreadWorkerControlRunnable(aNotification
->mWorkerPrivate
),
247 mNotification(aNotification
) {}
249 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
250 mNotification
->ReleaseObject();
255 class GetPermissionRunnable final
: public WorkerMainThreadRunnable
{
256 NotificationPermission mPermission
;
259 explicit GetPermissionRunnable(WorkerPrivate
* aWorker
)
260 : WorkerMainThreadRunnable(aWorker
, "Notification :: Get Permission"_ns
),
261 mPermission(NotificationPermission::Denied
) {}
263 bool MainThreadRun() override
{
265 mPermission
= Notification::GetPermissionInternal(
266 mWorkerPrivate
->GetPrincipal(), result
);
270 NotificationPermission
GetPermission() { return mPermission
; }
273 class FocusWindowRunnable final
: public Runnable
{
274 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> mWindow
;
277 explicit FocusWindowRunnable(
278 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
279 : Runnable("FocusWindowRunnable"), mWindow(aWindow
) {}
281 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
283 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
Run() override
{
284 AssertIsOnMainThread();
285 if (!mWindow
->IsCurrentInnerWindow()) {
286 // Window has been closed, this observer is not valid anymore
290 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= mWindow
->GetOuterWindow();
291 nsFocusManager::FocusWindow(outerWindow
, CallerType::System
);
296 nsresult
CheckScope(nsIPrincipal
* aPrincipal
, const nsACString
& aScope
,
297 uint64_t aWindowID
) {
298 AssertIsOnMainThread();
299 MOZ_ASSERT(aPrincipal
);
301 nsCOMPtr
<nsIURI
> scopeURI
;
302 nsresult rv
= NS_NewURI(getter_AddRefs(scopeURI
), aScope
);
303 if (NS_WARN_IF(NS_FAILED(rv
))) {
307 return aPrincipal
->CheckMayLoadWithReporting(
309 /* allowIfInheritsPrincipal = */ false, aWindowID
);
311 } // anonymous namespace
313 // Subclass that can be directly dispatched to child workers from the main
315 class NotificationWorkerRunnable
: public MainThreadWorkerRunnable
{
317 explicit NotificationWorkerRunnable(WorkerPrivate
* aWorkerPrivate
)
318 : MainThreadWorkerRunnable(aWorkerPrivate
) {}
320 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
321 aWorkerPrivate
->AssertIsOnWorkerThread();
322 aWorkerPrivate
->ModifyBusyCountFromWorker(true);
323 // WorkerScope might start dying at the moment. And WorkerRunInternal()
324 // should not be executed once WorkerScope is dying, since
325 // WorkerRunInternal() might access resources which already been freed
326 // during WorkerRef::Notify().
327 if (aWorkerPrivate
->GlobalScope() &&
328 !aWorkerPrivate
->GlobalScope()->IsDying()) {
329 WorkerRunInternal(aWorkerPrivate
);
334 void PostRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
335 bool aRunResult
) override
{
336 aWorkerPrivate
->ModifyBusyCountFromWorker(false);
339 virtual void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) = 0;
342 // Overrides dispatch and run handlers so we can directly dispatch from main
343 // thread to child workers.
344 class NotificationEventWorkerRunnable final
345 : public NotificationWorkerRunnable
{
346 Notification
* mNotification
;
347 const nsString mEventName
;
350 NotificationEventWorkerRunnable(Notification
* aNotification
,
351 const nsString
& aEventName
)
352 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
),
353 mNotification(aNotification
),
354 mEventName(aEventName
) {}
356 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
357 mNotification
->DispatchTrustedEvent(mEventName
);
361 class ReleaseNotificationRunnable final
: public NotificationWorkerRunnable
{
362 Notification
* mNotification
;
365 explicit ReleaseNotificationRunnable(Notification
* aNotification
)
366 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
),
367 mNotification(aNotification
) {}
369 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
370 aWorkerPrivate
->AssertIsOnWorkerThread();
371 aWorkerPrivate
->ModifyBusyCountFromWorker(true);
372 // ReleaseNotificationRunnable is only used in StrongWorkerRef's shutdown
373 // callback. At the moment, it is supposed to executing
374 // mNotification->ReleaseObject() safely even though the corresponding
375 // WorkerScope::IsDying() is true. It is unlike other
376 // NotificationWorkerRunnable.
377 WorkerRunInternal(aWorkerPrivate
);
381 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
382 mNotification
->ReleaseObject();
385 nsresult
Cancel() override
{
386 mNotification
->ReleaseObject();
391 // Create one whenever you require ownership of the notification. Use with
392 // UniquePtr<>. See Notification.h for details.
393 class NotificationRef final
{
394 friend class WorkerNotificationObserver
;
397 Notification
* mNotification
;
400 // Only useful for workers.
401 void Forget() { mNotification
= nullptr; }
404 explicit NotificationRef(Notification
* aNotification
)
405 : mNotification(aNotification
) {
406 MOZ_ASSERT(mNotification
);
407 if (mNotification
->mWorkerPrivate
) {
408 mNotification
->mWorkerPrivate
->AssertIsOnWorkerThread();
410 AssertIsOnMainThread();
413 mInited
= mNotification
->AddRefObject();
416 // This is only required because Gecko runs script in a worker's onclose
417 // handler (non-standard, Bug 790919) where calls to HoldWorker() will
418 // fail. Due to non-standardness and added complications if we decide to
419 // support this, attempts to create a Notification in onclose just throw
421 bool Initialized() { return mInited
; }
424 if (Initialized() && mNotification
) {
425 Notification
* notification
= mNotification
;
426 mNotification
= nullptr;
427 if (notification
->mWorkerPrivate
&& NS_IsMainThread()) {
428 // Try to pass ownership back to the worker. If the dispatch succeeds we
429 // are guaranteed this runnable will run, and that it will run after
430 // queued event runnables, so event runnables will have a safe pointer
431 // to the Notification.
433 // If the dispatch fails, the worker isn't running anymore and the event
434 // runnables have already run or been canceled. We can use a control
435 // runnable to release the reference.
436 RefPtr
<ReleaseNotificationRunnable
> r
=
437 new ReleaseNotificationRunnable(notification
);
439 if (!r
->Dispatch()) {
440 RefPtr
<ReleaseNotificationControlRunnable
> r
=
441 new ReleaseNotificationControlRunnable(notification
);
442 MOZ_ALWAYS_TRUE(r
->Dispatch());
445 notification
->AssertIsOnTargetThread();
446 notification
->ReleaseObject();
451 // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
452 // a rawptr that the NotificationRef can invalidate?
453 Notification
* GetNotification() {
454 MOZ_ASSERT(Initialized());
455 return mNotification
;
459 class NotificationTask
: public Runnable
{
461 enum NotificationAction
{ eShow
, eClose
};
463 NotificationTask(const char* aName
, UniquePtr
<NotificationRef
> aRef
,
464 NotificationAction aAction
)
465 : Runnable(aName
), mNotificationRef(std::move(aRef
)), mAction(aAction
) {}
471 virtual ~NotificationTask() = default;
473 UniquePtr
<NotificationRef
> mNotificationRef
;
474 NotificationAction mAction
;
477 uint32_t Notification::sCount
= 0;
479 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest
,
480 ContentPermissionRequestBase
, mCallback
)
481 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest
,
482 ContentPermissionRequestBase
)
483 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest
,
484 ContentPermissionRequestBase
)
486 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
487 NotificationPermissionRequest
, ContentPermissionRequestBase
, nsIRunnable
,
491 NotificationPermissionRequest::Run() {
492 bool isSystem
= mPrincipal
->IsSystemPrincipal();
493 bool blocked
= false;
495 mPermission
= NotificationPermission::Granted
;
496 } else if (mPrincipal
->GetPrivateBrowsingId() != 0) {
497 mPermission
= NotificationPermission::Denied
;
500 // File are automatically granted permission.
502 if (mPrincipal
->SchemeIs("file")) {
503 mPermission
= NotificationPermission::Granted
;
504 } else if (!mWindow
->IsSecureContext()) {
505 mPermission
= NotificationPermission::Denied
;
507 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
509 nsContentUtils::ReportToConsole(
510 nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
511 nsContentUtils::eDOM_PROPERTIES
,
512 "NotificationsInsecureRequestIsForbidden");
517 // We can't call ShowPrompt() directly here since our logic for determining
518 // whether to display a prompt depends on the checks above as well as the
519 // result of CheckPromptPrefs(). So we have to manually check the prompt
520 // prefs and decide what to do based on that.
521 PromptResult pr
= CheckPromptPrefs();
523 case PromptResult::Granted
:
524 mPermission
= NotificationPermission::Granted
;
526 case PromptResult::Denied
:
527 mPermission
= NotificationPermission::Denied
;
534 if (!mHasValidTransientUserGestureActivation
&&
535 !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
536 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
538 doc
->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation
);
542 // Check this after checking the prompt prefs to make sure this pref overrides
543 // those. We rely on this for testing purposes.
544 if (!isSystem
&& !blocked
&&
545 !StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
546 !mPrincipal
->Subsumes(mTopLevelPrincipal
)) {
547 mPermission
= NotificationPermission::Denied
;
549 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
551 nsContentUtils::ReportToConsole(
552 nsIScriptError::errorFlag
, "DOM"_ns
, doc
,
553 nsContentUtils::eDOM_PROPERTIES
,
554 "NotificationsCrossOriginIframeRequestIsForbidden");
558 if (mPermission
!= NotificationPermission::Default
) {
559 return DispatchResolvePromise();
562 return nsContentPermissionUtils::AskPermission(this, mWindow
);
566 NotificationPermissionRequest::Cancel() {
567 // `Cancel` is called if the user denied permission or dismissed the
568 // permission request. To distinguish between the two, we set the
569 // permission to "default" and query the permission manager in
571 mPermission
= NotificationPermission::Default
;
572 return DispatchResolvePromise();
576 NotificationPermissionRequest::Allow(JS::Handle
<JS::Value
> aChoices
) {
577 MOZ_ASSERT(aChoices
.isUndefined());
579 mPermission
= NotificationPermission::Granted
;
580 return DispatchResolvePromise();
583 inline nsresult
NotificationPermissionRequest::DispatchResolvePromise() {
584 nsCOMPtr
<nsIRunnable
> resolver
=
585 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
586 this, &NotificationPermissionRequest::ResolvePromise
);
587 return nsGlobalWindowInner::Cast(mWindow
.get())->Dispatch(resolver
.forget());
590 nsresult
NotificationPermissionRequest::ResolvePromise() {
592 // This will still be "default" if the user dismissed the doorhanger,
593 // or "denied" otherwise.
594 if (mPermission
== NotificationPermission::Default
) {
595 // When the front-end has decided to deny the permission request
596 // automatically and we are not handling user input, then log a
597 // warning in the current document that this happened because
598 // Notifications require a user gesture.
599 if (!mHasValidTransientUserGestureActivation
&&
600 StaticPrefs::dom_webnotifications_requireuserinteraction()) {
601 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
603 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag
, "DOM"_ns
,
604 doc
, nsContentUtils::eDOM_PROPERTIES
,
605 "NotificationsRequireUserGesture");
609 mPermission
= Notification::TestPermission(mPrincipal
);
613 RefPtr
<NotificationPermissionCallback
> callback(mCallback
);
614 callback
->Call(mPermission
, error
);
615 rv
= error
.StealNSResult();
617 mPromise
->MaybeResolve(mPermission
);
621 // Observer that the alert service calls to do common tasks and/or dispatch to
622 // the specific observer for the context e.g. main thread, worker, or service
624 class NotificationObserver final
: public nsIObserver
{
626 nsCOMPtr
<nsIObserver
> mObserver
;
627 nsCOMPtr
<nsIPrincipal
> mPrincipal
;
628 bool mInPrivateBrowsing
;
632 NotificationObserver(nsIObserver
* aObserver
, nsIPrincipal
* aPrincipal
,
633 bool aInPrivateBrowsing
)
634 : mObserver(aObserver
),
635 mPrincipal(aPrincipal
),
636 mInPrivateBrowsing(aInPrivateBrowsing
) {
637 AssertIsOnMainThread();
638 MOZ_ASSERT(mObserver
);
639 MOZ_ASSERT(mPrincipal
);
643 virtual ~NotificationObserver() { AssertIsOnMainThread(); }
645 nsresult
AdjustPushQuota(const char* aTopic
);
648 NS_IMPL_ISUPPORTS(NotificationObserver
, nsIObserver
)
650 class MainThreadNotificationObserver
: public nsIObserver
{
652 UniquePtr
<NotificationRef
> mNotificationRef
;
656 explicit MainThreadNotificationObserver(UniquePtr
<NotificationRef
> aRef
)
657 : mNotificationRef(std::move(aRef
)) {
658 AssertIsOnMainThread();
662 virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
665 NS_IMPL_ISUPPORTS(MainThreadNotificationObserver
, nsIObserver
)
668 NotificationTask::Run() {
669 AssertIsOnMainThread();
671 // Get a pointer to notification before the notification takes ownership of
672 // the ref (it owns itself temporarily, with ShowInternal() and
673 // CloseInternal() passing on the ownership appropriately.)
674 Notification
* notif
= mNotificationRef
->GetNotification();
675 notif
->mTempRef
.swap(mNotificationRef
);
676 if (mAction
== eShow
) {
677 notif
->ShowInternal();
678 } else if (mAction
== eClose
) {
679 notif
->CloseInternal();
681 MOZ_CRASH("Invalid action");
684 MOZ_ASSERT(!mNotificationRef
);
689 bool Notification::PrefEnabled(JSContext
* aCx
, JSObject
* aObj
) {
690 return StaticPrefs::dom_webnotifications_enabled();
693 Notification::Notification(nsIGlobalObject
* aGlobal
, const nsAString
& aID
,
694 const nsAString
& aTitle
, const nsAString
& aBody
,
695 NotificationDirection aDir
, const nsAString
& aLang
,
696 const nsAString
& aTag
, const nsAString
& aIconUrl
,
697 bool aRequireInteraction
, bool aSilent
,
698 nsTArray
<uint32_t>&& aVibrate
,
699 const NotificationBehavior
& aBehavior
)
700 : DOMEventTargetHelper(aGlobal
),
701 mWorkerPrivate(nullptr),
710 mRequireInteraction(aRequireInteraction
),
712 mVibrate(std::move(aVibrate
)),
713 mBehavior(aBehavior
),
714 mData(JS::NullValue()),
718 if (!NS_IsMainThread()) {
719 mWorkerPrivate
= GetCurrentThreadWorkerPrivate();
720 MOZ_ASSERT(mWorkerPrivate
);
724 nsresult
Notification::Init() {
725 if (!mWorkerPrivate
) {
726 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
727 NS_ENSURE_TRUE(obs
, NS_ERROR_FAILURE
);
729 nsresult rv
= obs
->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC
, true);
730 NS_ENSURE_SUCCESS(rv
, rv
);
732 rv
= obs
->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC
, true);
733 NS_ENSURE_SUCCESS(rv
, rv
);
739 void Notification::SetAlertName() {
740 AssertIsOnMainThread();
741 if (!mAlertName
.IsEmpty()) {
745 nsAutoString alertName
;
746 nsresult rv
= GetOrigin(GetPrincipal(), alertName
);
747 if (NS_WARN_IF(NS_FAILED(rv
))) {
751 // Get the notification name that is unique per origin + tag/ID.
752 // The name of the alert is of the form origin#tag/ID.
753 alertName
.Append('#');
754 if (!mTag
.IsEmpty()) {
755 alertName
.AppendLiteral("tag:");
756 alertName
.Append(mTag
);
758 alertName
.AppendLiteral("notag:");
759 alertName
.Append(mID
);
762 mAlertName
= alertName
;
765 // May be called on any thread.
767 already_AddRefed
<Notification
> Notification::Constructor(
768 const GlobalObject
& aGlobal
, const nsAString
& aTitle
,
769 const NotificationOptions
& aOptions
, ErrorResult
& aRv
) {
770 // FIXME(nsm): If the sticky flag is set, throw an error.
771 RefPtr
<ServiceWorkerGlobalScope
> scope
;
772 UNWRAP_OBJECT(ServiceWorkerGlobalScope
, aGlobal
.Get(), scope
);
775 "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
776 "Use registration.showNotification() instead.");
780 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
781 RefPtr
<Notification
> notification
=
782 CreateAndShow(aGlobal
.Context(), global
, aTitle
, aOptions
, u
""_ns
, aRv
);
783 if (NS_WARN_IF(aRv
.Failed())) {
787 // This is be ok since we are on the worker thread where this function will
788 // run to completion before the Notification has a chance to go away.
789 return notification
.forget();
793 already_AddRefed
<Notification
> Notification::ConstructFromFields(
794 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
795 const nsAString
& aDir
, const nsAString
& aLang
, const nsAString
& aBody
,
796 const nsAString
& aTag
, const nsAString
& aIcon
, const nsAString
& aData
,
797 const nsAString
& aServiceWorkerRegistrationScope
, ErrorResult
& aRv
) {
800 RootedDictionary
<NotificationOptions
> options(RootingCx());
801 options
.mDir
= Notification::StringToDirection(nsString(aDir
));
802 options
.mLang
= aLang
;
803 options
.mBody
= aBody
;
805 options
.mIcon
= aIcon
;
806 RefPtr
<Notification
> notification
=
807 CreateInternal(aGlobal
, aID
, aTitle
, options
, aRv
);
809 notification
->InitFromBase64(aData
, aRv
);
810 if (NS_WARN_IF(aRv
.Failed())) {
814 notification
->SetScope(aServiceWorkerRegistrationScope
);
816 return notification
.forget();
819 nsresult
Notification::PersistNotification() {
820 AssertIsOnMainThread();
822 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
823 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
829 rv
= GetOrigin(GetPrincipal(), origin
);
830 if (NS_WARN_IF(NS_FAILED(rv
))) {
838 GetAlertName(alertName
);
840 nsAutoString behavior
;
841 if (!mBehavior
.ToJSON(behavior
)) {
842 return NS_ERROR_FAILURE
;
845 rv
= notificationStorage
->Put(origin
, id
, mTitle
, DirectionToString(mDir
),
846 mLang
, mBody
, mTag
, mIconUrl
, alertName
,
847 mDataAsBase64
, behavior
, mScope
);
853 SetStoredState(true);
857 void Notification::UnpersistNotification() {
858 AssertIsOnMainThread();
860 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
861 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
);
862 if (notificationStorage
) {
864 nsresult rv
= GetOrigin(GetPrincipal(), origin
);
865 if (NS_SUCCEEDED(rv
)) {
866 notificationStorage
->Delete(origin
, mID
);
869 SetStoredState(false);
873 already_AddRefed
<Notification
> Notification::CreateInternal(
874 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
875 const NotificationOptions
& aOptions
, ErrorResult
& aRv
) {
878 if (!aID
.IsEmpty()) {
881 nsCOMPtr
<nsIUUIDGenerator
> uuidgen
=
882 do_GetService("@mozilla.org/uuid-generator;1");
883 NS_ENSURE_TRUE(uuidgen
, nullptr);
885 rv
= uuidgen
->GenerateUUIDInPlace(&uuid
);
886 NS_ENSURE_SUCCESS(rv
, nullptr);
888 char buffer
[NSID_LENGTH
];
889 uuid
.ToProvidedString(buffer
);
890 NS_ConvertASCIItoUTF16
convertedID(buffer
);
895 if (StaticPrefs::dom_webnotifications_silent_enabled()) {
896 silent
= aOptions
.mSilent
;
899 nsTArray
<uint32_t> vibrate
;
900 if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
901 aOptions
.mVibrate
.WasPassed()) {
904 "Silent notifications must not specify vibration patterns.");
908 const OwningUnsignedLongOrUnsignedLongSequence
& value
=
909 aOptions
.mVibrate
.Value();
910 if (value
.IsUnsignedLong()) {
911 AutoTArray
<uint32_t, 1> array
;
912 array
.AppendElement(value
.GetAsUnsignedLong());
913 vibrate
= SanitizeVibratePattern(array
);
915 vibrate
= SanitizeVibratePattern(value
.GetAsUnsignedLongSequence());
919 RefPtr
<Notification
> notification
= new Notification(
920 aGlobal
, id
, aTitle
, aOptions
.mBody
, aOptions
.mDir
, aOptions
.mLang
,
921 aOptions
.mTag
, aOptions
.mIcon
, aOptions
.mRequireInteraction
, silent
,
922 std::move(vibrate
), aOptions
.mMozbehavior
);
923 rv
= notification
->Init();
924 NS_ENSURE_SUCCESS(rv
, nullptr);
925 return notification
.forget();
928 Notification::~Notification() {
929 mozilla::DropJSObjects(this);
930 AssertIsOnTargetThread();
931 MOZ_ASSERT(!mWorkerRef
);
932 MOZ_ASSERT(!mTempRef
);
935 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification
)
936 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification
,
937 DOMEventTargetHelper
)
938 tmp
->mData
.setUndefined();
939 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
940 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
942 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification
,
943 DOMEventTargetHelper
)
944 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
946 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification
,
947 DOMEventTargetHelper
)
948 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData
)
949 NS_IMPL_CYCLE_COLLECTION_TRACE_END
951 NS_IMPL_ADDREF_INHERITED(Notification
, DOMEventTargetHelper
)
952 NS_IMPL_RELEASE_INHERITED(Notification
, DOMEventTargetHelper
)
954 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification
)
955 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
956 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
957 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
959 nsIPrincipal
* Notification::GetPrincipal() {
960 AssertIsOnMainThread();
961 if (mWorkerPrivate
) {
962 return mWorkerPrivate
->GetPrincipal();
964 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(GetOwner());
965 NS_ENSURE_TRUE(sop
, nullptr);
966 return sop
->GetPrincipal();
970 class WorkerNotificationObserver final
: public MainThreadNotificationObserver
{
972 NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver
,
973 MainThreadNotificationObserver
)
976 explicit WorkerNotificationObserver(UniquePtr
<NotificationRef
> aRef
)
977 : MainThreadNotificationObserver(std::move(aRef
)) {
978 AssertIsOnMainThread();
979 MOZ_ASSERT(mNotificationRef
->GetNotification()->mWorkerPrivate
);
982 void ForgetNotification() {
983 AssertIsOnMainThread();
984 mNotificationRef
->Forget();
988 virtual ~WorkerNotificationObserver() {
989 AssertIsOnMainThread();
991 MOZ_ASSERT(mNotificationRef
);
992 Notification
* notification
= mNotificationRef
->GetNotification();
994 notification
->mObserver
= nullptr;
999 class ServiceWorkerNotificationObserver final
: public nsIObserver
{
1004 ServiceWorkerNotificationObserver(
1005 const nsAString
& aScope
, nsIPrincipal
* aPrincipal
, const nsAString
& aID
,
1006 const nsAString
& aTitle
, const nsAString
& aDir
, const nsAString
& aLang
,
1007 const nsAString
& aBody
, const nsAString
& aTag
, const nsAString
& aIcon
,
1008 const nsAString
& aData
, const nsAString
& aBehavior
)
1011 mPrincipal(aPrincipal
),
1019 mBehavior(aBehavior
) {
1020 AssertIsOnMainThread();
1021 MOZ_ASSERT(aPrincipal
);
1025 ~ServiceWorkerNotificationObserver() = default;
1027 const nsString mScope
;
1029 nsCOMPtr
<nsIPrincipal
> mPrincipal
;
1030 const nsString mTitle
;
1031 const nsString mDir
;
1032 const nsString mLang
;
1033 const nsString mBody
;
1034 const nsString mTag
;
1035 const nsString mIcon
;
1036 const nsString mData
;
1037 const nsString mBehavior
;
1040 NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver
, nsIObserver
)
1042 bool Notification::DispatchClickEvent() {
1043 AssertIsOnTargetThread();
1044 RefPtr
<Event
> event
= NS_NewDOMEvent(this, nullptr, nullptr);
1045 event
->InitEvent(u
"click"_ns
, false, true);
1046 event
->SetTrusted(true);
1047 WantsPopupControlCheck
popupControlCheck(event
);
1048 return DispatchEvent(*event
, CallerType::System
, IgnoreErrors());
1051 // Overrides dispatch and run handlers so we can directly dispatch from main
1052 // thread to child workers.
1053 class NotificationClickWorkerRunnable final
1054 : public NotificationWorkerRunnable
{
1055 Notification
* mNotification
;
1056 // Optional window that gets focused if click event is not
1057 // preventDefault()ed.
1058 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> mWindow
;
1061 NotificationClickWorkerRunnable(
1062 Notification
* aNotification
,
1063 const nsMainThreadPtrHandle
<nsPIDOMWindowInner
>& aWindow
)
1064 : NotificationWorkerRunnable(aNotification
->mWorkerPrivate
),
1065 mNotification(aNotification
),
1067 MOZ_ASSERT_IF(mWorkerPrivate
->IsServiceWorker(), !mWindow
);
1070 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
1071 bool doDefaultAction
= mNotification
->DispatchClickEvent();
1072 MOZ_ASSERT_IF(mWorkerPrivate
->IsServiceWorker(), !doDefaultAction
);
1073 if (doDefaultAction
) {
1074 RefPtr
<FocusWindowRunnable
> r
= new FocusWindowRunnable(mWindow
);
1075 mWorkerPrivate
->DispatchToMainThread(r
.forget());
1081 NotificationObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
1082 const char16_t
* aData
) {
1083 AssertIsOnMainThread();
1085 if (!strcmp("alertdisablecallback", aTopic
)) {
1086 if (XRE_IsParentProcess()) {
1087 return Notification::RemovePermission(mPrincipal
);
1089 // Permissions can't be removed from the content process. Send a message
1090 // to the parent; `ContentParent::RecvDisableNotifications` will call
1091 // `RemovePermission`.
1092 ContentChild::GetSingleton()->SendDisableNotifications(mPrincipal
);
1094 } else if (!strcmp("alertsettingscallback", aTopic
)) {
1095 if (XRE_IsParentProcess()) {
1096 return Notification::OpenSettings(mPrincipal
);
1098 // `ContentParent::RecvOpenNotificationSettings` notifies observers in the
1100 ContentChild::GetSingleton()->SendOpenNotificationSettings(mPrincipal
);
1102 } else if (!strcmp("alertshow", aTopic
) || !strcmp("alertfinished", aTopic
)) {
1103 Unused
<< NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic
)));
1106 return mObserver
->Observe(aSubject
, aTopic
, aData
);
1109 nsresult
NotificationObserver::AdjustPushQuota(const char* aTopic
) {
1110 nsCOMPtr
<nsIPushQuotaManager
> pushQuotaManager
=
1111 do_GetService("@mozilla.org/push/Service;1");
1112 if (!pushQuotaManager
) {
1113 return NS_ERROR_FAILURE
;
1116 nsAutoCString origin
;
1117 nsresult rv
= mPrincipal
->GetOrigin(origin
);
1118 if (NS_FAILED(rv
)) {
1122 if (!strcmp("alertshow", aTopic
)) {
1123 return pushQuotaManager
->NotificationForOriginShown(origin
.get());
1125 return pushQuotaManager
->NotificationForOriginClosed(origin
.get());
1128 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
1130 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1131 MainThreadNotificationObserver::Observe(nsISupports
* aSubject
,
1133 const char16_t
* aData
) {
1134 AssertIsOnMainThread();
1135 MOZ_ASSERT(mNotificationRef
);
1136 Notification
* notification
= mNotificationRef
->GetNotification();
1137 MOZ_ASSERT(notification
);
1138 if (!strcmp("alertclickcallback", aTopic
)) {
1139 nsCOMPtr
<nsPIDOMWindowInner
> window
= notification
->GetOwner();
1140 if (NS_WARN_IF(!window
|| !window
->IsCurrentInnerWindow())) {
1141 // Window has been closed, this observer is not valid anymore
1142 return NS_ERROR_FAILURE
;
1145 bool doDefaultAction
= notification
->DispatchClickEvent();
1146 if (doDefaultAction
) {
1147 nsCOMPtr
<nsPIDOMWindowOuter
> outerWindow
= window
->GetOuterWindow();
1148 nsFocusManager::FocusWindow(outerWindow
, CallerType::System
);
1150 } else if (!strcmp("alertfinished", aTopic
)) {
1151 notification
->UnpersistNotification();
1152 notification
->mIsClosed
= true;
1153 notification
->DispatchTrustedEvent(u
"close"_ns
);
1154 } else if (!strcmp("alertshow", aTopic
)) {
1155 notification
->DispatchTrustedEvent(u
"show"_ns
);
1161 WorkerNotificationObserver::Observe(nsISupports
* aSubject
, const char* aTopic
,
1162 const char16_t
* aData
) {
1163 AssertIsOnMainThread();
1164 MOZ_ASSERT(mNotificationRef
);
1165 // For an explanation of why it is OK to pass this rawptr to the event
1166 // runnables, see the Notification class comment.
1167 Notification
* notification
= mNotificationRef
->GetNotification();
1168 // We can't assert notification here since the feature could've unset it.
1169 if (NS_WARN_IF(!notification
)) {
1170 return NS_ERROR_FAILURE
;
1173 MOZ_ASSERT(notification
->mWorkerPrivate
);
1175 RefPtr
<WorkerRunnable
> r
;
1176 if (!strcmp("alertclickcallback", aTopic
)) {
1177 nsPIDOMWindowInner
* window
= nullptr;
1178 if (!notification
->mWorkerPrivate
->IsServiceWorker()) {
1179 WorkerPrivate
* top
= notification
->mWorkerPrivate
;
1180 while (top
->GetParent()) {
1181 top
= top
->GetParent();
1184 window
= top
->GetWindow();
1185 if (NS_WARN_IF(!window
|| !window
->IsCurrentInnerWindow())) {
1186 // Window has been closed, this observer is not valid anymore
1187 return NS_ERROR_FAILURE
;
1191 // Instead of bothering with adding features and other worker lifecycle
1192 // management, we simply hold strongrefs to the window and document.
1193 nsMainThreadPtrHandle
<nsPIDOMWindowInner
> windowHandle(
1194 new nsMainThreadPtrHolder
<nsPIDOMWindowInner
>(
1195 "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window
));
1197 r
= new NotificationClickWorkerRunnable(notification
, windowHandle
);
1198 } else if (!strcmp("alertfinished", aTopic
)) {
1199 notification
->UnpersistNotification();
1200 notification
->mIsClosed
= true;
1201 r
= new NotificationEventWorkerRunnable(notification
, u
"close"_ns
);
1202 } else if (!strcmp("alertshow", aTopic
)) {
1203 r
= new NotificationEventWorkerRunnable(notification
, u
"show"_ns
);
1207 if (!r
->Dispatch()) {
1208 NS_WARNING("Could not dispatch event to worker notification");
1214 ServiceWorkerNotificationObserver::Observe(nsISupports
* aSubject
,
1216 const char16_t
* aData
) {
1217 AssertIsOnMainThread();
1219 nsAutoCString originSuffix
;
1220 nsresult rv
= mPrincipal
->GetOriginSuffix(originSuffix
);
1221 if (NS_WARN_IF(NS_FAILED(rv
))) {
1225 if (!strcmp("alertclickcallback", aTopic
)) {
1226 if (XRE_IsParentProcess()) {
1227 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
1228 mozilla::components::ServiceWorkerManager::Service();
1229 if (NS_WARN_IF(!swm
)) {
1230 return NS_ERROR_FAILURE
;
1233 rv
= swm
->SendNotificationClickEvent(
1234 originSuffix
, NS_ConvertUTF16toUTF8(mScope
), mID
, mTitle
, mDir
, mLang
,
1235 mBody
, mTag
, mIcon
, mData
, mBehavior
);
1236 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1238 auto* cc
= ContentChild::GetSingleton();
1239 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1240 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1242 Unused
<< cc
->SendNotificationEvent(u
"click"_ns
, data
);
1247 if (!strcmp("alertfinished", aTopic
)) {
1249 nsresult rv
= Notification::GetOrigin(mPrincipal
, origin
);
1250 if (NS_WARN_IF(NS_FAILED(rv
))) {
1254 // Remove closed or dismissed persistent notifications.
1255 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
1256 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
);
1257 if (notificationStorage
) {
1258 notificationStorage
->Delete(origin
, mID
);
1261 if (XRE_IsParentProcess()) {
1262 nsCOMPtr
<nsIServiceWorkerManager
> swm
=
1263 mozilla::components::ServiceWorkerManager::Service();
1264 if (NS_WARN_IF(!swm
)) {
1265 return NS_ERROR_FAILURE
;
1268 rv
= swm
->SendNotificationCloseEvent(
1269 originSuffix
, NS_ConvertUTF16toUTF8(mScope
), mID
, mTitle
, mDir
, mLang
,
1270 mBody
, mTag
, mIcon
, mData
, mBehavior
);
1271 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
1273 auto* cc
= ContentChild::GetSingleton();
1274 NotificationEventData
data(originSuffix
, NS_ConvertUTF16toUTF8(mScope
),
1275 mID
, mTitle
, mDir
, mLang
, mBody
, mTag
, mIcon
,
1277 Unused
<< cc
->SendNotificationEvent(u
"close"_ns
, data
);
1285 bool Notification::IsInPrivateBrowsing() {
1286 AssertIsOnMainThread();
1288 Document
* doc
= nullptr;
1290 if (mWorkerPrivate
) {
1291 doc
= mWorkerPrivate
->GetDocument();
1292 } else if (GetOwner()) {
1293 doc
= GetOwner()->GetExtantDoc();
1297 nsCOMPtr
<nsILoadContext
> loadContext
= doc
->GetLoadContext();
1298 return loadContext
&& loadContext
->UsePrivateBrowsing();
1301 if (mWorkerPrivate
) {
1302 // Not all workers may have a document, but with Bug 1107516 fixed, they
1303 // should all have a loadcontext.
1304 nsCOMPtr
<nsILoadGroup
> loadGroup
= mWorkerPrivate
->GetLoadGroup();
1305 nsCOMPtr
<nsILoadContext
> loadContext
;
1306 NS_QueryNotificationCallbacks(nullptr, loadGroup
,
1307 NS_GET_IID(nsILoadContext
),
1308 getter_AddRefs(loadContext
));
1309 return loadContext
&& loadContext
->UsePrivateBrowsing();
1312 // XXXnsm Should this default to true?
1316 void Notification::ShowInternal() {
1317 AssertIsOnMainThread();
1318 MOZ_ASSERT(mTempRef
,
1319 "Notification should take ownership of itself before"
1320 "calling ShowInternal!");
1321 // A notification can only have one observer and one call to ShowInternal.
1322 MOZ_ASSERT(!mObserver
);
1324 // Transfer ownership to local scope so we can either release it at the end
1325 // of this function or transfer it to the observer.
1326 UniquePtr
<NotificationRef
> ownership
;
1327 std::swap(ownership
, mTempRef
);
1328 MOZ_ASSERT(ownership
->GetNotification() == this);
1330 nsresult rv
= PersistNotification();
1331 if (NS_FAILED(rv
)) {
1332 NS_WARNING("Could not persist Notification");
1335 nsCOMPtr
<nsIAlertsService
> alertService
= components::Alerts::Service();
1338 NotificationPermission permission
= NotificationPermission::Denied
;
1339 if (mWorkerPrivate
) {
1340 permission
= GetPermissionInternal(mWorkerPrivate
->GetPrincipal(), result
);
1342 permission
= GetPermissionInternal(GetOwner(), result
);
1344 // We rely on GetPermissionInternal returning Denied on all failure codepaths.
1345 MOZ_ASSERT_IF(result
.Failed(), permission
== NotificationPermission::Denied
);
1346 result
.SuppressException();
1347 if (permission
!= NotificationPermission::Granted
|| !alertService
) {
1348 if (mWorkerPrivate
) {
1349 RefPtr
<NotificationEventWorkerRunnable
> r
=
1350 new NotificationEventWorkerRunnable(this, u
"error"_ns
);
1351 if (!r
->Dispatch()) {
1352 NS_WARNING("Could not dispatch event to worker notification");
1355 DispatchTrustedEvent(u
"error"_ns
);
1360 nsAutoString iconUrl
;
1361 nsAutoString soundUrl
;
1362 ResolveIconAndSoundURL(iconUrl
, soundUrl
);
1364 bool isPersistent
= false;
1365 nsCOMPtr
<nsIObserver
> observer
;
1366 if (mScope
.IsEmpty()) {
1367 // Ownership passed to observer.
1368 if (mWorkerPrivate
) {
1369 // Scope better be set on ServiceWorker initiated requests.
1370 MOZ_ASSERT(!mWorkerPrivate
->IsServiceWorker());
1371 // Keep a pointer so that the feature can tell the observer not to release
1372 // the notification.
1373 mObserver
= new WorkerNotificationObserver(std::move(ownership
));
1374 observer
= mObserver
;
1376 observer
= new MainThreadNotificationObserver(std::move(ownership
));
1379 isPersistent
= true;
1380 // This observer does not care about the Notification. It will be released
1381 // at the end of this function.
1383 // The observer is wholly owned by the NotificationObserver passed to the
1385 nsAutoString behavior
;
1386 if (NS_WARN_IF(!mBehavior
.ToJSON(behavior
))) {
1387 behavior
.Truncate();
1389 observer
= new ServiceWorkerNotificationObserver(
1390 mScope
, GetPrincipal(), mID
, mTitle
, DirectionToString(mDir
), mLang
,
1391 mBody
, mTag
, iconUrl
, mDataAsBase64
, behavior
);
1393 MOZ_ASSERT(observer
);
1394 nsCOMPtr
<nsIObserver
> alertObserver
=
1395 new NotificationObserver(observer
, GetPrincipal(), IsInPrivateBrowsing());
1397 // In the case of IPC, the parent process uses the cookie to map to
1398 // nsIObserver. Thus the cookie must be unique to differentiate observers.
1399 nsString uniqueCookie
= u
"notification:"_ns
;
1400 uniqueCookie
.AppendInt(sCount
++);
1401 bool inPrivateBrowsing
= IsInPrivateBrowsing();
1403 bool requireInteraction
= mRequireInteraction
;
1404 if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
1405 requireInteraction
= false;
1408 nsAutoString alertName
;
1409 GetAlertName(alertName
);
1410 nsCOMPtr
<nsIAlertNotification
> alert
=
1411 do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID
);
1412 NS_ENSURE_TRUE_VOID(alert
);
1413 nsIPrincipal
* principal
= GetPrincipal();
1415 alert
->Init(alertName
, iconUrl
, mTitle
, mBody
, true, uniqueCookie
,
1416 DirectionToString(mDir
), mLang
, mDataAsBase64
, GetPrincipal(),
1417 inPrivateBrowsing
, requireInteraction
, mSilent
, mVibrate
);
1418 NS_ENSURE_SUCCESS_VOID(rv
);
1421 JSONStringWriteFunc
<nsAutoCString
> persistentData
;
1422 JSONWriter
w(persistentData
);
1425 nsAutoString origin
;
1426 Notification::GetOrigin(principal
, origin
);
1427 w
.StringProperty("origin", NS_ConvertUTF16toUTF8(origin
));
1429 w
.StringProperty("id", NS_ConvertUTF16toUTF8(mID
));
1431 nsAutoCString originSuffix
;
1432 principal
->GetOriginSuffix(originSuffix
);
1433 w
.StringProperty("originSuffix", originSuffix
);
1437 alertService
->ShowPersistentNotification(
1438 NS_ConvertUTF8toUTF16(persistentData
.StringCRef()), alert
,
1441 alertService
->ShowAlert(alert
, alertObserver
);
1446 bool Notification::RequestPermissionEnabledForScope(JSContext
* aCx
,
1447 JSObject
* /* unused */) {
1448 // requestPermission() is not allowed on workers. The calling page should ask
1449 // for permission on the worker's behalf. This is to prevent 'which window
1450 // should show the browser pop-up'. See discussion:
1451 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
1452 return NS_IsMainThread();
1456 already_AddRefed
<Promise
> Notification::RequestPermission(
1457 const GlobalObject
& aGlobal
,
1458 const Optional
<OwningNonNull
<NotificationPermissionCallback
> >& aCallback
,
1460 AssertIsOnMainThread();
1462 // Get principal from global to make permission request for notifications.
1463 nsCOMPtr
<nsPIDOMWindowInner
> window
=
1464 do_QueryInterface(aGlobal
.GetAsSupports());
1465 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
=
1466 do_QueryInterface(aGlobal
.GetAsSupports());
1467 if (!sop
|| !window
) {
1468 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1472 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1474 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1478 RefPtr
<Promise
> promise
= Promise::Create(window
->AsGlobal(), aRv
);
1482 NotificationPermissionCallback
* permissionCallback
= nullptr;
1483 if (aCallback
.WasPassed()) {
1484 permissionCallback
= &aCallback
.Value();
1486 nsCOMPtr
<nsIRunnable
> request
= new NotificationPermissionRequest(
1487 principal
, window
, promise
, permissionCallback
);
1489 window
->AsGlobal()->Dispatch(request
.forget());
1491 return promise
.forget();
1495 NotificationPermission
Notification::GetPermission(const GlobalObject
& aGlobal
,
1497 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
1498 return GetPermission(global
, aRv
);
1502 NotificationPermission
Notification::GetPermission(nsIGlobalObject
* aGlobal
,
1504 if (NS_IsMainThread()) {
1505 return GetPermissionInternal(aGlobal
->GetAsInnerWindow(), aRv
);
1507 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1509 RefPtr
<GetPermissionRunnable
> r
= new GetPermissionRunnable(worker
);
1510 r
->Dispatch(Canceling
, aRv
);
1512 return NotificationPermission::Denied
;
1515 return r
->GetPermission();
1520 NotificationPermission
Notification::GetPermissionInternal(
1521 nsPIDOMWindowInner
* aWindow
, ErrorResult
& aRv
) {
1522 // Get principal from global to check permission for notifications.
1523 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aWindow
);
1525 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1526 return NotificationPermission::Denied
;
1529 nsCOMPtr
<nsIPrincipal
> principal
= sop
->GetPrincipal();
1531 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1532 return NotificationPermission::Denied
;
1535 if (principal
->GetPrivateBrowsingId() != 0) {
1536 return NotificationPermission::Denied
;
1538 // Disallow showing notification if our origin is not the same origin as the
1539 // toplevel one, see https://github.com/whatwg/notifications/issues/177.
1540 if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) {
1541 nsCOMPtr
<nsIScriptObjectPrincipal
> topSop
=
1542 do_QueryInterface(aWindow
->GetBrowsingContext()->Top()->GetDOMWindow());
1543 nsIPrincipal
* topPrincipal
= topSop
? topSop
->GetPrincipal() : nullptr;
1544 if (!topPrincipal
|| !principal
->Subsumes(topPrincipal
)) {
1545 return NotificationPermission::Denied
;
1549 return GetPermissionInternal(principal
, aRv
);
1553 NotificationPermission
Notification::GetPermissionInternal(
1554 nsIPrincipal
* aPrincipal
, ErrorResult
& aRv
) {
1555 AssertIsOnMainThread();
1556 MOZ_ASSERT(aPrincipal
);
1558 if (aPrincipal
->IsSystemPrincipal()) {
1559 return NotificationPermission::Granted
;
1561 // Allow files to show notifications by default.
1562 if (aPrincipal
->SchemeIs("file")) {
1563 return NotificationPermission::Granted
;
1567 // We also allow notifications is they are pref'ed on.
1568 if (Preferences::GetBool("notification.prompt.testing", false)) {
1569 if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
1570 return NotificationPermission::Granted
;
1572 return NotificationPermission::Denied
;
1576 return TestPermission(aPrincipal
);
1580 NotificationPermission
Notification::TestPermission(nsIPrincipal
* aPrincipal
) {
1581 AssertIsOnMainThread();
1583 uint32_t permission
= nsIPermissionManager::UNKNOWN_ACTION
;
1585 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
1586 components::PermissionManager::Service();
1587 if (!permissionManager
) {
1588 return NotificationPermission::Default
;
1591 permissionManager
->TestExactPermissionFromPrincipal(
1592 aPrincipal
, "desktop-notification"_ns
, &permission
);
1594 // Convert the result to one of the enum types.
1595 switch (permission
) {
1596 case nsIPermissionManager::ALLOW_ACTION
:
1597 return NotificationPermission::Granted
;
1598 case nsIPermissionManager::DENY_ACTION
:
1599 return NotificationPermission::Denied
;
1601 return NotificationPermission::Default
;
1605 nsresult
Notification::ResolveIconAndSoundURL(nsString
& iconUrl
,
1606 nsString
& soundUrl
) {
1607 AssertIsOnMainThread();
1608 nsresult rv
= NS_OK
;
1610 nsIURI
* baseUri
= nullptr;
1612 // XXXnsm If I understand correctly, the character encoding for resolving
1613 // URIs in new specs is dictated by the URL spec, which states that unless
1614 // the URL parser is passed an override encoding, the charset to be used is
1615 // UTF-8. The new Notification icon/sound specification just says to use the
1616 // Fetch API, where the Request constructor defers to URL parsing specifying
1617 // the API base URL and no override encoding. So we've to use UTF-8 on
1618 // workers, but for backwards compat keeping it document charset on main
1620 auto encoding
= UTF_8_ENCODING
;
1622 if (mWorkerPrivate
) {
1623 baseUri
= mWorkerPrivate
->GetBaseURI();
1625 Document
* doc
= GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
1627 baseUri
= doc
->GetBaseURI();
1628 encoding
= doc
->GetDocumentCharacterSet();
1630 NS_WARNING("No document found for main thread notification!");
1631 return NS_ERROR_FAILURE
;
1636 if (mIconUrl
.Length() > 0) {
1637 nsCOMPtr
<nsIURI
> srcUri
;
1638 rv
= NS_NewURI(getter_AddRefs(srcUri
), mIconUrl
, encoding
, baseUri
);
1639 if (NS_SUCCEEDED(rv
)) {
1641 srcUri
->GetSpec(src
);
1642 CopyUTF8toUTF16(src
, iconUrl
);
1645 if (mBehavior
.mSoundFile
.Length() > 0) {
1646 nsCOMPtr
<nsIURI
> srcUri
;
1647 rv
= NS_NewURI(getter_AddRefs(srcUri
), mBehavior
.mSoundFile
, encoding
,
1649 if (NS_SUCCEEDED(rv
)) {
1651 srcUri
->GetSpec(src
);
1652 CopyUTF8toUTF16(src
, soundUrl
);
1660 already_AddRefed
<Promise
> Notification::Get(
1661 nsPIDOMWindowInner
* aWindow
, const GetNotificationOptions
& aFilter
,
1662 const nsAString
& aScope
, ErrorResult
& aRv
) {
1663 AssertIsOnMainThread();
1664 MOZ_ASSERT(aWindow
);
1666 nsCOMPtr
<Document
> doc
= aWindow
->GetExtantDoc();
1668 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1673 aRv
= GetOrigin(doc
->NodePrincipal(), origin
);
1678 RefPtr
<Promise
> promise
= Promise::Create(aWindow
->AsGlobal(), aRv
);
1683 nsCOMPtr
<nsINotificationStorageCallback
> callback
=
1684 new NotificationStorageCallback(aWindow
->AsGlobal(), aScope
, promise
);
1686 RefPtr
<NotificationGetRunnable
> r
=
1687 new NotificationGetRunnable(origin
, aFilter
.mTag
, callback
);
1689 aRv
= aWindow
->AsGlobal()->Dispatch(r
.forget());
1690 if (NS_WARN_IF(aRv
.Failed())) {
1694 return promise
.forget();
1697 class WorkerGetResultRunnable final
: public NotificationWorkerRunnable
{
1698 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1699 const nsTArray
<NotificationStrings
> mStrings
;
1702 WorkerGetResultRunnable(WorkerPrivate
* aWorkerPrivate
,
1703 PromiseWorkerProxy
* aPromiseProxy
,
1704 nsTArray
<NotificationStrings
>&& aStrings
)
1705 : NotificationWorkerRunnable(aWorkerPrivate
),
1706 mPromiseProxy(aPromiseProxy
),
1707 mStrings(std::move(aStrings
)) {}
1709 void WorkerRunInternal(WorkerPrivate
* aWorkerPrivate
) override
{
1710 RefPtr
<Promise
> workerPromise
= mPromiseProxy
->WorkerPromise();
1713 AutoTArray
<RefPtr
<Notification
>, 5> notifications
;
1714 for (uint32_t i
= 0; i
< mStrings
.Length(); ++i
) {
1715 RefPtr
<Notification
> n
= Notification::ConstructFromFields(
1716 aWorkerPrivate
->GlobalScope(), mStrings
[i
].mID
, mStrings
[i
].mTitle
,
1717 mStrings
[i
].mDir
, mStrings
[i
].mLang
, mStrings
[i
].mBody
,
1718 mStrings
[i
].mTag
, mStrings
[i
].mIcon
, mStrings
[i
].mData
,
1719 /* mStrings[i].mBehavior, not
1721 mStrings
[i
].mServiceWorkerRegistrationScope
, result
);
1723 n
->SetStoredState(true);
1724 Unused
<< NS_WARN_IF(result
.Failed());
1725 if (!result
.Failed()) {
1726 notifications
.AppendElement(n
.forget());
1730 workerPromise
->MaybeResolve(notifications
);
1731 mPromiseProxy
->CleanUp();
1735 class WorkerGetCallback final
: public ScopeCheckingGetCallback
{
1736 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1741 WorkerGetCallback(PromiseWorkerProxy
* aProxy
, const nsAString
& aScope
)
1742 : ScopeCheckingGetCallback(aScope
), mPromiseProxy(aProxy
) {
1743 AssertIsOnMainThread();
1747 NS_IMETHOD
Done() final
{
1748 AssertIsOnMainThread();
1749 MOZ_ASSERT(mPromiseProxy
, "Was Done() called twice?");
1751 RefPtr
<PromiseWorkerProxy
> proxy
= std::move(mPromiseProxy
);
1752 MutexAutoLock
lock(proxy
->Lock());
1753 if (proxy
->CleanedUp()) {
1757 RefPtr
<WorkerGetResultRunnable
> r
= new WorkerGetResultRunnable(
1758 proxy
->GetWorkerPrivate(), proxy
, std::move(mStrings
));
1765 ~WorkerGetCallback() = default;
1768 NS_IMPL_ISUPPORTS(WorkerGetCallback
, nsINotificationStorageCallback
)
1770 class WorkerGetRunnable final
: public Runnable
{
1771 RefPtr
<PromiseWorkerProxy
> mPromiseProxy
;
1772 const nsString mTag
;
1773 const nsString mScope
;
1776 WorkerGetRunnable(PromiseWorkerProxy
* aProxy
, const nsAString
& aTag
,
1777 const nsAString
& aScope
)
1778 : Runnable("WorkerGetRunnable"),
1779 mPromiseProxy(aProxy
),
1782 MOZ_ASSERT(mPromiseProxy
);
1787 AssertIsOnMainThread();
1788 nsCOMPtr
<nsINotificationStorageCallback
> callback
=
1789 new WorkerGetCallback(mPromiseProxy
, mScope
);
1792 nsCOMPtr
<nsINotificationStorage
> notificationStorage
=
1793 do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID
, &rv
);
1794 if (NS_WARN_IF(NS_FAILED(rv
))) {
1799 MutexAutoLock
lock(mPromiseProxy
->Lock());
1800 if (mPromiseProxy
->CleanedUp()) {
1805 rv
= Notification::GetOrigin(
1806 mPromiseProxy
->GetWorkerPrivate()->GetPrincipal(), origin
);
1807 if (NS_WARN_IF(NS_FAILED(rv
))) {
1812 rv
= notificationStorage
->Get(origin
, mTag
, callback
);
1813 if (NS_WARN_IF(NS_FAILED(rv
))) {
1822 ~WorkerGetRunnable() = default;
1826 already_AddRefed
<Promise
> Notification::WorkerGet(
1827 WorkerPrivate
* aWorkerPrivate
, const GetNotificationOptions
& aFilter
,
1828 const nsAString
& aScope
, ErrorResult
& aRv
) {
1829 MOZ_ASSERT(aWorkerPrivate
);
1830 aWorkerPrivate
->AssertIsOnWorkerThread();
1831 RefPtr
<Promise
> p
= Promise::Create(aWorkerPrivate
->GlobalScope(), aRv
);
1832 if (NS_WARN_IF(aRv
.Failed())) {
1836 RefPtr
<PromiseWorkerProxy
> proxy
=
1837 PromiseWorkerProxy::Create(aWorkerPrivate
, p
);
1839 aRv
.Throw(NS_ERROR_DOM_ABORT_ERR
);
1843 RefPtr
<WorkerGetRunnable
> r
=
1844 new WorkerGetRunnable(proxy
, aFilter
.mTag
, aScope
);
1845 // Since this is called from script via
1846 // ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
1847 MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate
->DispatchToMainThread(r
.forget()));
1851 JSObject
* Notification::WrapObject(JSContext
* aCx
,
1852 JS::Handle
<JSObject
*> aGivenProto
) {
1853 return mozilla::dom::Notification_Binding::Wrap(aCx
, this, aGivenProto
);
1856 void Notification::Close() {
1857 AssertIsOnTargetThread();
1858 auto ref
= MakeUnique
<NotificationRef
>(this);
1859 if (!ref
->Initialized()) {
1863 nsCOMPtr
<nsIRunnable
> closeNotificationTask
= new NotificationTask(
1864 "Notification::Close", std::move(ref
), NotificationTask::eClose
);
1865 nsresult rv
= DispatchToMainThread(closeNotificationTask
.forget());
1867 if (NS_FAILED(rv
)) {
1868 DispatchTrustedEvent(u
"error"_ns
);
1869 // If dispatch fails, NotificationTask will release the ref when it goes
1870 // out of scope at the end of this function.
1874 void Notification::CloseInternal(bool aContextClosed
) {
1875 AssertIsOnMainThread();
1876 // Transfer ownership (if any) to local scope so we can release it at the end
1877 // of this function. This is relevant when the call is from
1878 // NotificationTask::Run().
1879 UniquePtr
<NotificationRef
> ownership
;
1880 std::swap(ownership
, mTempRef
);
1883 UnpersistNotification();
1885 nsCOMPtr
<nsIAlertsService
> alertService
= components::Alerts::Service();
1887 nsAutoString alertName
;
1888 GetAlertName(alertName
);
1889 alertService
->CloseAlert(alertName
, aContextClosed
);
1894 nsresult
Notification::GetOrigin(nsIPrincipal
* aPrincipal
, nsString
& aOrigin
) {
1896 return NS_ERROR_FAILURE
;
1900 nsContentUtils::GetWebExposedOriginSerialization(aPrincipal
, aOrigin
);
1901 NS_ENSURE_SUCCESS(rv
, rv
);
1906 bool Notification::RequireInteraction() const { return mRequireInteraction
; }
1908 bool Notification::Silent() const { return mSilent
; }
1910 void Notification::GetVibrate(nsTArray
<uint32_t>& aRetval
) const {
1911 aRetval
= mVibrate
.Clone();
1914 void Notification::GetData(JSContext
* aCx
,
1915 JS::MutableHandle
<JS::Value
> aRetval
) {
1916 if (mData
.isNull() && !mDataAsBase64
.IsEmpty()) {
1918 RefPtr
<nsStructuredCloneContainer
> container
=
1919 new nsStructuredCloneContainer();
1920 rv
= container
->InitFromBase64(mDataAsBase64
, JS_STRUCTURED_CLONE_VERSION
);
1921 if (NS_WARN_IF(NS_FAILED(rv
))) {
1926 JS::Rooted
<JS::Value
> data(aCx
);
1927 rv
= container
->DeserializeToJsval(aCx
, &data
);
1928 if (NS_WARN_IF(NS_FAILED(rv
))) {
1933 if (data
.isGCThing()) {
1934 mozilla::HoldJSObjects(this);
1938 if (mData
.isNull()) {
1946 void Notification::InitFromJSVal(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
1948 if (!mDataAsBase64
.IsEmpty() || aData
.isNull()) {
1951 RefPtr
<nsStructuredCloneContainer
> dataObjectContainer
=
1952 new nsStructuredCloneContainer();
1953 aRv
= dataObjectContainer
->InitFromJSVal(aData
, aCx
);
1954 if (NS_WARN_IF(aRv
.Failed())) {
1958 aRv
= dataObjectContainer
->GetDataAsBase64(mDataAsBase64
);
1959 if (NS_WARN_IF(aRv
.Failed())) {
1964 void Notification::InitFromBase64(const nsAString
& aData
, ErrorResult
& aRv
) {
1965 if (!mDataAsBase64
.IsEmpty() || aData
.IsEmpty()) {
1969 // To and fro to ensure it is valid base64.
1970 RefPtr
<nsStructuredCloneContainer
> container
=
1971 new nsStructuredCloneContainer();
1972 aRv
= container
->InitFromBase64(aData
, JS_STRUCTURED_CLONE_VERSION
);
1973 if (NS_WARN_IF(aRv
.Failed())) {
1977 aRv
= container
->GetDataAsBase64(mDataAsBase64
);
1978 if (NS_WARN_IF(aRv
.Failed())) {
1983 bool Notification::AddRefObject() {
1984 AssertIsOnTargetThread();
1985 MOZ_ASSERT_IF(mWorkerPrivate
&& !mWorkerRef
, mTaskCount
== 0);
1986 MOZ_ASSERT_IF(mWorkerPrivate
&& mWorkerRef
, mTaskCount
> 0);
1987 if (mWorkerPrivate
&& !mWorkerRef
) {
1988 if (!CreateWorkerRef()) {
1997 void Notification::ReleaseObject() {
1998 AssertIsOnTargetThread();
1999 MOZ_ASSERT(mTaskCount
> 0);
2000 MOZ_ASSERT_IF(mWorkerPrivate
, mWorkerRef
);
2003 if (mWorkerPrivate
&& mTaskCount
== 0) {
2004 MOZ_ASSERT(mWorkerRef
);
2005 mWorkerRef
= nullptr;
2011 * Called from the worker, runs on main thread, blocks worker.
2013 * We can freely access mNotification here because the feature supplied it and
2014 * the Notification owns the feature.
2016 class CloseNotificationRunnable final
: public WorkerMainThreadRunnable
{
2017 Notification
* mNotification
;
2021 explicit CloseNotificationRunnable(Notification
* aNotification
)
2022 : WorkerMainThreadRunnable(aNotification
->mWorkerPrivate
,
2023 "Notification :: Close Notification"_ns
),
2024 mNotification(aNotification
),
2025 mHadObserver(false) {}
2027 bool MainThreadRun() override
{
2028 if (mNotification
->mObserver
) {
2029 // The Notify() take's responsibility of releasing the Notification.
2030 mNotification
->mObserver
->ForgetNotification();
2031 mNotification
->mObserver
= nullptr;
2032 mHadObserver
= true;
2034 mNotification
->CloseInternal();
2038 bool HadObserver() { return mHadObserver
; }
2041 bool Notification::CreateWorkerRef() {
2042 MOZ_ASSERT(mWorkerPrivate
);
2043 mWorkerPrivate
->AssertIsOnWorkerThread();
2044 MOZ_ASSERT(!mWorkerRef
);
2046 RefPtr
<Notification
> self
= this;
2048 StrongWorkerRef::Create(mWorkerPrivate
, "Notification", [self
]() {
2049 // CloseNotificationRunnable blocks the worker by pushing a sync event
2050 // loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
2051 // the worker can still continue running. One of these is
2052 // ReleaseNotificationControlRunnable that releases the notification,
2053 // invalidating the notification and this feature. We hold this
2054 // reference to keep the notification valid until we are done with it.
2056 // An example of when the control runnable could get dispatched to the
2057 // worker is if a Notification is created and the worker is immediately
2058 // closed, but there is no permission to show it so that the main thread
2059 // immediately drops the NotificationRef. In this case, this function
2060 // blocks on the main thread, but the main thread dispatches the control
2061 // runnable, invalidating mNotification.
2063 // Dispatched to main thread, blocks on closing the Notification.
2064 RefPtr
<CloseNotificationRunnable
> r
=
2065 new CloseNotificationRunnable(self
);
2067 r
->Dispatch(Killing
, rv
);
2068 // XXXbz I'm told throwing and returning false from here is pointless
2069 // (and also that doing sync stuff from here is really weird), so I
2070 // guess we just suppress the exception on rv, if any.
2071 rv
.SuppressException();
2073 // Only call ReleaseObject() to match the observer's NotificationRef
2074 // ownership (since CloseNotificationRunnable asked the observer to drop
2075 // the reference to the notification).
2076 if (r
->HadObserver()) {
2077 self
->ReleaseObject();
2080 // From this point we cannot touch properties of this feature because
2081 // ReleaseObject() may have led to the notification going away and the
2082 // notification owns this feature!
2085 if (NS_WARN_IF(!mWorkerRef
)) {
2094 * 1) Is aWorker allowed to show a notification for scope?
2095 * 2) Is aWorker an active worker?
2097 * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
2099 class CheckLoadRunnable final
: public WorkerMainThreadRunnable
{
2102 ServiceWorkerRegistrationDescriptor mDescriptor
;
2105 explicit CheckLoadRunnable(
2106 WorkerPrivate
* aWorker
, const nsACString
& aScope
,
2107 const ServiceWorkerRegistrationDescriptor
& aDescriptor
)
2108 : WorkerMainThreadRunnable(aWorker
, "Notification :: Check Load"_ns
),
2109 mRv(NS_ERROR_DOM_SECURITY_ERR
),
2111 mDescriptor(aDescriptor
) {}
2113 bool MainThreadRun() override
{
2114 nsIPrincipal
* principal
= mWorkerPrivate
->GetPrincipal();
2115 mRv
= CheckScope(principal
, mScope
, mWorkerPrivate
->WindowID());
2117 if (NS_FAILED(mRv
)) {
2121 auto activeWorker
= mDescriptor
.GetActive();
2123 if (!activeWorker
||
2124 activeWorker
.ref().Id() != mWorkerPrivate
->ServiceWorkerID()) {
2125 mRv
= NS_ERROR_NOT_AVAILABLE
;
2131 nsresult
Result() { return mRv
; }
2135 already_AddRefed
<Promise
> Notification::ShowPersistentNotification(
2136 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aScope
,
2137 const nsAString
& aTitle
, const NotificationOptions
& aOptions
,
2138 const ServiceWorkerRegistrationDescriptor
& aDescriptor
, ErrorResult
& aRv
) {
2139 MOZ_ASSERT(aGlobal
);
2142 // XXXnsm: This may be slow due to blocking the worker and waiting on the main
2143 // thread. On calls from content, we can be sure the scope is valid since
2144 // ServiceWorkerRegistrations have their scope set correctly. Can this be made
2145 // debug only? The problem is that there would be different semantics in
2146 // debug and non-debug builds in such a case.
2147 if (NS_IsMainThread()) {
2148 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(aGlobal
);
2149 if (NS_WARN_IF(!sop
)) {
2150 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2154 nsIPrincipal
* principal
= sop
->GetPrincipal();
2155 if (NS_WARN_IF(!principal
)) {
2156 aRv
.Throw(NS_ERROR_UNEXPECTED
);
2160 uint64_t windowID
= 0;
2161 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
);
2163 windowID
= win
->WindowID();
2166 aRv
= CheckScope(principal
, NS_ConvertUTF16toUTF8(aScope
), windowID
);
2167 if (NS_WARN_IF(aRv
.Failed())) {
2168 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2172 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
2174 worker
->AssertIsOnWorkerThread();
2176 RefPtr
<CheckLoadRunnable
> loadChecker
= new CheckLoadRunnable(
2177 worker
, NS_ConvertUTF16toUTF8(aScope
), aDescriptor
);
2178 loadChecker
->Dispatch(Canceling
, aRv
);
2183 if (NS_WARN_IF(NS_FAILED(loadChecker
->Result()))) {
2184 if (loadChecker
->Result() == NS_ERROR_NOT_AVAILABLE
) {
2185 aRv
.ThrowTypeError
<MSG_NO_ACTIVE_WORKER
>(NS_ConvertUTF16toUTF8(aScope
));
2187 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
2193 RefPtr
<Promise
> p
= Promise::Create(aGlobal
, aRv
);
2194 if (NS_WARN_IF(aRv
.Failed())) {
2198 // We check permission here rather than pass the Promise to NotificationTask
2199 // which leads to uglier code.
2200 NotificationPermission permission
= GetPermission(aGlobal
, aRv
);
2202 // "If permission for notification's origin is not "granted", reject promise
2203 // with a TypeError exception, and terminate these substeps."
2204 if (NS_WARN_IF(aRv
.Failed()) ||
2205 permission
== NotificationPermission::Denied
) {
2206 p
->MaybeRejectWithTypeError("Permission to show Notification denied.");
2210 // "Otherwise, resolve promise with undefined."
2211 // The Notification may still not be shown due to other errors, but the spec
2212 // is not concerned with those.
2213 p
->MaybeResolveWithUndefined();
2215 RefPtr
<Notification
> notification
=
2216 CreateAndShow(aCx
, aGlobal
, aTitle
, aOptions
, aScope
, aRv
);
2217 if (NS_WARN_IF(aRv
.Failed())) {
2225 already_AddRefed
<Notification
> Notification::CreateAndShow(
2226 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aTitle
,
2227 const NotificationOptions
& aOptions
, const nsAString
& aScope
,
2229 MOZ_ASSERT(aGlobal
);
2231 RefPtr
<Notification
> notification
=
2232 CreateInternal(aGlobal
, u
""_ns
, aTitle
, aOptions
, aRv
);
2237 // Make a structured clone of the aOptions.mData object
2238 JS::Rooted
<JS::Value
> data(aCx
, aOptions
.mData
);
2239 notification
->InitFromJSVal(aCx
, data
, aRv
);
2240 if (NS_WARN_IF(aRv
.Failed())) {
2244 notification
->SetScope(aScope
);
2246 auto ref
= MakeUnique
<NotificationRef
>(notification
);
2247 if (NS_WARN_IF(!ref
->Initialized())) {
2248 aRv
.Throw(NS_ERROR_DOM_ABORT_ERR
);
2252 // Queue a task to show the notification.
2253 nsCOMPtr
<nsIRunnable
> showNotificationTask
= new NotificationTask(
2254 "Notification::CreateAndShow", std::move(ref
), NotificationTask::eShow
);
2257 notification
->DispatchToMainThread(showNotificationTask
.forget());
2259 if (NS_WARN_IF(NS_FAILED(rv
))) {
2260 notification
->DispatchTrustedEvent(u
"error"_ns
);
2263 return notification
.forget();
2267 nsresult
Notification::RemovePermission(nsIPrincipal
* aPrincipal
) {
2268 MOZ_ASSERT(XRE_IsParentProcess());
2269 nsCOMPtr
<nsIPermissionManager
> permissionManager
=
2270 mozilla::components::PermissionManager::Service();
2271 if (!permissionManager
) {
2272 return NS_ERROR_FAILURE
;
2274 permissionManager
->RemoveFromPrincipal(aPrincipal
, "desktop-notification"_ns
);
2279 nsresult
Notification::OpenSettings(nsIPrincipal
* aPrincipal
) {
2280 MOZ_ASSERT(XRE_IsParentProcess());
2281 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
2283 return NS_ERROR_FAILURE
;
2285 // Notify other observers so they can show settings UI.
2286 obs
->NotifyObservers(aPrincipal
, "notifications-open-settings", nullptr);
2291 Notification::Observe(nsISupports
* aSubject
, const char* aTopic
,
2292 const char16_t
* aData
) {
2293 AssertIsOnMainThread();
2295 if (!strcmp(aTopic
, DOM_WINDOW_DESTROYED_TOPIC
) ||
2296 !strcmp(aTopic
, DOM_WINDOW_FROZEN_TOPIC
)) {
2297 nsCOMPtr
<nsPIDOMWindowInner
> window
= GetOwner();
2298 if (SameCOMIdentity(aSubject
, window
)) {
2299 nsCOMPtr
<nsIObserverService
> obs
=
2300 mozilla::services::GetObserverService();
2302 obs
->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC
);
2303 obs
->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC
);
2306 CloseInternal(true);
2313 nsresult
Notification::DispatchToMainThread(
2314 already_AddRefed
<nsIRunnable
>&& aRunnable
) {
2315 if (mWorkerPrivate
) {
2316 return mWorkerPrivate
->DispatchToMainThread(std::move(aRunnable
));
2318 AssertIsOnMainThread();
2319 return NS_DispatchToCurrentThread(std::move(aRunnable
));
2322 } // namespace mozilla::dom