Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef mozilla_dom_notification_h__
7 : #define mozilla_dom_notification_h__
8 :
9 : #include "mozilla/DOMEventTargetHelper.h"
10 : #include "mozilla/UniquePtr.h"
11 : #include "mozilla/dom/NotificationBinding.h"
12 : #include "mozilla/dom/workers/bindings/WorkerHolder.h"
13 :
14 : #include "nsIObserver.h"
15 : #include "nsISupports.h"
16 :
17 : #include "nsCycleCollectionParticipant.h"
18 : #include "nsHashKeys.h"
19 : #include "nsTHashtable.h"
20 : #include "nsWeakReference.h"
21 :
22 : #define NOTIFICATIONTELEMETRYSERVICE_CONTRACTID \
23 : "@mozilla.org/notificationTelemetryService;1"
24 :
25 : class nsIPrincipal;
26 : class nsIVariant;
27 :
28 : namespace mozilla {
29 : namespace dom {
30 :
31 : class NotificationRef;
32 : class WorkerNotificationObserver;
33 : class Promise;
34 :
35 : namespace workers {
36 : class WorkerPrivate;
37 : } // namespace workers
38 :
39 : class Notification;
40 0 : class NotificationWorkerHolder final : public workers::WorkerHolder
41 : {
42 : // Since the feature is strongly held by a Notification, it is ok to hold
43 : // a raw pointer here.
44 : Notification* mNotification;
45 :
46 : public:
47 : explicit NotificationWorkerHolder(Notification* aNotification);
48 :
49 : bool
50 : Notify(workers::Status aStatus) override;
51 : };
52 :
53 : // Records telemetry probes at application startup, when a notification is
54 : // shown, and when the notification permission is revoked for a site.
55 : class NotificationTelemetryService final : public nsISupports
56 : {
57 : public:
58 : NS_DECL_ISUPPORTS
59 :
60 : NotificationTelemetryService();
61 :
62 : static already_AddRefed<NotificationTelemetryService> GetInstance();
63 :
64 : nsresult Init();
65 : void RecordDNDSupported();
66 : void RecordPermissions();
67 :
68 : private:
69 : virtual ~NotificationTelemetryService();
70 :
71 : bool GetNotificationPermission(nsISupports* aSupports,
72 : uint32_t* aCapability);
73 :
74 : bool mDNDRecorded;
75 : };
76 :
77 : /*
78 : * Notifications on workers introduce some lifetime issues. The property we
79 : * are trying to satisfy is:
80 : * Whenever a task is dispatched to the main thread to operate on
81 : * a Notification, the Notification should be addrefed on the worker thread
82 : * and a feature should be added to observe the worker lifetime. This main
83 : * thread owner should ensure it properly releases the reference to the
84 : * Notification, additionally removing the feature if necessary.
85 : *
86 : * To enforce the correct addref and release, along with managing the feature,
87 : * we introduce a NotificationRef. Only one object may ever own
88 : * a NotificationRef, so UniquePtr<> is used throughout. The NotificationRef
89 : * constructor calls AddRefObject(). When it is destroyed (on any thread) it
90 : * releases the Notification on the correct thread.
91 : *
92 : * Code should only access the underlying Notification object when it can
93 : * guarantee that it retains ownership of the NotificationRef while doing so.
94 : *
95 : * The one kink in this mechanism is that the worker feature may be Notify()ed
96 : * if the worker stops running script, even if the Notification's corresponding
97 : * UI is still visible to the user. We handle this case with the following
98 : * steps:
99 : * a) Close the notification. This is done by blocking the worker on the main
100 : * thread. This ensures that there are no main thread holders when the worker
101 : * resumes. This also deals with the case where Notify() runs on the worker
102 : * before the observer has been created on the main thread. Even in such
103 : * a situation, the CloseNotificationRunnable() will only run after the
104 : * Show task that was previously queued. Since the show task is only queued
105 : * once when the Notification is created, we can be sure that no new tasks
106 : * will follow the Notify().
107 : *
108 : * b) Ask the observer to let go of its NotificationRef's underlying
109 : * Notification without proper cleanup since the feature will handle the
110 : * release. This is only OK because every notification has only one
111 : * associated observer. The NotificationRef itself is still owned by the
112 : * observer and deleted by the UniquePtr, but it doesn't do anything since
113 : * the underlying Notification is null.
114 : *
115 : * To unify code-paths, we use the same NotificationRef in the main
116 : * thread implementation too.
117 : *
118 : * Note that the Notification's JS wrapper does it's standard
119 : * AddRef()/Release() and is not affected by any of this.
120 : *
121 : * Since the worker event queue can have runnables that will dispatch events on
122 : * the Notification, the NotificationRef destructor will first try to release
123 : * the Notification by dispatching a normal runnable to the worker so that it is
124 : * queued after any event runnables. If that dispatch fails, it means the worker
125 : * is no longer running and queued WorkerRunnables will be canceled, so we
126 : * dispatch a control runnable instead.
127 : *
128 : */
129 : class Notification : public DOMEventTargetHelper
130 : , public nsIObserver
131 : , public nsSupportsWeakReference
132 : {
133 : friend class CloseNotificationRunnable;
134 : friend class NotificationTask;
135 : friend class NotificationPermissionRequest;
136 : friend class MainThreadNotificationObserver;
137 : friend class NotificationStorageCallback;
138 : friend class ServiceWorkerNotificationObserver;
139 : friend class WorkerGetRunnable;
140 : friend class WorkerNotificationObserver;
141 : friend class NotificationTelemetryService;
142 :
143 : public:
144 0 : IMPL_EVENT_HANDLER(click)
145 0 : IMPL_EVENT_HANDLER(show)
146 0 : IMPL_EVENT_HANDLER(error)
147 0 : IMPL_EVENT_HANDLER(close)
148 :
149 : NS_DECL_ISUPPORTS_INHERITED
150 0 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(Notification, DOMEventTargetHelper)
151 : NS_DECL_NSIOBSERVER
152 :
153 : static bool RequireInteractionEnabled(JSContext* aCx, JSObject* aObj);
154 : static bool PrefEnabled(JSContext* aCx, JSObject* aObj);
155 : // Returns if Notification.get() is allowed for the current global.
156 : static bool IsGetEnabled(JSContext* aCx, JSObject* aObj);
157 :
158 : static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
159 : const nsAString& aTitle,
160 : const NotificationOptions& aOption,
161 : ErrorResult& aRv);
162 :
163 : /**
164 : * Used when dispatching the ServiceWorkerEvent.
165 : *
166 : * Does not initialize the Notification's behavior.
167 : * This is because:
168 : * 1) The Notification is not shown to the user and so the behavior
169 : * parameters don't matter.
170 : * 2) The default binding requires main thread for parsing the JSON from the
171 : * string behavior.
172 : */
173 : static already_AddRefed<Notification>
174 : ConstructFromFields(
175 : nsIGlobalObject* aGlobal,
176 : const nsAString& aID,
177 : const nsAString& aTitle,
178 : const nsAString& aDir,
179 : const nsAString& aLang,
180 : const nsAString& aBody,
181 : const nsAString& aTag,
182 : const nsAString& aIcon,
183 : const nsAString& aData,
184 : const nsAString& aServiceWorkerRegistrationScope,
185 : ErrorResult& aRv);
186 :
187 0 : void GetID(nsAString& aRetval) {
188 0 : aRetval = mID;
189 0 : }
190 :
191 0 : void GetTitle(nsAString& aRetval)
192 : {
193 0 : aRetval = mTitle;
194 0 : }
195 :
196 0 : NotificationDirection Dir()
197 : {
198 0 : return mDir;
199 : }
200 :
201 0 : void GetLang(nsAString& aRetval)
202 : {
203 0 : aRetval = mLang;
204 0 : }
205 :
206 0 : void GetBody(nsAString& aRetval)
207 : {
208 0 : aRetval = mBody;
209 0 : }
210 :
211 0 : void GetTag(nsAString& aRetval)
212 : {
213 0 : aRetval = mTag;
214 0 : }
215 :
216 0 : void GetIcon(nsAString& aRetval)
217 : {
218 0 : aRetval = mIconUrl;
219 0 : }
220 :
221 0 : void SetStoredState(bool val)
222 : {
223 0 : mIsStored = val;
224 0 : }
225 :
226 0 : bool IsStored()
227 : {
228 0 : return mIsStored;
229 : }
230 :
231 : static bool RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */);
232 :
233 : static already_AddRefed<Promise>
234 : RequestPermission(const GlobalObject& aGlobal,
235 : const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
236 : ErrorResult& aRv);
237 :
238 : static NotificationPermission GetPermission(const GlobalObject& aGlobal,
239 : ErrorResult& aRv);
240 :
241 : static already_AddRefed<Promise>
242 : Get(nsPIDOMWindowInner* aWindow,
243 : const GetNotificationOptions& aFilter,
244 : const nsAString& aScope,
245 : ErrorResult& aRv);
246 :
247 : static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
248 : const GetNotificationOptions& aFilter,
249 : ErrorResult& aRv);
250 :
251 : static already_AddRefed<Promise> WorkerGet(workers::WorkerPrivate* aWorkerPrivate,
252 : const GetNotificationOptions& aFilter,
253 : const nsAString& aScope,
254 : ErrorResult& aRv);
255 :
256 : // Notification implementation of
257 : // ServiceWorkerRegistration.showNotification.
258 : //
259 : //
260 : // Note that aCx may not be in the compartment of aGlobal, but aOptions will
261 : // have its JS things in the compartment of aCx.
262 : static already_AddRefed<Promise>
263 : ShowPersistentNotification(JSContext* aCx,
264 : nsIGlobalObject* aGlobal,
265 : const nsAString& aScope,
266 : const nsAString& aTitle,
267 : const NotificationOptions& aOptions,
268 : ErrorResult& aRv);
269 :
270 : void Close();
271 :
272 0 : nsPIDOMWindowInner* GetParentObject()
273 : {
274 0 : return GetOwner();
275 : }
276 :
277 : virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
278 :
279 : bool RequireInteraction() const;
280 :
281 : void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval);
282 :
283 : void InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData, ErrorResult& aRv);
284 :
285 : void InitFromBase64(const nsAString& aData, ErrorResult& aRv);
286 :
287 0 : void AssertIsOnTargetThread() const
288 : {
289 0 : MOZ_ASSERT(IsTargetThread());
290 0 : }
291 :
292 : // Initialized on the worker thread, never unset, and always used in
293 : // a read-only capacity. Used on any thread.
294 : workers::WorkerPrivate* mWorkerPrivate;
295 :
296 : // Main thread only.
297 : WorkerNotificationObserver* mObserver;
298 :
299 : // The NotificationTask calls ShowInternal()/CloseInternal() on the
300 : // Notification. At this point the task has ownership of the Notification. It
301 : // passes this on to the Notification itself via mTempRef so that
302 : // ShowInternal()/CloseInternal() may pass it along appropriately (or release
303 : // it).
304 : //
305 : // Main thread only.
306 : UniquePtr<NotificationRef> mTempRef;
307 :
308 : // Returns true if addref succeeded.
309 : bool AddRefObject();
310 : void ReleaseObject();
311 :
312 : static NotificationPermission GetPermission(nsIGlobalObject* aGlobal,
313 : ErrorResult& aRv);
314 :
315 : static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
316 : ErrorResult& rv);
317 :
318 : static NotificationPermission TestPermission(nsIPrincipal* aPrincipal);
319 :
320 : bool DispatchClickEvent();
321 : bool DispatchNotificationClickEvent();
322 :
323 : static nsresult RemovePermission(nsIPrincipal* aPrincipal);
324 : static nsresult OpenSettings(nsIPrincipal* aPrincipal);
325 :
326 : nsresult DispatchToMainThread(already_AddRefed<nsIRunnable>&& aRunnable);
327 : protected:
328 : Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
329 : const nsAString& aTitle, const nsAString& aBody,
330 : NotificationDirection aDir, const nsAString& aLang,
331 : const nsAString& aTag, const nsAString& aIconUrl,
332 : bool aRequireNotification,
333 : const NotificationBehavior& aBehavior);
334 :
335 : static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal,
336 : const nsAString& aID,
337 : const nsAString& aTitle,
338 : const NotificationOptions& aOptions);
339 :
340 : nsresult Init();
341 : bool IsInPrivateBrowsing();
342 : void ShowInternal();
343 : void CloseInternal();
344 :
345 : static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
346 : ErrorResult& rv);
347 :
348 0 : static const nsString DirectionToString(NotificationDirection aDirection)
349 : {
350 0 : switch (aDirection) {
351 : case NotificationDirection::Ltr:
352 0 : return NS_LITERAL_STRING("ltr");
353 : case NotificationDirection::Rtl:
354 0 : return NS_LITERAL_STRING("rtl");
355 : default:
356 0 : return NS_LITERAL_STRING("auto");
357 : }
358 : }
359 :
360 0 : static NotificationDirection StringToDirection(const nsAString& aDirection)
361 : {
362 0 : if (aDirection.EqualsLiteral("ltr")) {
363 0 : return NotificationDirection::Ltr;
364 : }
365 0 : if (aDirection.EqualsLiteral("rtl")) {
366 0 : return NotificationDirection::Rtl;
367 : }
368 0 : return NotificationDirection::Auto;
369 : }
370 :
371 : static nsresult GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin);
372 :
373 0 : void GetAlertName(nsAString& aRetval)
374 : {
375 0 : workers::AssertIsOnMainThread();
376 0 : if (mAlertName.IsEmpty()) {
377 0 : SetAlertName();
378 : }
379 0 : aRetval = mAlertName;
380 0 : }
381 :
382 : void GetScope(nsAString& aScope)
383 : {
384 : aScope = mScope;
385 : }
386 :
387 : void
388 0 : SetScope(const nsAString& aScope)
389 : {
390 0 : MOZ_ASSERT(mScope.IsEmpty());
391 0 : mScope = aScope;
392 0 : }
393 :
394 : const nsString mID;
395 : const nsString mTitle;
396 : const nsString mBody;
397 : const NotificationDirection mDir;
398 : const nsString mLang;
399 : const nsString mTag;
400 : const nsString mIconUrl;
401 : const bool mRequireInteraction;
402 : nsString mDataAsBase64;
403 : const NotificationBehavior mBehavior;
404 :
405 : // It's null until GetData is first called
406 : JS::Heap<JS::Value> mData;
407 :
408 : nsString mAlertName;
409 : nsString mScope;
410 :
411 : // Main thread only.
412 : bool mIsClosed;
413 :
414 : // We need to make a distinction between the notification being closed i.e.
415 : // removed from any pending or active lists, and the notification being
416 : // removed from the database. NotificationDB might fail when trying to remove
417 : // the notification.
418 : bool mIsStored;
419 :
420 : static uint32_t sCount;
421 :
422 : private:
423 : virtual ~Notification();
424 :
425 : // Creates a Notification and shows it. Returns a reference to the
426 : // Notification if result is NS_OK. The lifetime of this Notification is tied
427 : // to an underlying NotificationRef. Do not hold a non-stack raw pointer to
428 : // it. Be careful about thread safety if acquiring a strong reference.
429 : //
430 : // Note that aCx may not be in the compartment of aGlobal, but aOptions will
431 : // have its JS things in the compartment of aCx.
432 : static already_AddRefed<Notification>
433 : CreateAndShow(JSContext* aCx,
434 : nsIGlobalObject* aGlobal,
435 : const nsAString& aTitle,
436 : const NotificationOptions& aOptions,
437 : const nsAString& aScope,
438 : ErrorResult& aRv);
439 :
440 : nsIPrincipal* GetPrincipal();
441 :
442 : nsresult PersistNotification();
443 : void UnpersistNotification();
444 :
445 : void
446 : SetAlertName();
447 :
448 0 : bool IsTargetThread() const
449 : {
450 0 : return NS_IsMainThread() == !mWorkerPrivate;
451 : }
452 :
453 : bool RegisterWorkerHolder();
454 : void UnregisterWorkerHolder();
455 :
456 : nsresult ResolveIconAndSoundURL(nsString&, nsString&);
457 :
458 : // Only used for Notifications on Workers, worker thread only.
459 : UniquePtr<NotificationWorkerHolder> mWorkerHolder;
460 : // Target thread only.
461 : uint32_t mTaskCount;
462 : };
463 :
464 : } // namespace dom
465 : } // namespace mozilla
466 :
467 : #endif // mozilla_dom_notification_h__
468 :
|