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