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 #ifndef mozilla_dom_notification_h__
8 #define mozilla_dom_notification_h__
10 #include "mozilla/DOMEventTargetHelper.h"
11 #include "mozilla/UniquePtr.h"
12 #include "mozilla/dom/NotificationBinding.h"
14 #include "nsIObserver.h"
15 #include "nsISupports.h"
17 #include "nsCycleCollectionParticipant.h"
18 #include "nsHashKeys.h"
19 #include "nsTHashtable.h"
20 #include "nsWeakReference.h"
28 class NotificationRef
;
29 class WorkerNotificationObserver
;
31 class StrongWorkerRef
;
35 * Notifications on workers introduce some lifetime issues. The property we
36 * are trying to satisfy is:
37 * Whenever a task is dispatched to the main thread to operate on
38 * a Notification, the Notification should be addrefed on the worker thread
39 * and a feature should be added to observe the worker lifetime. This main
40 * thread owner should ensure it properly releases the reference to the
41 * Notification, additionally removing the feature if necessary.
43 * To enforce the correct addref and release, along with managing the feature,
44 * we introduce a NotificationRef. Only one object may ever own
45 * a NotificationRef, so UniquePtr<> is used throughout. The NotificationRef
46 * constructor calls AddRefObject(). When it is destroyed (on any thread) it
47 * releases the Notification on the correct thread.
49 * Code should only access the underlying Notification object when it can
50 * guarantee that it retains ownership of the NotificationRef while doing so.
52 * The one kink in this mechanism is that the worker feature may be Notify()ed
53 * if the worker stops running script, even if the Notification's corresponding
54 * UI is still visible to the user. We handle this case with the following
56 * a) Close the notification. This is done by blocking the worker on the main
57 * thread. This ensures that there are no main thread holders when the worker
58 * resumes. This also deals with the case where Notify() runs on the worker
59 * before the observer has been created on the main thread. Even in such
60 * a situation, the CloseNotificationRunnable() will only run after the
61 * Show task that was previously queued. Since the show task is only queued
62 * once when the Notification is created, we can be sure that no new tasks
63 * will follow the Notify().
65 * b) Ask the observer to let go of its NotificationRef's underlying
66 * Notification without proper cleanup since the feature will handle the
67 * release. This is only OK because every notification has only one
68 * associated observer. The NotificationRef itself is still owned by the
69 * observer and deleted by the UniquePtr, but it doesn't do anything since
70 * the underlying Notification is null.
72 * To unify code-paths, we use the same NotificationRef in the main
73 * thread implementation too.
75 * Note that the Notification's JS wrapper does it's standard
76 * AddRef()/Release() and is not affected by any of this.
78 * Since the worker event queue can have runnables that will dispatch events on
79 * the Notification, the NotificationRef destructor will first try to release
80 * the Notification by dispatching a normal runnable to the worker so that it is
81 * queued after any event runnables. If that dispatch fails, it means the worker
82 * is no longer running and queued WorkerRunnables will be canceled, so we
83 * dispatch a control runnable instead.
86 class Notification
: public DOMEventTargetHelper
,
88 public nsSupportsWeakReference
{
89 friend class CloseNotificationRunnable
;
90 friend class NotificationTask
;
91 friend class NotificationPermissionRequest
;
92 friend class MainThreadNotificationObserver
;
93 friend class NotificationStorageCallback
;
94 friend class ServiceWorkerNotificationObserver
;
95 friend class WorkerGetRunnable
;
96 friend class WorkerNotificationObserver
;
99 IMPL_EVENT_HANDLER(click
)
100 IMPL_EVENT_HANDLER(show
)
101 IMPL_EVENT_HANDLER(error
)
102 IMPL_EVENT_HANDLER(close
)
104 NS_DECL_ISUPPORTS_INHERITED
105 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Notification
,
106 DOMEventTargetHelper
)
109 static bool PrefEnabled(JSContext
* aCx
, JSObject
* aObj
);
110 // Returns if Notification.get() is allowed for the current global.
111 static bool IsGetEnabled(JSContext
* aCx
, JSObject
* aObj
);
113 static already_AddRefed
<Notification
> Constructor(
114 const GlobalObject
& aGlobal
, const nsAString
& aTitle
,
115 const NotificationOptions
& aOption
, ErrorResult
& aRv
);
118 * Used when dispatching the ServiceWorkerEvent.
120 * Does not initialize the Notification's behavior.
122 * 1) The Notification is not shown to the user and so the behavior
123 * parameters don't matter.
124 * 2) The default binding requires main thread for parsing the JSON from the
127 static already_AddRefed
<Notification
> ConstructFromFields(
128 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
129 const nsAString
& aDir
, const nsAString
& aLang
, const nsAString
& aBody
,
130 const nsAString
& aTag
, const nsAString
& aIcon
, const nsAString
& aData
,
131 const nsAString
& aServiceWorkerRegistrationScope
, ErrorResult
& aRv
);
133 void GetID(nsAString
& aRetval
) { aRetval
= mID
; }
135 void GetTitle(nsAString
& aRetval
) { aRetval
= mTitle
; }
137 NotificationDirection
Dir() { return mDir
; }
139 void GetLang(nsAString
& aRetval
) { aRetval
= mLang
; }
141 void GetBody(nsAString
& aRetval
) { aRetval
= mBody
; }
143 void GetTag(nsAString
& aRetval
) { aRetval
= mTag
; }
145 void GetIcon(nsAString
& aRetval
) { aRetval
= mIconUrl
; }
147 void SetStoredState(bool val
) { mIsStored
= val
; }
149 bool IsStored() { return mIsStored
; }
151 static bool RequestPermissionEnabledForScope(JSContext
* aCx
,
152 JSObject
* /* unused */);
154 static already_AddRefed
<Promise
> RequestPermission(
155 const GlobalObject
& aGlobal
,
156 const Optional
<OwningNonNull
<NotificationPermissionCallback
> >& aCallback
,
159 static NotificationPermission
GetPermission(const GlobalObject
& aGlobal
,
162 static already_AddRefed
<Promise
> Get(nsPIDOMWindowInner
* aWindow
,
163 const GetNotificationOptions
& aFilter
,
164 const nsAString
& aScope
,
167 static already_AddRefed
<Promise
> Get(const GlobalObject
& aGlobal
,
168 const GetNotificationOptions
& aFilter
,
171 static already_AddRefed
<Promise
> WorkerGet(
172 WorkerPrivate
* aWorkerPrivate
, const GetNotificationOptions
& aFilter
,
173 const nsAString
& aScope
, ErrorResult
& aRv
);
175 // Notification implementation of
176 // ServiceWorkerRegistration.showNotification.
179 // Note that aCx may not be in the compartment of aGlobal, but aOptions will
180 // have its JS things in the compartment of aCx.
181 static already_AddRefed
<Promise
> ShowPersistentNotification(
182 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aScope
,
183 const nsAString
& aTitle
, const NotificationOptions
& aOptions
,
184 const ServiceWorkerRegistrationDescriptor
& aDescriptor
, ErrorResult
& aRv
);
188 nsPIDOMWindowInner
* GetParentObject() { return GetOwner(); }
190 virtual JSObject
* WrapObject(JSContext
* aCx
,
191 JS::Handle
<JSObject
*> aGivenProto
) override
;
193 bool RequireInteraction() const;
195 void GetData(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aRetval
);
197 void InitFromJSVal(JSContext
* aCx
, JS::Handle
<JS::Value
> aData
,
200 void InitFromBase64(const nsAString
& aData
, ErrorResult
& aRv
);
202 void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); }
204 // Initialized on the worker thread, never unset, and always used in
205 // a read-only capacity. Used on any thread.
206 WorkerPrivate
* mWorkerPrivate
;
209 WorkerNotificationObserver
* mObserver
;
211 // The NotificationTask calls ShowInternal()/CloseInternal() on the
212 // Notification. At this point the task has ownership of the Notification. It
213 // passes this on to the Notification itself via mTempRef so that
214 // ShowInternal()/CloseInternal() may pass it along appropriately (or release
218 UniquePtr
<NotificationRef
> mTempRef
;
220 // Returns true if addref succeeded.
222 void ReleaseObject();
224 static NotificationPermission
GetPermission(nsIGlobalObject
* aGlobal
,
227 static NotificationPermission
GetPermissionInternal(nsIPrincipal
* aPrincipal
,
230 static NotificationPermission
TestPermission(nsIPrincipal
* aPrincipal
);
232 bool DispatchClickEvent();
234 static nsresult
RemovePermission(nsIPrincipal
* aPrincipal
);
235 static nsresult
OpenSettings(nsIPrincipal
* aPrincipal
);
237 nsresult
DispatchToMainThread(already_AddRefed
<nsIRunnable
>&& aRunnable
);
240 Notification(nsIGlobalObject
* aGlobal
, const nsAString
& aID
,
241 const nsAString
& aTitle
, const nsAString
& aBody
,
242 NotificationDirection aDir
, const nsAString
& aLang
,
243 const nsAString
& aTag
, const nsAString
& aIconUrl
,
244 bool aRequireNotification
,
245 const NotificationBehavior
& aBehavior
);
247 static already_AddRefed
<Notification
> CreateInternal(
248 nsIGlobalObject
* aGlobal
, const nsAString
& aID
, const nsAString
& aTitle
,
249 const NotificationOptions
& aOptions
);
252 bool IsInPrivateBrowsing();
254 void CloseInternal();
256 static NotificationPermission
GetPermissionInternal(nsISupports
* aGlobal
,
259 static const nsString
DirectionToString(NotificationDirection aDirection
) {
260 switch (aDirection
) {
261 case NotificationDirection::Ltr
:
263 case NotificationDirection::Rtl
:
270 static NotificationDirection
StringToDirection(const nsAString
& aDirection
) {
271 if (aDirection
.EqualsLiteral("ltr")) {
272 return NotificationDirection::Ltr
;
274 if (aDirection
.EqualsLiteral("rtl")) {
275 return NotificationDirection::Rtl
;
277 return NotificationDirection::Auto
;
280 static nsresult
GetOrigin(nsIPrincipal
* aPrincipal
, nsString
& aOrigin
);
282 void GetAlertName(nsAString
& aRetval
) {
283 AssertIsOnMainThread();
284 if (mAlertName
.IsEmpty()) {
287 aRetval
= mAlertName
;
290 void GetScope(nsAString
& aScope
) { aScope
= mScope
; }
292 void SetScope(const nsAString
& aScope
) {
293 MOZ_ASSERT(mScope
.IsEmpty());
298 const nsString mTitle
;
299 const nsString mBody
;
300 const NotificationDirection mDir
;
301 const nsString mLang
;
303 const nsString mIconUrl
;
304 const bool mRequireInteraction
;
305 nsString mDataAsBase64
;
306 const NotificationBehavior mBehavior
;
308 // It's null until GetData is first called
309 JS::Heap
<JS::Value
> mData
;
317 // We need to make a distinction between the notification being closed i.e.
318 // removed from any pending or active lists, and the notification being
319 // removed from the database. NotificationDB might fail when trying to remove
323 static uint32_t sCount
;
326 virtual ~Notification();
328 // Creates a Notification and shows it. Returns a reference to the
329 // Notification if result is NS_OK. The lifetime of this Notification is tied
330 // to an underlying NotificationRef. Do not hold a non-stack raw pointer to
331 // it. Be careful about thread safety if acquiring a strong reference.
333 // Note that aCx may not be in the compartment of aGlobal, but aOptions will
334 // have its JS things in the compartment of aCx.
335 static already_AddRefed
<Notification
> CreateAndShow(
336 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, const nsAString
& aTitle
,
337 const NotificationOptions
& aOptions
, const nsAString
& aScope
,
340 nsIPrincipal
* GetPrincipal();
342 nsresult
PersistNotification();
343 void UnpersistNotification();
347 bool IsTargetThread() const { return NS_IsMainThread() == !mWorkerPrivate
; }
349 bool CreateWorkerRef();
351 nsresult
ResolveIconAndSoundURL(nsString
&, nsString
&);
353 // Only used for Notifications on Workers, worker thread only.
354 RefPtr
<StrongWorkerRef
> mWorkerRef
;
355 // Target thread only.
360 } // namespace mozilla
362 #endif // mozilla_dom_notification_h__