/src/mozilla-central/dom/notification/Notification.cpp
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 | | #include "mozilla/dom/Notification.h" |
8 | | |
9 | | #include "mozilla/Encoding.h" |
10 | | #include "mozilla/EventStateManager.h" |
11 | | #include "mozilla/JSONWriter.h" |
12 | | #include "mozilla/Move.h" |
13 | | #include "mozilla/OwningNonNull.h" |
14 | | #include "mozilla/Preferences.h" |
15 | | #include "mozilla/Services.h" |
16 | | #include "mozilla/StaticPrefs.h" |
17 | | #include "mozilla/Telemetry.h" |
18 | | #include "mozilla/Unused.h" |
19 | | |
20 | | #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" |
21 | | #include "mozilla/dom/BindingUtils.h" |
22 | | #include "mozilla/dom/ContentChild.h" |
23 | | #include "mozilla/dom/NotificationEvent.h" |
24 | | #include "mozilla/dom/PermissionMessageUtils.h" |
25 | | #include "mozilla/dom/Promise.h" |
26 | | #include "mozilla/dom/PromiseWorkerProxy.h" |
27 | | #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" |
28 | | #include "mozilla/dom/ServiceWorkerManager.h" |
29 | | #include "mozilla/dom/WorkerPrivate.h" |
30 | | #include "mozilla/dom/WorkerRunnable.h" |
31 | | #include "mozilla/dom/WorkerScope.h" |
32 | | |
33 | | #include "nsAlertsUtils.h" |
34 | | #include "nsComponentManagerUtils.h" |
35 | | #include "nsContentPermissionHelper.h" |
36 | | #include "nsContentUtils.h" |
37 | | #include "nsCRTGlue.h" |
38 | | #include "nsDOMJSUtils.h" |
39 | | #include "nsGlobalWindow.h" |
40 | | #include "nsIAlertsService.h" |
41 | | #include "nsIContentPermissionPrompt.h" |
42 | | #include "nsIDocument.h" |
43 | | #include "nsILoadContext.h" |
44 | | #include "nsINotificationStorage.h" |
45 | | #include "nsIPermissionManager.h" |
46 | | #include "nsIPermission.h" |
47 | | #include "nsIPushService.h" |
48 | | #include "nsIScriptSecurityManager.h" |
49 | | #include "nsIServiceWorkerManager.h" |
50 | | #include "nsISimpleEnumerator.h" |
51 | | #include "nsIUUIDGenerator.h" |
52 | | #include "nsIXPConnect.h" |
53 | | #include "nsNetUtil.h" |
54 | | #include "nsProxyRelease.h" |
55 | | #include "nsServiceManagerUtils.h" |
56 | | #include "nsStructuredCloneContainer.h" |
57 | | #include "nsThreadUtils.h" |
58 | | #include "nsToolkitCompsCID.h" |
59 | | #include "nsXULAppAPI.h" |
60 | | |
61 | | namespace mozilla { |
62 | | namespace dom { |
63 | | |
64 | | struct NotificationStrings |
65 | | { |
66 | | const nsString mID; |
67 | | const nsString mTitle; |
68 | | const nsString mDir; |
69 | | const nsString mLang; |
70 | | const nsString mBody; |
71 | | const nsString mTag; |
72 | | const nsString mIcon; |
73 | | const nsString mData; |
74 | | const nsString mBehavior; |
75 | | const nsString mServiceWorkerRegistrationScope; |
76 | | }; |
77 | | |
78 | | class ScopeCheckingGetCallback : public nsINotificationStorageCallback |
79 | | { |
80 | | const nsString mScope; |
81 | | public: |
82 | | explicit ScopeCheckingGetCallback(const nsAString& aScope) |
83 | | : mScope(aScope) |
84 | 0 | {} |
85 | | |
86 | | NS_IMETHOD Handle(const nsAString& aID, |
87 | | const nsAString& aTitle, |
88 | | const nsAString& aDir, |
89 | | const nsAString& aLang, |
90 | | const nsAString& aBody, |
91 | | const nsAString& aTag, |
92 | | const nsAString& aIcon, |
93 | | const nsAString& aData, |
94 | | const nsAString& aBehavior, |
95 | | const nsAString& aServiceWorkerRegistrationScope) final |
96 | 0 | { |
97 | 0 | AssertIsOnMainThread(); |
98 | 0 | MOZ_ASSERT(!aID.IsEmpty()); |
99 | 0 |
|
100 | 0 | // Skip scopes that don't match when called from getNotifications(). |
101 | 0 | if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) { |
102 | 0 | return NS_OK; |
103 | 0 | } |
104 | 0 | |
105 | 0 | NotificationStrings strings = { |
106 | 0 | nsString(aID), |
107 | 0 | nsString(aTitle), |
108 | 0 | nsString(aDir), |
109 | 0 | nsString(aLang), |
110 | 0 | nsString(aBody), |
111 | 0 | nsString(aTag), |
112 | 0 | nsString(aIcon), |
113 | 0 | nsString(aData), |
114 | 0 | nsString(aBehavior), |
115 | 0 | nsString(aServiceWorkerRegistrationScope), |
116 | 0 | }; |
117 | 0 |
|
118 | 0 | mStrings.AppendElement(std::move(strings)); |
119 | 0 | return NS_OK; |
120 | 0 | } |
121 | | |
122 | | NS_IMETHOD Done() override = 0; |
123 | | |
124 | | protected: |
125 | | virtual ~ScopeCheckingGetCallback() |
126 | 0 | {} |
127 | | |
128 | | nsTArray<NotificationStrings> mStrings; |
129 | | }; |
130 | | |
131 | | class NotificationStorageCallback final : public ScopeCheckingGetCallback |
132 | | { |
133 | | public: |
134 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
135 | | NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback) |
136 | | |
137 | | NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope, |
138 | | Promise* aPromise) |
139 | | : ScopeCheckingGetCallback(aScope), |
140 | | mWindow(aWindow), |
141 | | mPromise(aPromise) |
142 | 0 | { |
143 | 0 | AssertIsOnMainThread(); |
144 | 0 | MOZ_ASSERT(aWindow); |
145 | 0 | MOZ_ASSERT(aPromise); |
146 | 0 | } |
147 | | |
148 | | NS_IMETHOD Done() final |
149 | 0 | { |
150 | 0 | ErrorResult result; |
151 | 0 | AutoTArray<RefPtr<Notification>, 5> notifications; |
152 | 0 |
|
153 | 0 | for (uint32_t i = 0; i < mStrings.Length(); ++i) { |
154 | 0 | RefPtr<Notification> n = |
155 | 0 | Notification::ConstructFromFields(mWindow, |
156 | 0 | mStrings[i].mID, |
157 | 0 | mStrings[i].mTitle, |
158 | 0 | mStrings[i].mDir, |
159 | 0 | mStrings[i].mLang, |
160 | 0 | mStrings[i].mBody, |
161 | 0 | mStrings[i].mTag, |
162 | 0 | mStrings[i].mIcon, |
163 | 0 | mStrings[i].mData, |
164 | 0 | /* mStrings[i].mBehavior, not |
165 | 0 | * supported */ |
166 | 0 | mStrings[i].mServiceWorkerRegistrationScope, |
167 | 0 | result); |
168 | 0 |
|
169 | 0 | n->SetStoredState(true); |
170 | 0 | Unused << NS_WARN_IF(result.Failed()); |
171 | 0 | if (!result.Failed()) { |
172 | 0 | notifications.AppendElement(n.forget()); |
173 | 0 | } |
174 | 0 | } |
175 | 0 |
|
176 | 0 | mPromise->MaybeResolve(notifications); |
177 | 0 | return NS_OK; |
178 | 0 | } |
179 | | |
180 | | private: |
181 | | virtual ~NotificationStorageCallback() |
182 | 0 | {} |
183 | | |
184 | | nsCOMPtr<nsIGlobalObject> mWindow; |
185 | | RefPtr<Promise> mPromise; |
186 | | const nsString mScope; |
187 | | }; |
188 | | |
189 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback) |
190 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback) |
191 | | NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise); |
192 | | |
193 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback) |
194 | 0 | NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback) |
195 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
196 | 0 | NS_INTERFACE_MAP_END |
197 | | |
198 | | class NotificationGetRunnable final : public Runnable |
199 | | { |
200 | | const nsString mOrigin; |
201 | | const nsString mTag; |
202 | | nsCOMPtr<nsINotificationStorageCallback> mCallback; |
203 | | public: |
204 | | NotificationGetRunnable(const nsAString& aOrigin, |
205 | | const nsAString& aTag, |
206 | | nsINotificationStorageCallback* aCallback) |
207 | | : Runnable("NotificationGetRunnable") |
208 | | , mOrigin(aOrigin), mTag(aTag), mCallback(aCallback) |
209 | 0 | {} |
210 | | |
211 | | NS_IMETHOD |
212 | | Run() override |
213 | 0 | { |
214 | 0 | nsresult rv; |
215 | 0 | nsCOMPtr<nsINotificationStorage> notificationStorage = |
216 | 0 | do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); |
217 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
218 | 0 | return rv; |
219 | 0 | } |
220 | 0 | |
221 | 0 | rv = notificationStorage->Get(mOrigin, mTag, mCallback); |
222 | 0 | //XXXnsm Is it guaranteed mCallback will be called in case of failure? |
223 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
224 | 0 | return rv; |
225 | 0 | } |
226 | | }; |
227 | | |
228 | | class NotificationPermissionRequest : public nsIContentPermissionRequest, |
229 | | public nsIRunnable, |
230 | | public nsINamed |
231 | | { |
232 | | public: |
233 | | NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
234 | | NS_DECL_NSICONTENTPERMISSIONREQUEST |
235 | | NS_DECL_NSIRUNNABLE |
236 | | NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest, |
237 | | nsIContentPermissionRequest) |
238 | | |
239 | | NotificationPermissionRequest(nsIPrincipal* aPrincipal, bool aIsHandlingUserInput, |
240 | | nsPIDOMWindowInner* aWindow, Promise* aPromise, |
241 | | NotificationPermissionCallback* aCallback) |
242 | | : mPrincipal(aPrincipal), mWindow(aWindow), |
243 | | mPermission(NotificationPermission::Default), |
244 | | mPromise(aPromise), |
245 | | mCallback(aCallback), |
246 | | mIsHandlingUserInput(aIsHandlingUserInput) |
247 | 0 | { |
248 | 0 | MOZ_ASSERT(aPromise); |
249 | 0 | mRequester = new nsContentPermissionRequester(mWindow); |
250 | 0 | } |
251 | | |
252 | | NS_IMETHOD GetName(nsACString& aName) override |
253 | 0 | { |
254 | 0 | aName.AssignLiteral("NotificationPermissionRequest"); |
255 | 0 | return NS_OK; |
256 | 0 | } |
257 | | |
258 | | protected: |
259 | 0 | virtual ~NotificationPermissionRequest() {} |
260 | | |
261 | | nsresult ResolvePromise(); |
262 | | nsresult DispatchResolvePromise(); |
263 | | nsCOMPtr<nsIPrincipal> mPrincipal; |
264 | | nsCOMPtr<nsPIDOMWindowInner> mWindow; |
265 | | NotificationPermission mPermission; |
266 | | RefPtr<Promise> mPromise; |
267 | | RefPtr<NotificationPermissionCallback> mCallback; |
268 | | nsCOMPtr<nsIContentPermissionRequester> mRequester; |
269 | | bool mIsHandlingUserInput; |
270 | | }; |
271 | | |
272 | | namespace { |
273 | | class ReleaseNotificationControlRunnable final : public MainThreadWorkerControlRunnable |
274 | | { |
275 | | Notification* mNotification; |
276 | | |
277 | | public: |
278 | | explicit ReleaseNotificationControlRunnable(Notification* aNotification) |
279 | | : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate) |
280 | | , mNotification(aNotification) |
281 | 0 | { } |
282 | | |
283 | | bool |
284 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
285 | 0 | { |
286 | 0 | mNotification->ReleaseObject(); |
287 | 0 | return true; |
288 | 0 | } |
289 | | }; |
290 | | |
291 | | class GetPermissionRunnable final : public WorkerMainThreadRunnable |
292 | | { |
293 | | NotificationPermission mPermission; |
294 | | |
295 | | public: |
296 | | explicit GetPermissionRunnable(WorkerPrivate* aWorker) |
297 | | : WorkerMainThreadRunnable(aWorker, |
298 | | NS_LITERAL_CSTRING("Notification :: Get Permission")) |
299 | | , mPermission(NotificationPermission::Denied) |
300 | 0 | { } |
301 | | |
302 | | bool |
303 | | MainThreadRun() override |
304 | 0 | { |
305 | 0 | ErrorResult result; |
306 | 0 | mPermission = |
307 | 0 | Notification::GetPermissionInternal(mWorkerPrivate->GetPrincipal(), |
308 | 0 | result); |
309 | 0 | return true; |
310 | 0 | } |
311 | | |
312 | | NotificationPermission |
313 | | GetPermission() |
314 | 0 | { |
315 | 0 | return mPermission; |
316 | 0 | } |
317 | | }; |
318 | | |
319 | | class FocusWindowRunnable final : public Runnable |
320 | | { |
321 | | nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow; |
322 | | public: |
323 | | explicit FocusWindowRunnable(const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow) |
324 | | : Runnable("FocusWindowRunnable") |
325 | | , mWindow(aWindow) |
326 | 0 | { } |
327 | | |
328 | | NS_IMETHOD |
329 | | Run() override |
330 | 0 | { |
331 | 0 | AssertIsOnMainThread(); |
332 | 0 | if (!mWindow->IsCurrentInnerWindow()) { |
333 | 0 | // Window has been closed, this observer is not valid anymore |
334 | 0 | return NS_OK; |
335 | 0 | } |
336 | 0 | |
337 | 0 | // Browser UI may use DOMWindowFocus to focus the tab |
338 | 0 | // from which the event was dispatched. |
339 | 0 | nsContentUtils::DispatchFocusChromeEvent(mWindow->GetOuterWindow()); |
340 | 0 |
|
341 | 0 | return NS_OK; |
342 | 0 | } |
343 | | }; |
344 | | |
345 | | nsresult |
346 | | CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope) |
347 | 0 | { |
348 | 0 | AssertIsOnMainThread(); |
349 | 0 | MOZ_ASSERT(aPrincipal); |
350 | 0 |
|
351 | 0 | nsCOMPtr<nsIURI> scopeURI; |
352 | 0 | nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr); |
353 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
354 | 0 | return rv; |
355 | 0 | } |
356 | 0 | |
357 | 0 | return aPrincipal->CheckMayLoad(scopeURI, /* report = */ true, |
358 | 0 | /* allowIfInheritsPrincipal = */ false); |
359 | 0 | } |
360 | | } // anonymous namespace |
361 | | |
362 | | // Subclass that can be directly dispatched to child workers from the main |
363 | | // thread. |
364 | | class NotificationWorkerRunnable : public MainThreadWorkerRunnable |
365 | | { |
366 | | protected: |
367 | | explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate) |
368 | | : MainThreadWorkerRunnable(aWorkerPrivate) |
369 | 0 | { |
370 | 0 | } |
371 | | |
372 | | bool |
373 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
374 | 0 | { |
375 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
376 | 0 | aWorkerPrivate->ModifyBusyCountFromWorker(true); |
377 | 0 | WorkerRunInternal(aWorkerPrivate); |
378 | 0 | return true; |
379 | 0 | } |
380 | | |
381 | | void |
382 | | PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, |
383 | | bool aRunResult) override |
384 | 0 | { |
385 | 0 | aWorkerPrivate->ModifyBusyCountFromWorker(false); |
386 | 0 | } |
387 | | |
388 | | virtual void |
389 | | WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 0; |
390 | | }; |
391 | | |
392 | | // Overrides dispatch and run handlers so we can directly dispatch from main |
393 | | // thread to child workers. |
394 | | class NotificationEventWorkerRunnable final : public NotificationWorkerRunnable |
395 | | { |
396 | | Notification* mNotification; |
397 | | const nsString mEventName; |
398 | | public: |
399 | | NotificationEventWorkerRunnable(Notification* aNotification, |
400 | | const nsString& aEventName) |
401 | | : NotificationWorkerRunnable(aNotification->mWorkerPrivate) |
402 | | , mNotification(aNotification) |
403 | | , mEventName(aEventName) |
404 | 0 | {} |
405 | | |
406 | | void |
407 | | WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override |
408 | 0 | { |
409 | 0 | mNotification->DispatchTrustedEvent(mEventName); |
410 | 0 | } |
411 | | }; |
412 | | |
413 | | class ReleaseNotificationRunnable final : public NotificationWorkerRunnable |
414 | | { |
415 | | Notification* mNotification; |
416 | | public: |
417 | | explicit ReleaseNotificationRunnable(Notification* aNotification) |
418 | | : NotificationWorkerRunnable(aNotification->mWorkerPrivate) |
419 | | , mNotification(aNotification) |
420 | 0 | {} |
421 | | |
422 | | void |
423 | | WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override |
424 | 0 | { |
425 | 0 | mNotification->ReleaseObject(); |
426 | 0 | } |
427 | | |
428 | | nsresult |
429 | | Cancel() override |
430 | 0 | { |
431 | 0 | mNotification->ReleaseObject(); |
432 | 0 | return NS_OK; |
433 | 0 | } |
434 | | }; |
435 | | |
436 | | // Create one whenever you require ownership of the notification. Use with |
437 | | // UniquePtr<>. See Notification.h for details. |
438 | | class NotificationRef final { |
439 | | friend class WorkerNotificationObserver; |
440 | | |
441 | | private: |
442 | | Notification* mNotification; |
443 | | bool mInited; |
444 | | |
445 | | // Only useful for workers. |
446 | | void |
447 | | Forget() |
448 | 0 | { |
449 | 0 | mNotification = nullptr; |
450 | 0 | } |
451 | | |
452 | | public: |
453 | | explicit NotificationRef(Notification* aNotification) |
454 | | : mNotification(aNotification) |
455 | 0 | { |
456 | 0 | MOZ_ASSERT(mNotification); |
457 | 0 | if (mNotification->mWorkerPrivate) { |
458 | 0 | mNotification->mWorkerPrivate->AssertIsOnWorkerThread(); |
459 | 0 | } else { |
460 | 0 | AssertIsOnMainThread(); |
461 | 0 | } |
462 | 0 |
|
463 | 0 | mInited = mNotification->AddRefObject(); |
464 | 0 | } |
465 | | |
466 | | // This is only required because Gecko runs script in a worker's onclose |
467 | | // handler (non-standard, Bug 790919) where calls to HoldWorker() will |
468 | | // fail. Due to non-standardness and added complications if we decide to |
469 | | // support this, attempts to create a Notification in onclose just throw |
470 | | // exceptions. |
471 | | bool |
472 | | Initialized() |
473 | 0 | { |
474 | 0 | return mInited; |
475 | 0 | } |
476 | | |
477 | | ~NotificationRef() |
478 | 0 | { |
479 | 0 | if (Initialized() && mNotification) { |
480 | 0 | Notification* notification = mNotification; |
481 | 0 | mNotification = nullptr; |
482 | 0 | if (notification->mWorkerPrivate && NS_IsMainThread()) { |
483 | 0 | // Try to pass ownership back to the worker. If the dispatch succeeds we |
484 | 0 | // are guaranteed this runnable will run, and that it will run after queued |
485 | 0 | // event runnables, so event runnables will have a safe pointer to the |
486 | 0 | // Notification. |
487 | 0 | // |
488 | 0 | // If the dispatch fails, the worker isn't running anymore and the event |
489 | 0 | // runnables have already run or been canceled. We can use a control |
490 | 0 | // runnable to release the reference. |
491 | 0 | RefPtr<ReleaseNotificationRunnable> r = |
492 | 0 | new ReleaseNotificationRunnable(notification); |
493 | 0 |
|
494 | 0 | if (!r->Dispatch()) { |
495 | 0 | RefPtr<ReleaseNotificationControlRunnable> r = |
496 | 0 | new ReleaseNotificationControlRunnable(notification); |
497 | 0 | MOZ_ALWAYS_TRUE(r->Dispatch()); |
498 | 0 | } |
499 | 0 | } else { |
500 | 0 | notification->AssertIsOnTargetThread(); |
501 | 0 | notification->ReleaseObject(); |
502 | 0 | } |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of |
507 | | // a rawptr that the NotificationRef can invalidate? |
508 | | Notification* |
509 | | GetNotification() |
510 | 0 | { |
511 | 0 | MOZ_ASSERT(Initialized()); |
512 | 0 | return mNotification; |
513 | 0 | } |
514 | | }; |
515 | | |
516 | | class NotificationTask : public Runnable |
517 | | { |
518 | | public: |
519 | | enum NotificationAction { |
520 | | eShow, |
521 | | eClose |
522 | | }; |
523 | | |
524 | | NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef, |
525 | | NotificationAction aAction) |
526 | | : Runnable(aName) |
527 | | , mNotificationRef(std::move(aRef)), mAction(aAction) |
528 | 0 | {} |
529 | | |
530 | | NS_IMETHOD |
531 | | Run() override; |
532 | | protected: |
533 | 0 | virtual ~NotificationTask() {} |
534 | | |
535 | | UniquePtr<NotificationRef> mNotificationRef; |
536 | | NotificationAction mAction; |
537 | | }; |
538 | | |
539 | | uint32_t Notification::sCount = 0; |
540 | | |
541 | | NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow, mPromise, |
542 | | mCallback) |
543 | | |
544 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest) |
545 | 0 | NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) |
546 | 0 | NS_INTERFACE_MAP_ENTRY(nsIRunnable) |
547 | 0 | NS_INTERFACE_MAP_ENTRY(nsINamed) |
548 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) |
549 | 0 | NS_INTERFACE_MAP_END |
550 | | |
551 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationPermissionRequest) |
552 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationPermissionRequest) |
553 | | |
554 | | NS_IMETHODIMP |
555 | | NotificationPermissionRequest::Run() |
556 | 0 | { |
557 | 0 | if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { |
558 | 0 | mPermission = NotificationPermission::Granted; |
559 | 0 | } else { |
560 | 0 | // File are automatically granted permission. |
561 | 0 | nsCOMPtr<nsIURI> uri; |
562 | 0 | mPrincipal->GetURI(getter_AddRefs(uri)); |
563 | 0 |
|
564 | 0 | if (uri) { |
565 | 0 | bool isFile; |
566 | 0 | uri->SchemeIs("file", &isFile); |
567 | 0 | if (isFile) { |
568 | 0 | mPermission = NotificationPermission::Granted; |
569 | 0 | } |
570 | 0 | } |
571 | 0 | } |
572 | 0 |
|
573 | 0 | // Grant permission if pref'ed on. |
574 | 0 | if (Preferences::GetBool("notification.prompt.testing", false)) { |
575 | 0 | if (Preferences::GetBool("notification.prompt.testing.allow", true)) { |
576 | 0 | mPermission = NotificationPermission::Granted; |
577 | 0 | } else { |
578 | 0 | mPermission = NotificationPermission::Denied; |
579 | 0 | } |
580 | 0 | } |
581 | 0 |
|
582 | 0 | if (mPermission != NotificationPermission::Default) { |
583 | 0 | return DispatchResolvePromise(); |
584 | 0 | } |
585 | 0 | |
586 | 0 | return nsContentPermissionUtils::AskPermission(this, mWindow); |
587 | 0 | } |
588 | | |
589 | | NS_IMETHODIMP |
590 | | NotificationPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal) |
591 | 0 | { |
592 | 0 | NS_ADDREF(*aRequestingPrincipal = mPrincipal); |
593 | 0 | return NS_OK; |
594 | 0 | } |
595 | | |
596 | | NS_IMETHODIMP |
597 | | NotificationPermissionRequest::GetWindow(mozIDOMWindow** aRequestingWindow) |
598 | 0 | { |
599 | 0 | NS_ADDREF(*aRequestingWindow = mWindow); |
600 | 0 | return NS_OK; |
601 | 0 | } |
602 | | |
603 | | NS_IMETHODIMP |
604 | | NotificationPermissionRequest::GetElement(Element** aElement) |
605 | 0 | { |
606 | 0 | NS_ENSURE_ARG_POINTER(aElement); |
607 | 0 | *aElement = nullptr; |
608 | 0 | return NS_OK; |
609 | 0 | } |
610 | | |
611 | | NS_IMETHODIMP |
612 | | NotificationPermissionRequest::GetIsHandlingUserInput(bool* aIsHandlingUserInput) |
613 | 0 | { |
614 | 0 | *aIsHandlingUserInput = mIsHandlingUserInput; |
615 | 0 | return NS_OK; |
616 | 0 | } |
617 | | |
618 | | NS_IMETHODIMP |
619 | | NotificationPermissionRequest::Cancel() |
620 | 0 | { |
621 | 0 | // `Cancel` is called if the user denied permission or dismissed the |
622 | 0 | // permission request. To distinguish between the two, we set the |
623 | 0 | // permission to "default" and query the permission manager in |
624 | 0 | // `ResolvePromise`. |
625 | 0 | mPermission = NotificationPermission::Default; |
626 | 0 | return DispatchResolvePromise(); |
627 | 0 | } |
628 | | |
629 | | NS_IMETHODIMP |
630 | | NotificationPermissionRequest::Allow(JS::HandleValue aChoices) |
631 | 0 | { |
632 | 0 | MOZ_ASSERT(aChoices.isUndefined()); |
633 | 0 |
|
634 | 0 | mPermission = NotificationPermission::Granted; |
635 | 0 | return DispatchResolvePromise(); |
636 | 0 | } |
637 | | |
638 | | NS_IMETHODIMP |
639 | | NotificationPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester) |
640 | 0 | { |
641 | 0 | NS_ENSURE_ARG_POINTER(aRequester); |
642 | 0 |
|
643 | 0 | nsCOMPtr<nsIContentPermissionRequester> requester = mRequester; |
644 | 0 | requester.forget(aRequester); |
645 | 0 | return NS_OK; |
646 | 0 | } |
647 | | |
648 | | inline nsresult |
649 | | NotificationPermissionRequest::DispatchResolvePromise() |
650 | 0 | { |
651 | 0 | nsCOMPtr<nsIRunnable> resolver = |
652 | 0 | NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise", |
653 | 0 | this, &NotificationPermissionRequest::ResolvePromise); |
654 | 0 | if (nsIEventTarget* target = mWindow->EventTargetFor(TaskCategory::Other)) { |
655 | 0 | return target->Dispatch(resolver.forget(), nsIEventTarget::DISPATCH_NORMAL); |
656 | 0 | } |
657 | 0 | return NS_ERROR_FAILURE; |
658 | 0 | } |
659 | | |
660 | | nsresult |
661 | | NotificationPermissionRequest::ResolvePromise() |
662 | 0 | { |
663 | 0 | nsresult rv = NS_OK; |
664 | 0 | if (mPermission == NotificationPermission::Default) { |
665 | 0 | // This will still be "default" if the user dismissed the doorhanger, |
666 | 0 | // or "denied" otherwise. |
667 | 0 | mPermission = Notification::TestPermission(mPrincipal); |
668 | 0 | } |
669 | 0 | if (mCallback) { |
670 | 0 | ErrorResult error; |
671 | 0 | mCallback->Call(mPermission, error); |
672 | 0 | rv = error.StealNSResult(); |
673 | 0 | } |
674 | 0 | mPromise->MaybeResolve(mPermission); |
675 | 0 | return rv; |
676 | 0 | } |
677 | | |
678 | | NS_IMETHODIMP |
679 | | NotificationPermissionRequest::GetTypes(nsIArray** aTypes) |
680 | 0 | { |
681 | 0 | nsTArray<nsString> emptyOptions; |
682 | 0 | return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), |
683 | 0 | NS_LITERAL_CSTRING("unused"), |
684 | 0 | emptyOptions, |
685 | 0 | aTypes); |
686 | 0 | } |
687 | | |
688 | | NS_IMPL_ISUPPORTS(NotificationTelemetryService, nsIObserver) |
689 | | |
690 | | NotificationTelemetryService::NotificationTelemetryService() |
691 | | : mDNDRecorded(false) |
692 | 0 | {} |
693 | | |
694 | | NotificationTelemetryService::~NotificationTelemetryService() |
695 | 0 | {} |
696 | | |
697 | | /* static */ already_AddRefed<NotificationTelemetryService> |
698 | | NotificationTelemetryService::GetInstance() |
699 | 0 | { |
700 | 0 | nsCOMPtr<nsISupports> telemetrySupports = |
701 | 0 | do_GetService(NOTIFICATIONTELEMETRYSERVICE_CONTRACTID); |
702 | 0 | if (!telemetrySupports) { |
703 | 0 | return nullptr; |
704 | 0 | } |
705 | 0 | RefPtr<NotificationTelemetryService> telemetry = |
706 | 0 | static_cast<NotificationTelemetryService*>(telemetrySupports.get()); |
707 | 0 | return telemetry.forget(); |
708 | 0 | } |
709 | | |
710 | | nsresult |
711 | | NotificationTelemetryService::Init() |
712 | 0 | { |
713 | 0 | // Only perform permissions telemetry collection in the parent process. |
714 | 0 | if (!XRE_IsParentProcess()) { |
715 | 0 | return NS_OK; |
716 | 0 | } |
717 | 0 | |
718 | 0 | RecordPermissions(); |
719 | 0 |
|
720 | 0 | return NS_OK; |
721 | 0 | } |
722 | | |
723 | | void |
724 | | NotificationTelemetryService::RecordPermissions() |
725 | 0 | { |
726 | 0 | MOZ_ASSERT(XRE_IsParentProcess(), |
727 | 0 | "RecordPermissions may only be called in the parent process"); |
728 | 0 |
|
729 | 0 | if (!Telemetry::CanRecordBase() || !Telemetry::CanRecordExtended()) { |
730 | 0 | return; |
731 | 0 | } |
732 | 0 | |
733 | 0 | nsCOMPtr<nsIPermissionManager> permissionManager = |
734 | 0 | services::GetPermissionManager(); |
735 | 0 | if (!permissionManager) { |
736 | 0 | return; |
737 | 0 | } |
738 | 0 | |
739 | 0 | nsCOMPtr<nsISimpleEnumerator> enumerator; |
740 | 0 | nsresult rv = permissionManager->GetEnumerator(getter_AddRefs(enumerator)); |
741 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
742 | 0 | return; |
743 | 0 | } |
744 | 0 | |
745 | 0 | for (;;) { |
746 | 0 | bool hasMoreElements; |
747 | 0 | nsresult rv = enumerator->HasMoreElements(&hasMoreElements); |
748 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
749 | 0 | return; |
750 | 0 | } |
751 | 0 | if (!hasMoreElements) { |
752 | 0 | break; |
753 | 0 | } |
754 | 0 | nsCOMPtr<nsISupports> supportsPermission; |
755 | 0 | rv = enumerator->GetNext(getter_AddRefs(supportsPermission)); |
756 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
757 | 0 | return; |
758 | 0 | } |
759 | 0 | uint32_t capability; |
760 | 0 | if (!GetNotificationPermission(supportsPermission, &capability)) { |
761 | 0 | continue; |
762 | 0 | } |
763 | 0 | if (capability == nsIPermissionManager::DENY_ACTION) { |
764 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 0); |
765 | 0 | } else if (capability == nsIPermissionManager::ALLOW_ACTION) { |
766 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_PERMISSIONS, 1); |
767 | 0 | } |
768 | 0 | } |
769 | 0 | } |
770 | | |
771 | | bool |
772 | | NotificationTelemetryService::GetNotificationPermission(nsISupports* aSupports, |
773 | | uint32_t* aCapability) |
774 | 0 | { |
775 | 0 | nsCOMPtr<nsIPermission> permission = do_QueryInterface(aSupports); |
776 | 0 | if (!permission) { |
777 | 0 | return false; |
778 | 0 | } |
779 | 0 | nsAutoCString type; |
780 | 0 | permission->GetType(type); |
781 | 0 | if (!type.EqualsLiteral("desktop-notification")) { |
782 | 0 | return false; |
783 | 0 | } |
784 | 0 | permission->GetCapability(aCapability); |
785 | 0 | return true; |
786 | 0 | } |
787 | | |
788 | | void |
789 | | NotificationTelemetryService::RecordDNDSupported() |
790 | 0 | { |
791 | 0 | if (mDNDRecorded) { |
792 | 0 | return; |
793 | 0 | } |
794 | 0 | |
795 | 0 | nsCOMPtr<nsIAlertsService> alertService = |
796 | 0 | do_GetService(NS_ALERTSERVICE_CONTRACTID); |
797 | 0 | if (!alertService) { |
798 | 0 | return; |
799 | 0 | } |
800 | 0 | |
801 | 0 | nsCOMPtr<nsIAlertsDoNotDisturb> alertServiceDND = |
802 | 0 | do_QueryInterface(alertService); |
803 | 0 | if (!alertServiceDND) { |
804 | 0 | return; |
805 | 0 | } |
806 | 0 | |
807 | 0 | mDNDRecorded = true; |
808 | 0 | bool isEnabled; |
809 | 0 | nsresult rv = alertServiceDND->GetManualDoNotDisturb(&isEnabled); |
810 | 0 | if (NS_FAILED(rv)) { |
811 | 0 | return; |
812 | 0 | } |
813 | 0 | |
814 | 0 | Telemetry::Accumulate( |
815 | 0 | Telemetry::ALERTS_SERVICE_DND_SUPPORTED_FLAG, true); |
816 | 0 | } |
817 | | |
818 | | NS_IMETHODIMP |
819 | | NotificationTelemetryService::Observe(nsISupports* aSubject, |
820 | | const char* aTopic, |
821 | | const char16_t* aData) |
822 | 0 | { |
823 | 0 | return NS_OK; |
824 | 0 | } |
825 | | |
826 | | // Observer that the alert service calls to do common tasks and/or dispatch to the |
827 | | // specific observer for the context e.g. main thread, worker, or service worker. |
828 | | class NotificationObserver final : public nsIObserver |
829 | | { |
830 | | public: |
831 | | nsCOMPtr<nsIObserver> mObserver; |
832 | | nsCOMPtr<nsIPrincipal> mPrincipal; |
833 | | bool mInPrivateBrowsing; |
834 | | NS_DECL_ISUPPORTS |
835 | | NS_DECL_NSIOBSERVER |
836 | | |
837 | | NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal, |
838 | | bool aInPrivateBrowsing) |
839 | | : mObserver(aObserver), mPrincipal(aPrincipal), |
840 | | mInPrivateBrowsing(aInPrivateBrowsing) |
841 | 0 | { |
842 | 0 | AssertIsOnMainThread(); |
843 | 0 | MOZ_ASSERT(mObserver); |
844 | 0 | MOZ_ASSERT(mPrincipal); |
845 | 0 | } |
846 | | |
847 | | protected: |
848 | | virtual ~NotificationObserver() |
849 | 0 | { |
850 | 0 | AssertIsOnMainThread(); |
851 | 0 | } |
852 | | |
853 | | nsresult AdjustPushQuota(const char* aTopic); |
854 | | }; |
855 | | |
856 | | NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver) |
857 | | |
858 | | class MainThreadNotificationObserver : public nsIObserver |
859 | | { |
860 | | public: |
861 | | UniquePtr<NotificationRef> mNotificationRef; |
862 | | NS_DECL_ISUPPORTS |
863 | | NS_DECL_NSIOBSERVER |
864 | | |
865 | | explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef) |
866 | | : mNotificationRef(std::move(aRef)) |
867 | 0 | { |
868 | 0 | AssertIsOnMainThread(); |
869 | 0 | } |
870 | | |
871 | | protected: |
872 | | virtual ~MainThreadNotificationObserver() |
873 | 0 | { |
874 | 0 | AssertIsOnMainThread(); |
875 | 0 | } |
876 | | }; |
877 | | |
878 | | NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver) |
879 | | |
880 | | NS_IMETHODIMP |
881 | | NotificationTask::Run() |
882 | 0 | { |
883 | 0 | AssertIsOnMainThread(); |
884 | 0 |
|
885 | 0 | // Get a pointer to notification before the notification takes ownership of |
886 | 0 | // the ref (it owns itself temporarily, with ShowInternal() and |
887 | 0 | // CloseInternal() passing on the ownership appropriately.) |
888 | 0 | Notification* notif = mNotificationRef->GetNotification(); |
889 | 0 | notif->mTempRef.swap(mNotificationRef); |
890 | 0 | if (mAction == eShow) { |
891 | 0 | notif->ShowInternal(); |
892 | 0 | } else if (mAction == eClose) { |
893 | 0 | notif->CloseInternal(); |
894 | 0 | } else { |
895 | 0 | MOZ_CRASH("Invalid action"); |
896 | 0 | } |
897 | 0 |
|
898 | 0 | MOZ_ASSERT(!mNotificationRef); |
899 | 0 | return NS_OK; |
900 | 0 | } |
901 | | |
902 | | // static |
903 | | bool |
904 | | Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) |
905 | 0 | { |
906 | 0 | if (!NS_IsMainThread()) { |
907 | 0 | WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); |
908 | 0 | if (!workerPrivate) { |
909 | 0 | return false; |
910 | 0 | } |
911 | 0 | |
912 | 0 | if (workerPrivate->IsServiceWorker()) { |
913 | 0 | return StaticPrefs::dom_webnotifications_serviceworker_enabled(); |
914 | 0 | } |
915 | 0 | } |
916 | 0 | |
917 | 0 | return StaticPrefs::dom_webnotifications_enabled(); |
918 | 0 | } |
919 | | |
920 | | // static |
921 | | bool |
922 | | Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj) |
923 | 0 | { |
924 | 0 | return NS_IsMainThread(); |
925 | 0 | } |
926 | | |
927 | | Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID, |
928 | | const nsAString& aTitle, const nsAString& aBody, |
929 | | NotificationDirection aDir, const nsAString& aLang, |
930 | | const nsAString& aTag, const nsAString& aIconUrl, |
931 | | bool aRequireInteraction, |
932 | | const NotificationBehavior& aBehavior) |
933 | | : DOMEventTargetHelper(aGlobal), |
934 | | mWorkerPrivate(nullptr), mObserver(nullptr), |
935 | | mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang), |
936 | | mTag(aTag), mIconUrl(aIconUrl), mRequireInteraction(aRequireInteraction), |
937 | | mBehavior(aBehavior), mData(JS::NullValue()), |
938 | | mIsClosed(false), mIsStored(false), mTaskCount(0) |
939 | 0 | { |
940 | 0 | if (!NS_IsMainThread()) { |
941 | 0 | mWorkerPrivate = GetCurrentThreadWorkerPrivate(); |
942 | 0 | MOZ_ASSERT(mWorkerPrivate); |
943 | 0 | } |
944 | 0 | } |
945 | | |
946 | | nsresult |
947 | | Notification::Init() |
948 | 0 | { |
949 | 0 | if (!mWorkerPrivate) { |
950 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
951 | 0 | NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE); |
952 | 0 |
|
953 | 0 | nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true); |
954 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
955 | 0 |
|
956 | 0 | rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true); |
957 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
958 | 0 | } |
959 | 0 |
|
960 | 0 | return NS_OK; |
961 | 0 | } |
962 | | |
963 | | void |
964 | | Notification::SetAlertName() |
965 | 0 | { |
966 | 0 | AssertIsOnMainThread(); |
967 | 0 | if (!mAlertName.IsEmpty()) { |
968 | 0 | return; |
969 | 0 | } |
970 | 0 | |
971 | 0 | nsAutoString alertName; |
972 | 0 | nsresult rv = GetOrigin(GetPrincipal(), alertName); |
973 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
974 | 0 | return; |
975 | 0 | } |
976 | 0 | |
977 | 0 | // Get the notification name that is unique per origin + tag/ID. |
978 | 0 | // The name of the alert is of the form origin#tag/ID. |
979 | 0 | alertName.Append('#'); |
980 | 0 | if (!mTag.IsEmpty()) { |
981 | 0 | alertName.AppendLiteral("tag:"); |
982 | 0 | alertName.Append(mTag); |
983 | 0 | } else { |
984 | 0 | alertName.AppendLiteral("notag:"); |
985 | 0 | alertName.Append(mID); |
986 | 0 | } |
987 | 0 |
|
988 | 0 | mAlertName = alertName; |
989 | 0 | } |
990 | | |
991 | | // May be called on any thread. |
992 | | // static |
993 | | already_AddRefed<Notification> |
994 | | Notification::Constructor(const GlobalObject& aGlobal, |
995 | | const nsAString& aTitle, |
996 | | const NotificationOptions& aOptions, |
997 | | ErrorResult& aRv) |
998 | 0 | { |
999 | 0 | // FIXME(nsm): If the sticky flag is set, throw an error. |
1000 | 0 | RefPtr<ServiceWorkerGlobalScope> scope; |
1001 | 0 | UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope); |
1002 | 0 | if (scope) { |
1003 | 0 | aRv.ThrowTypeError<MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER>(); |
1004 | 0 | return nullptr; |
1005 | 0 | } |
1006 | 0 | |
1007 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); |
1008 | 0 | RefPtr<Notification> notification = |
1009 | 0 | CreateAndShow(aGlobal.Context(), global, aTitle, aOptions, |
1010 | 0 | EmptyString(), aRv); |
1011 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
1012 | 0 | return nullptr; |
1013 | 0 | } |
1014 | 0 | |
1015 | 0 | // This is be ok since we are on the worker thread where this function will |
1016 | 0 | // run to completion before the Notification has a chance to go away. |
1017 | 0 | return notification.forget(); |
1018 | 0 | } |
1019 | | |
1020 | | // static |
1021 | | already_AddRefed<Notification> |
1022 | | Notification::ConstructFromFields( |
1023 | | nsIGlobalObject* aGlobal, |
1024 | | const nsAString& aID, |
1025 | | const nsAString& aTitle, |
1026 | | const nsAString& aDir, |
1027 | | const nsAString& aLang, |
1028 | | const nsAString& aBody, |
1029 | | const nsAString& aTag, |
1030 | | const nsAString& aIcon, |
1031 | | const nsAString& aData, |
1032 | | const nsAString& aServiceWorkerRegistrationScope, |
1033 | | ErrorResult& aRv) |
1034 | 0 | { |
1035 | 0 | MOZ_ASSERT(aGlobal); |
1036 | 0 |
|
1037 | 0 | RootedDictionary<NotificationOptions> options(RootingCx()); |
1038 | 0 | options.mDir = Notification::StringToDirection(nsString(aDir)); |
1039 | 0 | options.mLang = aLang; |
1040 | 0 | options.mBody = aBody; |
1041 | 0 | options.mTag = aTag; |
1042 | 0 | options.mIcon = aIcon; |
1043 | 0 | RefPtr<Notification> notification = CreateInternal(aGlobal, aID, aTitle, |
1044 | 0 | options); |
1045 | 0 |
|
1046 | 0 | notification->InitFromBase64(aData, aRv); |
1047 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
1048 | 0 | return nullptr; |
1049 | 0 | } |
1050 | 0 | |
1051 | 0 | notification->SetScope(aServiceWorkerRegistrationScope); |
1052 | 0 |
|
1053 | 0 | return notification.forget(); |
1054 | 0 | } |
1055 | | |
1056 | | nsresult |
1057 | | Notification::PersistNotification() |
1058 | 0 | { |
1059 | 0 | AssertIsOnMainThread(); |
1060 | 0 | nsresult rv; |
1061 | 0 | nsCOMPtr<nsINotificationStorage> notificationStorage = |
1062 | 0 | do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); |
1063 | 0 | if (NS_FAILED(rv)) { |
1064 | 0 | return rv; |
1065 | 0 | } |
1066 | 0 | |
1067 | 0 | nsString origin; |
1068 | 0 | rv = GetOrigin(GetPrincipal(), origin); |
1069 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1070 | 0 | return rv; |
1071 | 0 | } |
1072 | 0 | |
1073 | 0 | nsString id; |
1074 | 0 | GetID(id); |
1075 | 0 |
|
1076 | 0 | nsString alertName; |
1077 | 0 | GetAlertName(alertName); |
1078 | 0 |
|
1079 | 0 | nsAutoString behavior; |
1080 | 0 | if (!mBehavior.ToJSON(behavior)) { |
1081 | 0 | return NS_ERROR_FAILURE; |
1082 | 0 | } |
1083 | 0 | |
1084 | 0 | rv = notificationStorage->Put(origin, |
1085 | 0 | id, |
1086 | 0 | mTitle, |
1087 | 0 | DirectionToString(mDir), |
1088 | 0 | mLang, |
1089 | 0 | mBody, |
1090 | 0 | mTag, |
1091 | 0 | mIconUrl, |
1092 | 0 | alertName, |
1093 | 0 | mDataAsBase64, |
1094 | 0 | behavior, |
1095 | 0 | mScope); |
1096 | 0 |
|
1097 | 0 | if (NS_FAILED(rv)) { |
1098 | 0 | return rv; |
1099 | 0 | } |
1100 | 0 | |
1101 | 0 | SetStoredState(true); |
1102 | 0 | return NS_OK; |
1103 | 0 | } |
1104 | | |
1105 | | void |
1106 | | Notification::UnpersistNotification() |
1107 | 0 | { |
1108 | 0 | AssertIsOnMainThread(); |
1109 | 0 | if (IsStored()) { |
1110 | 0 | nsCOMPtr<nsINotificationStorage> notificationStorage = |
1111 | 0 | do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); |
1112 | 0 | if (notificationStorage) { |
1113 | 0 | nsString origin; |
1114 | 0 | nsresult rv = GetOrigin(GetPrincipal(), origin); |
1115 | 0 | if (NS_SUCCEEDED(rv)) { |
1116 | 0 | notificationStorage->Delete(origin, mID); |
1117 | 0 | } |
1118 | 0 | } |
1119 | 0 | SetStoredState(false); |
1120 | 0 | } |
1121 | 0 | } |
1122 | | |
1123 | | already_AddRefed<Notification> |
1124 | | Notification::CreateInternal(nsIGlobalObject* aGlobal, |
1125 | | const nsAString& aID, |
1126 | | const nsAString& aTitle, |
1127 | | const NotificationOptions& aOptions) |
1128 | 0 | { |
1129 | 0 | nsresult rv; |
1130 | 0 | nsString id; |
1131 | 0 | if (!aID.IsEmpty()) { |
1132 | 0 | id = aID; |
1133 | 0 | } else { |
1134 | 0 | nsCOMPtr<nsIUUIDGenerator> uuidgen = |
1135 | 0 | do_GetService("@mozilla.org/uuid-generator;1"); |
1136 | 0 | NS_ENSURE_TRUE(uuidgen, nullptr); |
1137 | 0 | nsID uuid; |
1138 | 0 | rv = uuidgen->GenerateUUIDInPlace(&uuid); |
1139 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
1140 | 0 |
|
1141 | 0 | char buffer[NSID_LENGTH]; |
1142 | 0 | uuid.ToProvidedString(buffer); |
1143 | 0 | NS_ConvertASCIItoUTF16 convertedID(buffer); |
1144 | 0 | id = convertedID; |
1145 | 0 | } |
1146 | 0 |
|
1147 | 0 | RefPtr<Notification> notification = new Notification(aGlobal, id, aTitle, |
1148 | 0 | aOptions.mBody, |
1149 | 0 | aOptions.mDir, |
1150 | 0 | aOptions.mLang, |
1151 | 0 | aOptions.mTag, |
1152 | 0 | aOptions.mIcon, |
1153 | 0 | aOptions.mRequireInteraction, |
1154 | 0 | aOptions.mMozbehavior); |
1155 | 0 | rv = notification->Init(); |
1156 | 0 | NS_ENSURE_SUCCESS(rv, nullptr); |
1157 | 0 | return notification.forget(); |
1158 | 0 | } |
1159 | | |
1160 | | Notification::~Notification() |
1161 | 0 | { |
1162 | 0 | mData.setUndefined(); |
1163 | 0 | mozilla::DropJSObjects(this); |
1164 | 0 | AssertIsOnTargetThread(); |
1165 | 0 | MOZ_ASSERT(!mWorkerHolder); |
1166 | 0 | MOZ_ASSERT(!mTempRef); |
1167 | 0 | } |
1168 | | |
1169 | | NS_IMPL_CYCLE_COLLECTION_CLASS(Notification) |
1170 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification, DOMEventTargetHelper) |
1171 | 0 | tmp->mData.setUndefined(); |
1172 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
1173 | | |
1174 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification, DOMEventTargetHelper) |
1175 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
1176 | | |
1177 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification, DOMEventTargetHelper) |
1178 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData) |
1179 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
1180 | | |
1181 | | NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper) |
1182 | | NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper) |
1183 | | |
1184 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification) |
1185 | 0 | NS_INTERFACE_MAP_ENTRY(nsIObserver) |
1186 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) |
1187 | 0 | NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) |
1188 | | |
1189 | | nsIPrincipal* |
1190 | | Notification::GetPrincipal() |
1191 | 0 | { |
1192 | 0 | AssertIsOnMainThread(); |
1193 | 0 | if (mWorkerPrivate) { |
1194 | 0 | return mWorkerPrivate->GetPrincipal(); |
1195 | 0 | } else { |
1196 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); |
1197 | 0 | NS_ENSURE_TRUE(sop, nullptr); |
1198 | 0 | return sop->GetPrincipal(); |
1199 | 0 | } |
1200 | 0 | } |
1201 | | |
1202 | | class WorkerNotificationObserver final : public MainThreadNotificationObserver |
1203 | | { |
1204 | | public: |
1205 | | NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver, |
1206 | | MainThreadNotificationObserver) |
1207 | | NS_DECL_NSIOBSERVER |
1208 | | |
1209 | | explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef) |
1210 | | : MainThreadNotificationObserver(std::move(aRef)) |
1211 | 0 | { |
1212 | 0 | AssertIsOnMainThread(); |
1213 | 0 | MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate); |
1214 | 0 | } |
1215 | | |
1216 | | void |
1217 | | ForgetNotification() |
1218 | 0 | { |
1219 | 0 | AssertIsOnMainThread(); |
1220 | 0 | mNotificationRef->Forget(); |
1221 | 0 | } |
1222 | | |
1223 | | protected: |
1224 | | virtual ~WorkerNotificationObserver() |
1225 | 0 | { |
1226 | 0 | AssertIsOnMainThread(); |
1227 | 0 |
|
1228 | 0 | MOZ_ASSERT(mNotificationRef); |
1229 | 0 | Notification* notification = mNotificationRef->GetNotification(); |
1230 | 0 | if (notification) { |
1231 | 0 | notification->mObserver = nullptr; |
1232 | 0 | } |
1233 | 0 | } |
1234 | | }; |
1235 | | |
1236 | | class ServiceWorkerNotificationObserver final : public nsIObserver |
1237 | | { |
1238 | | public: |
1239 | | NS_DECL_ISUPPORTS |
1240 | | NS_DECL_NSIOBSERVER |
1241 | | |
1242 | | ServiceWorkerNotificationObserver(const nsAString& aScope, |
1243 | | nsIPrincipal* aPrincipal, |
1244 | | const nsAString& aID, |
1245 | | const nsAString& aTitle, |
1246 | | const nsAString& aDir, |
1247 | | const nsAString& aLang, |
1248 | | const nsAString& aBody, |
1249 | | const nsAString& aTag, |
1250 | | const nsAString& aIcon, |
1251 | | const nsAString& aData, |
1252 | | const nsAString& aBehavior) |
1253 | | : mScope(aScope), mID(aID), mPrincipal(aPrincipal), mTitle(aTitle) |
1254 | | , mDir(aDir), mLang(aLang), mBody(aBody), mTag(aTag), mIcon(aIcon) |
1255 | | , mData(aData), mBehavior(aBehavior) |
1256 | 0 | { |
1257 | 0 | AssertIsOnMainThread(); |
1258 | 0 | MOZ_ASSERT(aPrincipal); |
1259 | 0 | } |
1260 | | |
1261 | | private: |
1262 | | ~ServiceWorkerNotificationObserver() |
1263 | 0 | {} |
1264 | | |
1265 | | const nsString mScope; |
1266 | | const nsString mID; |
1267 | | nsCOMPtr<nsIPrincipal> mPrincipal; |
1268 | | const nsString mTitle; |
1269 | | const nsString mDir; |
1270 | | const nsString mLang; |
1271 | | const nsString mBody; |
1272 | | const nsString mTag; |
1273 | | const nsString mIcon; |
1274 | | const nsString mData; |
1275 | | const nsString mBehavior; |
1276 | | }; |
1277 | | |
1278 | | NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver) |
1279 | | |
1280 | | // For ServiceWorkers. |
1281 | | bool |
1282 | | Notification::DispatchNotificationClickEvent() |
1283 | 0 | { |
1284 | 0 | MOZ_ASSERT(mWorkerPrivate); |
1285 | 0 | MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); |
1286 | 0 | mWorkerPrivate->AssertIsOnWorkerThread(); |
1287 | 0 |
|
1288 | 0 | NotificationEventInit options; |
1289 | 0 | options.mNotification = this; |
1290 | 0 |
|
1291 | 0 | ErrorResult result; |
1292 | 0 | RefPtr<EventTarget> target = mWorkerPrivate->GlobalScope(); |
1293 | 0 | RefPtr<NotificationEvent> event = |
1294 | 0 | NotificationEvent::Constructor(target, |
1295 | 0 | NS_LITERAL_STRING("notificationclick"), |
1296 | 0 | options, |
1297 | 0 | result); |
1298 | 0 | if (NS_WARN_IF(result.Failed())) { |
1299 | 0 | return false; |
1300 | 0 | } |
1301 | 0 | |
1302 | 0 | event->SetTrusted(true); |
1303 | 0 | WantsPopupControlCheck popupControlCheck(event); |
1304 | 0 | target->DispatchEvent(*event); |
1305 | 0 | // We always return false since in case of dispatching on the serviceworker, |
1306 | 0 | // there is no well defined window to focus. The script may use the |
1307 | 0 | // Client.focus() API if it wishes. |
1308 | 0 | return false; |
1309 | 0 | } |
1310 | | |
1311 | | bool |
1312 | | Notification::DispatchClickEvent() |
1313 | 0 | { |
1314 | 0 | AssertIsOnTargetThread(); |
1315 | 0 | RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); |
1316 | 0 | event->InitEvent(NS_LITERAL_STRING("click"), false, true); |
1317 | 0 | event->SetTrusted(true); |
1318 | 0 | WantsPopupControlCheck popupControlCheck(event); |
1319 | 0 | return DispatchEvent(*event, CallerType::System, IgnoreErrors()); |
1320 | 0 | } |
1321 | | |
1322 | | // Overrides dispatch and run handlers so we can directly dispatch from main |
1323 | | // thread to child workers. |
1324 | | class NotificationClickWorkerRunnable final : public NotificationWorkerRunnable |
1325 | | { |
1326 | | Notification* mNotification; |
1327 | | // Optional window that gets focused if click event is not |
1328 | | // preventDefault()ed. |
1329 | | nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow; |
1330 | | public: |
1331 | | NotificationClickWorkerRunnable(Notification* aNotification, |
1332 | | const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow) |
1333 | | : NotificationWorkerRunnable(aNotification->mWorkerPrivate) |
1334 | | , mNotification(aNotification) |
1335 | | , mWindow(aWindow) |
1336 | 0 | { |
1337 | 0 | MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow); |
1338 | 0 | } |
1339 | | |
1340 | | void |
1341 | | WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override |
1342 | 0 | { |
1343 | 0 | bool doDefaultAction = mNotification->DispatchClickEvent(); |
1344 | 0 | MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction); |
1345 | 0 | if (doDefaultAction) { |
1346 | 0 | RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow); |
1347 | 0 | mWorkerPrivate->DispatchToMainThread(r.forget()); |
1348 | 0 | } |
1349 | 0 | } |
1350 | | }; |
1351 | | |
1352 | | NS_IMETHODIMP |
1353 | | NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, |
1354 | | const char16_t* aData) |
1355 | 0 | { |
1356 | 0 | AssertIsOnMainThread(); |
1357 | 0 |
|
1358 | 0 | if (!strcmp("alertdisablecallback", aTopic)) { |
1359 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 1); |
1360 | 0 | if (XRE_IsParentProcess()) { |
1361 | 0 | return Notification::RemovePermission(mPrincipal); |
1362 | 0 | } |
1363 | 0 | // Permissions can't be removed from the content process. Send a message |
1364 | 0 | // to the parent; `ContentParent::RecvDisableNotifications` will call |
1365 | 0 | // `RemovePermission`. |
1366 | 0 | ContentChild::GetSingleton()->SendDisableNotifications( |
1367 | 0 | IPC::Principal(mPrincipal)); |
1368 | 0 | return NS_OK; |
1369 | 0 | } else if (!strcmp("alertclickcallback", aTopic)) { |
1370 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_CLICKED, 1); |
1371 | 0 | } else if (!strcmp("alertsettingscallback", aTopic)) { |
1372 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_MENU, 2); |
1373 | 0 | if (XRE_IsParentProcess()) { |
1374 | 0 | return Notification::OpenSettings(mPrincipal); |
1375 | 0 | } |
1376 | 0 | // `ContentParent::RecvOpenNotificationSettings` notifies observers in the |
1377 | 0 | // parent process. |
1378 | 0 | ContentChild::GetSingleton()->SendOpenNotificationSettings( |
1379 | 0 | IPC::Principal(mPrincipal)); |
1380 | 0 | return NS_OK; |
1381 | 0 | } else if (!strcmp("alertshow", aTopic) || |
1382 | 0 | !strcmp("alertfinished", aTopic)) { |
1383 | 0 | RefPtr<NotificationTelemetryService> telemetry = |
1384 | 0 | NotificationTelemetryService::GetInstance(); |
1385 | 0 | if (telemetry) { |
1386 | 0 | // Record whether "do not disturb" is supported after the first |
1387 | 0 | // notification, to account for falling back to XUL alerts. |
1388 | 0 | telemetry->RecordDNDSupported(); |
1389 | 0 | } |
1390 | 0 | Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic))); |
1391 | 0 |
|
1392 | 0 | if (!strcmp("alertshow", aTopic)) { |
1393 | 0 | // Record notifications actually shown (e.g. don't count if DND is on). |
1394 | 0 | Telemetry::Accumulate(Telemetry::WEB_NOTIFICATION_SHOWN, 1); |
1395 | 0 | } |
1396 | 0 | } |
1397 | 0 |
|
1398 | 0 | return mObserver->Observe(aSubject, aTopic, aData); |
1399 | 0 | } |
1400 | | |
1401 | | nsresult |
1402 | | NotificationObserver::AdjustPushQuota(const char* aTopic) |
1403 | 0 | { |
1404 | 0 | nsCOMPtr<nsIPushQuotaManager> pushQuotaManager = |
1405 | 0 | do_GetService("@mozilla.org/push/Service;1"); |
1406 | 0 | if (!pushQuotaManager) { |
1407 | 0 | return NS_ERROR_FAILURE; |
1408 | 0 | } |
1409 | 0 | |
1410 | 0 | nsAutoCString origin; |
1411 | 0 | nsresult rv = mPrincipal->GetOrigin(origin); |
1412 | 0 | if (NS_FAILED(rv)) { |
1413 | 0 | return rv; |
1414 | 0 | } |
1415 | 0 | |
1416 | 0 | if (!strcmp("alertshow", aTopic)) { |
1417 | 0 | return pushQuotaManager->NotificationForOriginShown(origin.get()); |
1418 | 0 | } |
1419 | 0 | return pushQuotaManager->NotificationForOriginClosed(origin.get()); |
1420 | 0 | } |
1421 | | |
1422 | | NS_IMETHODIMP |
1423 | | MainThreadNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, |
1424 | | const char16_t* aData) |
1425 | 0 | { |
1426 | 0 | AssertIsOnMainThread(); |
1427 | 0 | MOZ_ASSERT(mNotificationRef); |
1428 | 0 | Notification* notification = mNotificationRef->GetNotification(); |
1429 | 0 | MOZ_ASSERT(notification); |
1430 | 0 | if (!strcmp("alertclickcallback", aTopic)) { |
1431 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = notification->GetOwner(); |
1432 | 0 | if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) { |
1433 | 0 | // Window has been closed, this observer is not valid anymore |
1434 | 0 | return NS_ERROR_FAILURE; |
1435 | 0 | } |
1436 | 0 | |
1437 | 0 | bool doDefaultAction = notification->DispatchClickEvent(); |
1438 | 0 | if (doDefaultAction) { |
1439 | 0 | // Browser UI may use DOMWindowFocus to focus the tab |
1440 | 0 | // from which the event was dispatched. |
1441 | 0 | nsContentUtils::DispatchFocusChromeEvent(window->GetOuterWindow()); |
1442 | 0 | } |
1443 | 0 | } else if (!strcmp("alertfinished", aTopic)) { |
1444 | 0 | notification->UnpersistNotification(); |
1445 | 0 | notification->mIsClosed = true; |
1446 | 0 | notification->DispatchTrustedEvent(NS_LITERAL_STRING("close")); |
1447 | 0 | } else if (!strcmp("alertshow", aTopic)) { |
1448 | 0 | notification->DispatchTrustedEvent(NS_LITERAL_STRING("show")); |
1449 | 0 | } |
1450 | 0 | return NS_OK; |
1451 | 0 | } |
1452 | | |
1453 | | NS_IMETHODIMP |
1454 | | WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, |
1455 | | const char16_t* aData) |
1456 | 0 | { |
1457 | 0 | AssertIsOnMainThread(); |
1458 | 0 | MOZ_ASSERT(mNotificationRef); |
1459 | 0 | // For an explanation of why it is OK to pass this rawptr to the event |
1460 | 0 | // runnables, see the Notification class comment. |
1461 | 0 | Notification* notification = mNotificationRef->GetNotification(); |
1462 | 0 | // We can't assert notification here since the feature could've unset it. |
1463 | 0 | if (NS_WARN_IF(!notification)) { |
1464 | 0 | return NS_ERROR_FAILURE; |
1465 | 0 | } |
1466 | 0 | |
1467 | 0 | MOZ_ASSERT(notification->mWorkerPrivate); |
1468 | 0 |
|
1469 | 0 | RefPtr<WorkerRunnable> r; |
1470 | 0 | if (!strcmp("alertclickcallback", aTopic)) { |
1471 | 0 | nsPIDOMWindowInner* window = nullptr; |
1472 | 0 | if (!notification->mWorkerPrivate->IsServiceWorker()) { |
1473 | 0 | WorkerPrivate* top = notification->mWorkerPrivate; |
1474 | 0 | while (top->GetParent()) { |
1475 | 0 | top = top->GetParent(); |
1476 | 0 | } |
1477 | 0 |
|
1478 | 0 | window = top->GetWindow(); |
1479 | 0 | if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) { |
1480 | 0 | // Window has been closed, this observer is not valid anymore |
1481 | 0 | return NS_ERROR_FAILURE; |
1482 | 0 | } |
1483 | 0 | } |
1484 | 0 | |
1485 | 0 | // Instead of bothering with adding features and other worker lifecycle |
1486 | 0 | // management, we simply hold strongrefs to the window and document. |
1487 | 0 | nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle( |
1488 | 0 | new nsMainThreadPtrHolder<nsPIDOMWindowInner>( |
1489 | 0 | "WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window)); |
1490 | 0 |
|
1491 | 0 | r = new NotificationClickWorkerRunnable(notification, windowHandle); |
1492 | 0 | } else if (!strcmp("alertfinished", aTopic)) { |
1493 | 0 | notification->UnpersistNotification(); |
1494 | 0 | notification->mIsClosed = true; |
1495 | 0 | r = new NotificationEventWorkerRunnable(notification, |
1496 | 0 | NS_LITERAL_STRING("close")); |
1497 | 0 | } else if (!strcmp("alertshow", aTopic)) { |
1498 | 0 | r = new NotificationEventWorkerRunnable(notification, |
1499 | 0 | NS_LITERAL_STRING("show")); |
1500 | 0 | } |
1501 | 0 |
|
1502 | 0 | MOZ_ASSERT(r); |
1503 | 0 | if (!r->Dispatch()) { |
1504 | 0 | NS_WARNING("Could not dispatch event to worker notification"); |
1505 | 0 | } |
1506 | 0 | return NS_OK; |
1507 | 0 | } |
1508 | | |
1509 | | NS_IMETHODIMP |
1510 | | ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject, |
1511 | | const char* aTopic, |
1512 | | const char16_t* aData) |
1513 | 0 | { |
1514 | 0 | AssertIsOnMainThread(); |
1515 | 0 |
|
1516 | 0 | nsAutoCString originSuffix; |
1517 | 0 | nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); |
1518 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1519 | 0 | return rv; |
1520 | 0 | } |
1521 | 0 | |
1522 | 0 | nsCOMPtr<nsIServiceWorkerManager> swm = |
1523 | 0 | mozilla::services::GetServiceWorkerManager(); |
1524 | 0 | if (NS_WARN_IF(!swm)) { |
1525 | 0 | return NS_ERROR_FAILURE; |
1526 | 0 | } |
1527 | 0 | |
1528 | 0 | if (!strcmp("alertclickcallback", aTopic)) { |
1529 | 0 | rv = swm->SendNotificationClickEvent(originSuffix, |
1530 | 0 | NS_ConvertUTF16toUTF8(mScope), |
1531 | 0 | mID, |
1532 | 0 | mTitle, |
1533 | 0 | mDir, |
1534 | 0 | mLang, |
1535 | 0 | mBody, |
1536 | 0 | mTag, |
1537 | 0 | mIcon, |
1538 | 0 | mData, |
1539 | 0 | mBehavior); |
1540 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
1541 | 0 | return NS_OK; |
1542 | 0 | } |
1543 | 0 |
|
1544 | 0 | if (!strcmp("alertfinished", aTopic)) { |
1545 | 0 | nsString origin; |
1546 | 0 | nsresult rv = Notification::GetOrigin(mPrincipal, origin); |
1547 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1548 | 0 | return rv; |
1549 | 0 | } |
1550 | 0 | |
1551 | 0 | // Remove closed or dismissed persistent notifications. |
1552 | 0 | nsCOMPtr<nsINotificationStorage> notificationStorage = |
1553 | 0 | do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); |
1554 | 0 | if (notificationStorage) { |
1555 | 0 | notificationStorage->Delete(origin, mID); |
1556 | 0 | } |
1557 | 0 |
|
1558 | 0 | rv = swm->SendNotificationCloseEvent(originSuffix, |
1559 | 0 | NS_ConvertUTF16toUTF8(mScope), |
1560 | 0 | mID, |
1561 | 0 | mTitle, |
1562 | 0 | mDir, |
1563 | 0 | mLang, |
1564 | 0 | mBody, |
1565 | 0 | mTag, |
1566 | 0 | mIcon, |
1567 | 0 | mData, |
1568 | 0 | mBehavior); |
1569 | 0 | Unused << NS_WARN_IF(NS_FAILED(rv)); |
1570 | 0 | return NS_OK; |
1571 | 0 | } |
1572 | 0 |
|
1573 | 0 | return NS_OK; |
1574 | 0 | } |
1575 | | |
1576 | | bool |
1577 | | Notification::IsInPrivateBrowsing() |
1578 | 0 | { |
1579 | 0 | AssertIsOnMainThread(); |
1580 | 0 |
|
1581 | 0 | nsIDocument* doc = nullptr; |
1582 | 0 |
|
1583 | 0 | if (mWorkerPrivate) { |
1584 | 0 | doc = mWorkerPrivate->GetDocument(); |
1585 | 0 | } else if (GetOwner()) { |
1586 | 0 | doc = GetOwner()->GetExtantDoc(); |
1587 | 0 | } |
1588 | 0 |
|
1589 | 0 | if (doc) { |
1590 | 0 | nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext(); |
1591 | 0 | return loadContext && loadContext->UsePrivateBrowsing(); |
1592 | 0 | } |
1593 | 0 |
|
1594 | 0 | if (mWorkerPrivate) { |
1595 | 0 | // Not all workers may have a document, but with Bug 1107516 fixed, they |
1596 | 0 | // should all have a loadcontext. |
1597 | 0 | nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup(); |
1598 | 0 | nsCOMPtr<nsILoadContext> loadContext; |
1599 | 0 | NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext), |
1600 | 0 | getter_AddRefs(loadContext)); |
1601 | 0 | return loadContext && loadContext->UsePrivateBrowsing(); |
1602 | 0 | } |
1603 | 0 |
|
1604 | 0 | //XXXnsm Should this default to true? |
1605 | 0 | return false; |
1606 | 0 | } |
1607 | | |
1608 | | namespace { |
1609 | | struct StringWriteFunc : public JSONWriteFunc |
1610 | | { |
1611 | | nsAString& mBuffer; // This struct must not outlive this buffer |
1612 | 0 | explicit StringWriteFunc(nsAString& buffer) : mBuffer(buffer) {} |
1613 | | |
1614 | | void Write(const char* aStr) override |
1615 | 0 | { |
1616 | 0 | mBuffer.Append(NS_ConvertUTF8toUTF16(aStr)); |
1617 | 0 | } |
1618 | | }; |
1619 | | } |
1620 | | |
1621 | | void |
1622 | | Notification::ShowInternal() |
1623 | 0 | { |
1624 | 0 | AssertIsOnMainThread(); |
1625 | 0 | MOZ_ASSERT(mTempRef, "Notification should take ownership of itself before" |
1626 | 0 | "calling ShowInternal!"); |
1627 | 0 | // A notification can only have one observer and one call to ShowInternal. |
1628 | 0 | MOZ_ASSERT(!mObserver); |
1629 | 0 |
|
1630 | 0 | // Transfer ownership to local scope so we can either release it at the end |
1631 | 0 | // of this function or transfer it to the observer. |
1632 | 0 | UniquePtr<NotificationRef> ownership; |
1633 | 0 | mozilla::Swap(ownership, mTempRef); |
1634 | 0 | MOZ_ASSERT(ownership->GetNotification() == this); |
1635 | 0 |
|
1636 | 0 | nsresult rv = PersistNotification(); |
1637 | 0 | if (NS_FAILED(rv)) { |
1638 | 0 | NS_WARNING("Could not persist Notification"); |
1639 | 0 | } |
1640 | 0 |
|
1641 | 0 | nsCOMPtr<nsIAlertsService> alertService = |
1642 | 0 | do_GetService(NS_ALERTSERVICE_CONTRACTID); |
1643 | 0 |
|
1644 | 0 | ErrorResult result; |
1645 | 0 | NotificationPermission permission = NotificationPermission::Denied; |
1646 | 0 | if (mWorkerPrivate) { |
1647 | 0 | permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result); |
1648 | 0 | } else { |
1649 | 0 | permission = GetPermissionInternal(GetOwner(), result); |
1650 | 0 | } |
1651 | 0 | // We rely on GetPermissionInternal returning Denied on all failure codepaths. |
1652 | 0 | MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied); |
1653 | 0 | result.SuppressException(); |
1654 | 0 | if (permission != NotificationPermission::Granted || !alertService) { |
1655 | 0 | if (mWorkerPrivate) { |
1656 | 0 | RefPtr<NotificationEventWorkerRunnable> r = |
1657 | 0 | new NotificationEventWorkerRunnable(this, |
1658 | 0 | NS_LITERAL_STRING("error")); |
1659 | 0 | if (!r->Dispatch()) { |
1660 | 0 | NS_WARNING("Could not dispatch event to worker notification"); |
1661 | 0 | } |
1662 | 0 | } else { |
1663 | 0 | DispatchTrustedEvent(NS_LITERAL_STRING("error")); |
1664 | 0 | } |
1665 | 0 | return; |
1666 | 0 | } |
1667 | 0 |
|
1668 | 0 | nsAutoString iconUrl; |
1669 | 0 | nsAutoString soundUrl; |
1670 | 0 | ResolveIconAndSoundURL(iconUrl, soundUrl); |
1671 | 0 |
|
1672 | 0 | bool isPersistent = false; |
1673 | 0 | nsCOMPtr<nsIObserver> observer; |
1674 | 0 | if (mScope.IsEmpty()) { |
1675 | 0 | // Ownership passed to observer. |
1676 | 0 | if (mWorkerPrivate) { |
1677 | 0 | // Scope better be set on ServiceWorker initiated requests. |
1678 | 0 | MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker()); |
1679 | 0 | // Keep a pointer so that the feature can tell the observer not to release |
1680 | 0 | // the notification. |
1681 | 0 | mObserver = new WorkerNotificationObserver(std::move(ownership)); |
1682 | 0 | observer = mObserver; |
1683 | 0 | } else { |
1684 | 0 | observer = new MainThreadNotificationObserver(std::move(ownership)); |
1685 | 0 | } |
1686 | 0 | } else { |
1687 | 0 | isPersistent = true; |
1688 | 0 | // This observer does not care about the Notification. It will be released |
1689 | 0 | // at the end of this function. |
1690 | 0 | // |
1691 | 0 | // The observer is wholly owned by the NotificationObserver passed to the alert service. |
1692 | 0 | nsAutoString behavior; |
1693 | 0 | if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) { |
1694 | 0 | behavior.Truncate(); |
1695 | 0 | } |
1696 | 0 | observer = new ServiceWorkerNotificationObserver(mScope, |
1697 | 0 | GetPrincipal(), |
1698 | 0 | mID, |
1699 | 0 | mTitle, |
1700 | 0 | DirectionToString(mDir), |
1701 | 0 | mLang, |
1702 | 0 | mBody, |
1703 | 0 | mTag, |
1704 | 0 | iconUrl, |
1705 | 0 | mDataAsBase64, |
1706 | 0 | behavior); |
1707 | 0 | } |
1708 | 0 | MOZ_ASSERT(observer); |
1709 | 0 | nsCOMPtr<nsIObserver> alertObserver = new NotificationObserver(observer, |
1710 | 0 | GetPrincipal(), |
1711 | 0 | IsInPrivateBrowsing()); |
1712 | 0 |
|
1713 | 0 |
|
1714 | 0 | // In the case of IPC, the parent process uses the cookie to map to |
1715 | 0 | // nsIObserver. Thus the cookie must be unique to differentiate observers. |
1716 | 0 | nsString uniqueCookie = NS_LITERAL_STRING("notification:"); |
1717 | 0 | uniqueCookie.AppendInt(sCount++); |
1718 | 0 | bool inPrivateBrowsing = IsInPrivateBrowsing(); |
1719 | 0 |
|
1720 | 0 | bool requireInteraction = mRequireInteraction; |
1721 | 0 | if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) { |
1722 | 0 | requireInteraction = false; |
1723 | 0 | } |
1724 | 0 |
|
1725 | 0 | nsAutoString alertName; |
1726 | 0 | GetAlertName(alertName); |
1727 | 0 | nsCOMPtr<nsIAlertNotification> alert = |
1728 | 0 | do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID); |
1729 | 0 | NS_ENSURE_TRUE_VOID(alert); |
1730 | 0 | nsIPrincipal* principal = GetPrincipal(); |
1731 | 0 | rv = alert->Init(alertName, iconUrl, mTitle, mBody, |
1732 | 0 | true, |
1733 | 0 | uniqueCookie, |
1734 | 0 | DirectionToString(mDir), |
1735 | 0 | mLang, |
1736 | 0 | mDataAsBase64, |
1737 | 0 | GetPrincipal(), |
1738 | 0 | inPrivateBrowsing, |
1739 | 0 | requireInteraction); |
1740 | 0 | NS_ENSURE_SUCCESS_VOID(rv); |
1741 | 0 |
|
1742 | 0 | if (isPersistent) { |
1743 | 0 | nsAutoString persistentData; |
1744 | 0 |
|
1745 | 0 | JSONWriter w(MakeUnique<StringWriteFunc>(persistentData)); |
1746 | 0 | w.Start(); |
1747 | 0 |
|
1748 | 0 | nsAutoString origin; |
1749 | 0 | Notification::GetOrigin(principal, origin); |
1750 | 0 | w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin).get()); |
1751 | 0 |
|
1752 | 0 | w.StringProperty("id", NS_ConvertUTF16toUTF8(mID).get()); |
1753 | 0 |
|
1754 | 0 | nsAutoCString originSuffix; |
1755 | 0 | principal->GetOriginSuffix(originSuffix); |
1756 | 0 | w.StringProperty("originSuffix", originSuffix.get()); |
1757 | 0 |
|
1758 | 0 | w.End(); |
1759 | 0 |
|
1760 | 0 | alertService->ShowPersistentNotification(persistentData, alert, alertObserver); |
1761 | 0 | } else { |
1762 | 0 | alertService->ShowAlert(alert, alertObserver); |
1763 | 0 | } |
1764 | 0 | } |
1765 | | |
1766 | | /* static */ bool |
1767 | | Notification::RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */) |
1768 | 0 | { |
1769 | 0 | // requestPermission() is not allowed on workers. The calling page should ask |
1770 | 0 | // for permission on the worker's behalf. This is to prevent 'which window |
1771 | 0 | // should show the browser pop-up'. See discussion: |
1772 | 0 | // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html |
1773 | 0 | return NS_IsMainThread(); |
1774 | 0 | } |
1775 | | |
1776 | | // static |
1777 | | already_AddRefed<Promise> |
1778 | | Notification::RequestPermission(const GlobalObject& aGlobal, |
1779 | | const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback, |
1780 | | ErrorResult& aRv) |
1781 | 0 | { |
1782 | 0 | AssertIsOnMainThread(); |
1783 | 0 |
|
1784 | 0 | // Get principal from global to make permission request for notifications. |
1785 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports()); |
1786 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports()); |
1787 | 0 | if (!sop) { |
1788 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
1789 | 0 | return nullptr; |
1790 | 0 | } |
1791 | 0 | nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); |
1792 | 0 |
|
1793 | 0 | RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv); |
1794 | 0 | if (aRv.Failed()) { |
1795 | 0 | return nullptr; |
1796 | 0 | } |
1797 | 0 | NotificationPermissionCallback* permissionCallback = nullptr; |
1798 | 0 | if (aCallback.WasPassed()) { |
1799 | 0 | permissionCallback = &aCallback.Value(); |
1800 | 0 | } |
1801 | 0 | bool isHandlingUserInput = EventStateManager::IsHandlingUserInput(); |
1802 | 0 | nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest( |
1803 | 0 | principal, isHandlingUserInput, window, promise, permissionCallback); |
1804 | 0 |
|
1805 | 0 | window->AsGlobal()->Dispatch(TaskCategory::Other, request.forget()); |
1806 | 0 |
|
1807 | 0 | return promise.forget(); |
1808 | 0 | } |
1809 | | |
1810 | | // static |
1811 | | NotificationPermission |
1812 | | Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv) |
1813 | 0 | { |
1814 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); |
1815 | 0 | return GetPermission(global, aRv); |
1816 | 0 | } |
1817 | | |
1818 | | // static |
1819 | | NotificationPermission |
1820 | | Notification::GetPermission(nsIGlobalObject* aGlobal, ErrorResult& aRv) |
1821 | 0 | { |
1822 | 0 | if (NS_IsMainThread()) { |
1823 | 0 | return GetPermissionInternal(aGlobal, aRv); |
1824 | 0 | } else { |
1825 | 0 | WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
1826 | 0 | MOZ_ASSERT(worker); |
1827 | 0 | RefPtr<GetPermissionRunnable> r = |
1828 | 0 | new GetPermissionRunnable(worker); |
1829 | 0 | r->Dispatch(Canceling, aRv); |
1830 | 0 | if (aRv.Failed()) { |
1831 | 0 | return NotificationPermission::Denied; |
1832 | 0 | } |
1833 | 0 | |
1834 | 0 | return r->GetPermission(); |
1835 | 0 | } |
1836 | 0 | } |
1837 | | |
1838 | | /* static */ NotificationPermission |
1839 | | Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) |
1840 | 0 | { |
1841 | 0 | // Get principal from global to check permission for notifications. |
1842 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal); |
1843 | 0 | if (!sop) { |
1844 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
1845 | 0 | return NotificationPermission::Denied; |
1846 | 0 | } |
1847 | 0 | |
1848 | 0 | nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); |
1849 | 0 | return GetPermissionInternal(principal, aRv); |
1850 | 0 | } |
1851 | | |
1852 | | /* static */ NotificationPermission |
1853 | | Notification::GetPermissionInternal(nsIPrincipal* aPrincipal, |
1854 | | ErrorResult& aRv) |
1855 | 0 | { |
1856 | 0 | AssertIsOnMainThread(); |
1857 | 0 | MOZ_ASSERT(aPrincipal); |
1858 | 0 |
|
1859 | 0 | if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { |
1860 | 0 | return NotificationPermission::Granted; |
1861 | 0 | } else { |
1862 | 0 | // Allow files to show notifications by default. |
1863 | 0 | nsCOMPtr<nsIURI> uri; |
1864 | 0 | aPrincipal->GetURI(getter_AddRefs(uri)); |
1865 | 0 | if (uri) { |
1866 | 0 | bool isFile; |
1867 | 0 | uri->SchemeIs("file", &isFile); |
1868 | 0 | if (isFile) { |
1869 | 0 | return NotificationPermission::Granted; |
1870 | 0 | } |
1871 | 0 | } |
1872 | 0 | } |
1873 | 0 | |
1874 | 0 | // We also allow notifications is they are pref'ed on. |
1875 | 0 | if (Preferences::GetBool("notification.prompt.testing", false)) { |
1876 | 0 | if (Preferences::GetBool("notification.prompt.testing.allow", true)) { |
1877 | 0 | return NotificationPermission::Granted; |
1878 | 0 | } else { |
1879 | 0 | return NotificationPermission::Denied; |
1880 | 0 | } |
1881 | 0 | } |
1882 | 0 | |
1883 | 0 | return TestPermission(aPrincipal); |
1884 | 0 | } |
1885 | | |
1886 | | /* static */ NotificationPermission |
1887 | | Notification::TestPermission(nsIPrincipal* aPrincipal) |
1888 | 0 | { |
1889 | 0 | AssertIsOnMainThread(); |
1890 | 0 |
|
1891 | 0 | uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; |
1892 | 0 |
|
1893 | 0 | nsCOMPtr<nsIPermissionManager> permissionManager = |
1894 | 0 | services::GetPermissionManager(); |
1895 | 0 | if (!permissionManager) { |
1896 | 0 | return NotificationPermission::Default; |
1897 | 0 | } |
1898 | 0 | |
1899 | 0 | permissionManager->TestExactPermissionFromPrincipal(aPrincipal, |
1900 | 0 | "desktop-notification", |
1901 | 0 | &permission); |
1902 | 0 |
|
1903 | 0 | // Convert the result to one of the enum types. |
1904 | 0 | switch (permission) { |
1905 | 0 | case nsIPermissionManager::ALLOW_ACTION: |
1906 | 0 | return NotificationPermission::Granted; |
1907 | 0 | case nsIPermissionManager::DENY_ACTION: |
1908 | 0 | return NotificationPermission::Denied; |
1909 | 0 | default: |
1910 | 0 | return NotificationPermission::Default; |
1911 | 0 | } |
1912 | 0 | } |
1913 | | |
1914 | | nsresult |
1915 | | Notification::ResolveIconAndSoundURL(nsString& iconUrl, nsString& soundUrl) |
1916 | 0 | { |
1917 | 0 | AssertIsOnMainThread(); |
1918 | 0 | nsresult rv = NS_OK; |
1919 | 0 |
|
1920 | 0 | nsCOMPtr<nsIURI> baseUri; |
1921 | 0 |
|
1922 | 0 | // XXXnsm If I understand correctly, the character encoding for resolving |
1923 | 0 | // URIs in new specs is dictated by the URL spec, which states that unless |
1924 | 0 | // the URL parser is passed an override encoding, the charset to be used is |
1925 | 0 | // UTF-8. The new Notification icon/sound specification just says to use the |
1926 | 0 | // Fetch API, where the Request constructor defers to URL parsing specifying |
1927 | 0 | // the API base URL and no override encoding. So we've to use UTF-8 on |
1928 | 0 | // workers, but for backwards compat keeping it document charset on main |
1929 | 0 | // thread. |
1930 | 0 | auto encoding = UTF_8_ENCODING; |
1931 | 0 |
|
1932 | 0 | if (mWorkerPrivate) { |
1933 | 0 | baseUri = mWorkerPrivate->GetBaseURI(); |
1934 | 0 | } else { |
1935 | 0 | nsIDocument* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr; |
1936 | 0 | if (doc) { |
1937 | 0 | baseUri = doc->GetBaseURI(); |
1938 | 0 | encoding = doc->GetDocumentCharacterSet(); |
1939 | 0 | } else { |
1940 | 0 | NS_WARNING("No document found for main thread notification!"); |
1941 | 0 | return NS_ERROR_FAILURE; |
1942 | 0 | } |
1943 | 0 | } |
1944 | 0 |
|
1945 | 0 | if (baseUri) { |
1946 | 0 | if (mIconUrl.Length() > 0) { |
1947 | 0 | nsCOMPtr<nsIURI> srcUri; |
1948 | 0 | rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, encoding, baseUri); |
1949 | 0 | if (NS_SUCCEEDED(rv)) { |
1950 | 0 | nsAutoCString src; |
1951 | 0 | srcUri->GetSpec(src); |
1952 | 0 | iconUrl = NS_ConvertUTF8toUTF16(src); |
1953 | 0 | } |
1954 | 0 | } |
1955 | 0 | if (mBehavior.mSoundFile.Length() > 0) { |
1956 | 0 | nsCOMPtr<nsIURI> srcUri; |
1957 | 0 | rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding, baseUri); |
1958 | 0 | if (NS_SUCCEEDED(rv)) { |
1959 | 0 | nsAutoCString src; |
1960 | 0 | srcUri->GetSpec(src); |
1961 | 0 | soundUrl = NS_ConvertUTF8toUTF16(src); |
1962 | 0 | } |
1963 | 0 | } |
1964 | 0 | } |
1965 | 0 |
|
1966 | 0 | return rv; |
1967 | 0 | } |
1968 | | |
1969 | | already_AddRefed<Promise> |
1970 | | Notification::Get(nsPIDOMWindowInner* aWindow, |
1971 | | const GetNotificationOptions& aFilter, |
1972 | | const nsAString& aScope, |
1973 | | ErrorResult& aRv) |
1974 | 0 | { |
1975 | 0 | AssertIsOnMainThread(); |
1976 | 0 | MOZ_ASSERT(aWindow); |
1977 | 0 |
|
1978 | 0 | nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc(); |
1979 | 0 | if (!doc) { |
1980 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
1981 | 0 | return nullptr; |
1982 | 0 | } |
1983 | 0 | |
1984 | 0 | nsString origin; |
1985 | 0 | aRv = GetOrigin(doc->NodePrincipal(), origin); |
1986 | 0 | if (aRv.Failed()) { |
1987 | 0 | return nullptr; |
1988 | 0 | } |
1989 | 0 | |
1990 | 0 | RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv); |
1991 | 0 | if (aRv.Failed()) { |
1992 | 0 | return nullptr; |
1993 | 0 | } |
1994 | 0 | |
1995 | 0 | nsCOMPtr<nsINotificationStorageCallback> callback = |
1996 | 0 | new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise); |
1997 | 0 |
|
1998 | 0 | RefPtr<NotificationGetRunnable> r = |
1999 | 0 | new NotificationGetRunnable(origin, aFilter.mTag, callback); |
2000 | 0 |
|
2001 | 0 | aRv = aWindow->AsGlobal()->Dispatch(TaskCategory::Other, r.forget()); |
2002 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2003 | 0 | return nullptr; |
2004 | 0 | } |
2005 | 0 | |
2006 | 0 | return promise.forget(); |
2007 | 0 | } |
2008 | | |
2009 | | already_AddRefed<Promise> |
2010 | | Notification::Get(const GlobalObject& aGlobal, |
2011 | | const GetNotificationOptions& aFilter, |
2012 | | ErrorResult& aRv) |
2013 | 0 | { |
2014 | 0 | AssertIsOnMainThread(); |
2015 | 0 | nsCOMPtr<nsIGlobalObject> global = |
2016 | 0 | do_QueryInterface(aGlobal.GetAsSupports()); |
2017 | 0 | MOZ_ASSERT(global); |
2018 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); |
2019 | 0 |
|
2020 | 0 | return Get(window, aFilter, EmptyString(), aRv); |
2021 | 0 | } |
2022 | | |
2023 | | class WorkerGetResultRunnable final : public NotificationWorkerRunnable |
2024 | | { |
2025 | | RefPtr<PromiseWorkerProxy> mPromiseProxy; |
2026 | | const nsTArray<NotificationStrings> mStrings; |
2027 | | public: |
2028 | | WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate, |
2029 | | PromiseWorkerProxy* aPromiseProxy, |
2030 | | const nsTArray<NotificationStrings>&& aStrings) |
2031 | | : NotificationWorkerRunnable(aWorkerPrivate) |
2032 | | , mPromiseProxy(aPromiseProxy) |
2033 | | , mStrings(std::move(aStrings)) |
2034 | 0 | { |
2035 | 0 | } |
2036 | | |
2037 | | void |
2038 | | WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override |
2039 | 0 | { |
2040 | 0 | RefPtr<Promise> workerPromise = mPromiseProxy->WorkerPromise(); |
2041 | 0 |
|
2042 | 0 | ErrorResult result; |
2043 | 0 | AutoTArray<RefPtr<Notification>, 5> notifications; |
2044 | 0 | for (uint32_t i = 0; i < mStrings.Length(); ++i) { |
2045 | 0 | RefPtr<Notification> n = |
2046 | 0 | Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(), |
2047 | 0 | mStrings[i].mID, |
2048 | 0 | mStrings[i].mTitle, |
2049 | 0 | mStrings[i].mDir, |
2050 | 0 | mStrings[i].mLang, |
2051 | 0 | mStrings[i].mBody, |
2052 | 0 | mStrings[i].mTag, |
2053 | 0 | mStrings[i].mIcon, |
2054 | 0 | mStrings[i].mData, |
2055 | 0 | /* mStrings[i].mBehavior, not |
2056 | 0 | * supported */ |
2057 | 0 | mStrings[i].mServiceWorkerRegistrationScope, |
2058 | 0 | result); |
2059 | 0 |
|
2060 | 0 | n->SetStoredState(true); |
2061 | 0 | Unused << NS_WARN_IF(result.Failed()); |
2062 | 0 | if (!result.Failed()) { |
2063 | 0 | notifications.AppendElement(n.forget()); |
2064 | 0 | } |
2065 | 0 | } |
2066 | 0 |
|
2067 | 0 | workerPromise->MaybeResolve(notifications); |
2068 | 0 | mPromiseProxy->CleanUp(); |
2069 | 0 | } |
2070 | | }; |
2071 | | |
2072 | | class WorkerGetCallback final : public ScopeCheckingGetCallback |
2073 | | { |
2074 | | RefPtr<PromiseWorkerProxy> mPromiseProxy; |
2075 | | public: |
2076 | | NS_DECL_ISUPPORTS |
2077 | | |
2078 | | WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope) |
2079 | | : ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) |
2080 | 0 | { |
2081 | 0 | AssertIsOnMainThread(); |
2082 | 0 | MOZ_ASSERT(aProxy); |
2083 | 0 | } |
2084 | | |
2085 | | NS_IMETHOD Done() final |
2086 | 0 | { |
2087 | 0 | AssertIsOnMainThread(); |
2088 | 0 | MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?"); |
2089 | 0 |
|
2090 | 0 | RefPtr<PromiseWorkerProxy> proxy = mPromiseProxy.forget(); |
2091 | 0 | MutexAutoLock lock(proxy->Lock()); |
2092 | 0 | if (proxy->CleanedUp()) { |
2093 | 0 | return NS_OK; |
2094 | 0 | } |
2095 | 0 | |
2096 | 0 | RefPtr<WorkerGetResultRunnable> r = |
2097 | 0 | new WorkerGetResultRunnable(proxy->GetWorkerPrivate(), |
2098 | 0 | proxy, |
2099 | 0 | std::move(mStrings)); |
2100 | 0 |
|
2101 | 0 | r->Dispatch(); |
2102 | 0 | return NS_OK; |
2103 | 0 | } |
2104 | | |
2105 | | private: |
2106 | | ~WorkerGetCallback() |
2107 | 0 | {} |
2108 | | }; |
2109 | | |
2110 | | NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback) |
2111 | | |
2112 | | class WorkerGetRunnable final : public Runnable |
2113 | | { |
2114 | | RefPtr<PromiseWorkerProxy> mPromiseProxy; |
2115 | | const nsString mTag; |
2116 | | const nsString mScope; |
2117 | | public: |
2118 | | WorkerGetRunnable(PromiseWorkerProxy* aProxy, |
2119 | | const nsAString& aTag, |
2120 | | const nsAString& aScope) |
2121 | | : Runnable("WorkerGetRunnable") |
2122 | | , mPromiseProxy(aProxy), mTag(aTag), mScope(aScope) |
2123 | 0 | { |
2124 | 0 | MOZ_ASSERT(mPromiseProxy); |
2125 | 0 | } |
2126 | | |
2127 | | NS_IMETHOD |
2128 | | Run() override |
2129 | 0 | { |
2130 | 0 | AssertIsOnMainThread(); |
2131 | 0 | nsCOMPtr<nsINotificationStorageCallback> callback = |
2132 | 0 | new WorkerGetCallback(mPromiseProxy, mScope); |
2133 | 0 |
|
2134 | 0 | nsresult rv; |
2135 | 0 | nsCOMPtr<nsINotificationStorage> notificationStorage = |
2136 | 0 | do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); |
2137 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2138 | 0 | callback->Done(); |
2139 | 0 | return rv; |
2140 | 0 | } |
2141 | 0 | |
2142 | 0 | MutexAutoLock lock(mPromiseProxy->Lock()); |
2143 | 0 | if (mPromiseProxy->CleanedUp()) { |
2144 | 0 | return NS_OK; |
2145 | 0 | } |
2146 | 0 | |
2147 | 0 | nsString origin; |
2148 | 0 | rv = |
2149 | 0 | Notification::GetOrigin(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), |
2150 | 0 | origin); |
2151 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2152 | 0 | callback->Done(); |
2153 | 0 | return rv; |
2154 | 0 | } |
2155 | 0 | |
2156 | 0 | rv = notificationStorage->Get(origin, mTag, callback); |
2157 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2158 | 0 | callback->Done(); |
2159 | 0 | return rv; |
2160 | 0 | } |
2161 | 0 | |
2162 | 0 | return NS_OK; |
2163 | 0 | } |
2164 | | private: |
2165 | | ~WorkerGetRunnable() |
2166 | 0 | {} |
2167 | | }; |
2168 | | |
2169 | | // static |
2170 | | already_AddRefed<Promise> |
2171 | | Notification::WorkerGet(WorkerPrivate* aWorkerPrivate, |
2172 | | const GetNotificationOptions& aFilter, |
2173 | | const nsAString& aScope, |
2174 | | ErrorResult& aRv) |
2175 | 0 | { |
2176 | 0 | MOZ_ASSERT(aWorkerPrivate); |
2177 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
2178 | 0 | RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv); |
2179 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2180 | 0 | return nullptr; |
2181 | 0 | } |
2182 | 0 | |
2183 | 0 | RefPtr<PromiseWorkerProxy> proxy = |
2184 | 0 | PromiseWorkerProxy::Create(aWorkerPrivate, p); |
2185 | 0 | if (!proxy) { |
2186 | 0 | aRv.Throw(NS_ERROR_DOM_ABORT_ERR); |
2187 | 0 | return nullptr; |
2188 | 0 | } |
2189 | 0 | |
2190 | 0 | RefPtr<WorkerGetRunnable> r = |
2191 | 0 | new WorkerGetRunnable(proxy, aFilter.mTag, aScope); |
2192 | 0 | // Since this is called from script via |
2193 | 0 | // ServiceWorkerRegistration::GetNotifications, we can assert dispatch. |
2194 | 0 | MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget())); |
2195 | 0 | return p.forget(); |
2196 | 0 | } |
2197 | | |
2198 | | JSObject* |
2199 | | Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
2200 | 0 | { |
2201 | 0 | return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto); |
2202 | 0 | } |
2203 | | |
2204 | | void |
2205 | | Notification::Close() |
2206 | 0 | { |
2207 | 0 | AssertIsOnTargetThread(); |
2208 | 0 | auto ref = MakeUnique<NotificationRef>(this); |
2209 | 0 | if (!ref->Initialized()) { |
2210 | 0 | return; |
2211 | 0 | } |
2212 | 0 | |
2213 | 0 | nsCOMPtr<nsIRunnable> closeNotificationTask = |
2214 | 0 | new NotificationTask("Notification::Close", std::move(ref), |
2215 | 0 | NotificationTask::eClose); |
2216 | 0 | nsresult rv = DispatchToMainThread(closeNotificationTask.forget()); |
2217 | 0 |
|
2218 | 0 | if (NS_FAILED(rv)) { |
2219 | 0 | DispatchTrustedEvent(NS_LITERAL_STRING("error")); |
2220 | 0 | // If dispatch fails, NotificationTask will release the ref when it goes |
2221 | 0 | // out of scope at the end of this function. |
2222 | 0 | } |
2223 | 0 | } |
2224 | | |
2225 | | void |
2226 | | Notification::CloseInternal() |
2227 | 0 | { |
2228 | 0 | AssertIsOnMainThread(); |
2229 | 0 | // Transfer ownership (if any) to local scope so we can release it at the end |
2230 | 0 | // of this function. This is relevant when the call is from |
2231 | 0 | // NotificationTask::Run(). |
2232 | 0 | UniquePtr<NotificationRef> ownership; |
2233 | 0 | mozilla::Swap(ownership, mTempRef); |
2234 | 0 |
|
2235 | 0 | SetAlertName(); |
2236 | 0 | UnpersistNotification(); |
2237 | 0 | if (!mIsClosed) { |
2238 | 0 | nsCOMPtr<nsIAlertsService> alertService = |
2239 | 0 | do_GetService(NS_ALERTSERVICE_CONTRACTID); |
2240 | 0 | if (alertService) { |
2241 | 0 | nsAutoString alertName; |
2242 | 0 | GetAlertName(alertName); |
2243 | 0 | alertService->CloseAlert(alertName, GetPrincipal()); |
2244 | 0 | } |
2245 | 0 | } |
2246 | 0 | } |
2247 | | |
2248 | | nsresult |
2249 | | Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) |
2250 | 0 | { |
2251 | 0 | if (!aPrincipal) { |
2252 | 0 | return NS_ERROR_FAILURE; |
2253 | 0 | } |
2254 | 0 | |
2255 | 0 | nsresult rv = nsContentUtils::GetUTFOrigin(aPrincipal, aOrigin); |
2256 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2257 | 0 |
|
2258 | 0 | return NS_OK; |
2259 | 0 | } |
2260 | | |
2261 | | bool |
2262 | | Notification::RequireInteraction() const |
2263 | 0 | { |
2264 | 0 | return mRequireInteraction; |
2265 | 0 | } |
2266 | | |
2267 | | void |
2268 | | Notification::GetData(JSContext* aCx, |
2269 | | JS::MutableHandle<JS::Value> aRetval) |
2270 | 0 | { |
2271 | 0 | if (mData.isNull() && !mDataAsBase64.IsEmpty()) { |
2272 | 0 | nsresult rv; |
2273 | 0 | RefPtr<nsStructuredCloneContainer> container = |
2274 | 0 | new nsStructuredCloneContainer(); |
2275 | 0 | rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION); |
2276 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2277 | 0 | aRetval.setNull(); |
2278 | 0 | return; |
2279 | 0 | } |
2280 | 0 | |
2281 | 0 | JS::Rooted<JS::Value> data(aCx); |
2282 | 0 | rv = container->DeserializeToJsval(aCx, &data); |
2283 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2284 | 0 | aRetval.setNull(); |
2285 | 0 | return; |
2286 | 0 | } |
2287 | 0 | |
2288 | 0 | if (data.isGCThing()) { |
2289 | 0 | mozilla::HoldJSObjects(this); |
2290 | 0 | } |
2291 | 0 | mData = data; |
2292 | 0 | } |
2293 | 0 | if (mData.isNull()) { |
2294 | 0 | aRetval.setNull(); |
2295 | 0 | return; |
2296 | 0 | } |
2297 | 0 | |
2298 | 0 | aRetval.set(mData); |
2299 | 0 | } |
2300 | | |
2301 | | void |
2302 | | Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData, |
2303 | | ErrorResult& aRv) |
2304 | 0 | { |
2305 | 0 | if (!mDataAsBase64.IsEmpty() || aData.isNull()) { |
2306 | 0 | return; |
2307 | 0 | } |
2308 | 0 | RefPtr<nsStructuredCloneContainer> dataObjectContainer = |
2309 | 0 | new nsStructuredCloneContainer(); |
2310 | 0 | aRv = dataObjectContainer->InitFromJSVal(aData, aCx); |
2311 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2312 | 0 | return; |
2313 | 0 | } |
2314 | 0 | |
2315 | 0 | aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64); |
2316 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2317 | 0 | return; |
2318 | 0 | } |
2319 | 0 | } |
2320 | | |
2321 | | void Notification::InitFromBase64(const nsAString& aData, ErrorResult& aRv) |
2322 | 0 | { |
2323 | 0 | if (!mDataAsBase64.IsEmpty() || aData.IsEmpty()) { |
2324 | 0 | return; |
2325 | 0 | } |
2326 | 0 | |
2327 | 0 | // To and fro to ensure it is valid base64. |
2328 | 0 | RefPtr<nsStructuredCloneContainer> container = |
2329 | 0 | new nsStructuredCloneContainer(); |
2330 | 0 | aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION); |
2331 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2332 | 0 | return; |
2333 | 0 | } |
2334 | 0 | |
2335 | 0 | aRv = container->GetDataAsBase64(mDataAsBase64); |
2336 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2337 | 0 | return; |
2338 | 0 | } |
2339 | 0 | } |
2340 | | |
2341 | | bool |
2342 | | Notification::AddRefObject() |
2343 | 0 | { |
2344 | 0 | AssertIsOnTargetThread(); |
2345 | 0 | MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerHolder, mTaskCount == 0); |
2346 | 0 | MOZ_ASSERT_IF(mWorkerPrivate && mWorkerHolder, mTaskCount > 0); |
2347 | 0 | if (mWorkerPrivate && !mWorkerHolder) { |
2348 | 0 | if (!RegisterWorkerHolder()) { |
2349 | 0 | return false; |
2350 | 0 | } |
2351 | 0 | } |
2352 | 0 | AddRef(); |
2353 | 0 | ++mTaskCount; |
2354 | 0 | return true; |
2355 | 0 | } |
2356 | | |
2357 | | void |
2358 | | Notification::ReleaseObject() |
2359 | 0 | { |
2360 | 0 | AssertIsOnTargetThread(); |
2361 | 0 | MOZ_ASSERT(mTaskCount > 0); |
2362 | 0 | MOZ_ASSERT_IF(mWorkerPrivate, mWorkerHolder); |
2363 | 0 |
|
2364 | 0 | --mTaskCount; |
2365 | 0 | if (mWorkerPrivate && mTaskCount == 0) { |
2366 | 0 | UnregisterWorkerHolder(); |
2367 | 0 | } |
2368 | 0 | Release(); |
2369 | 0 | } |
2370 | | |
2371 | | NotificationWorkerHolder::NotificationWorkerHolder(Notification* aNotification) |
2372 | | : WorkerHolder("NotificationWorkerHolder") |
2373 | | , mNotification(aNotification) |
2374 | 0 | { |
2375 | 0 | MOZ_ASSERT(mNotification->mWorkerPrivate); |
2376 | 0 | mNotification->mWorkerPrivate->AssertIsOnWorkerThread(); |
2377 | 0 | } |
2378 | | |
2379 | | /* |
2380 | | * Called from the worker, runs on main thread, blocks worker. |
2381 | | * |
2382 | | * We can freely access mNotification here because the feature supplied it and |
2383 | | * the Notification owns the feature. |
2384 | | */ |
2385 | | class CloseNotificationRunnable final |
2386 | | : public WorkerMainThreadRunnable |
2387 | | { |
2388 | | Notification* mNotification; |
2389 | | bool mHadObserver; |
2390 | | |
2391 | | public: |
2392 | | explicit CloseNotificationRunnable(Notification* aNotification) |
2393 | | : WorkerMainThreadRunnable(aNotification->mWorkerPrivate, |
2394 | | NS_LITERAL_CSTRING("Notification :: Close Notification")) |
2395 | | , mNotification(aNotification) |
2396 | | , mHadObserver(false) |
2397 | 0 | {} |
2398 | | |
2399 | | bool |
2400 | | MainThreadRun() override |
2401 | 0 | { |
2402 | 0 | if (mNotification->mObserver) { |
2403 | 0 | // The Notify() take's responsibility of releasing the Notification. |
2404 | 0 | mNotification->mObserver->ForgetNotification(); |
2405 | 0 | mNotification->mObserver = nullptr; |
2406 | 0 | mHadObserver = true; |
2407 | 0 | } |
2408 | 0 | mNotification->CloseInternal(); |
2409 | 0 | return true; |
2410 | 0 | } |
2411 | | |
2412 | | bool |
2413 | | HadObserver() |
2414 | 0 | { |
2415 | 0 | return mHadObserver; |
2416 | 0 | } |
2417 | | }; |
2418 | | |
2419 | | bool |
2420 | | NotificationWorkerHolder::Notify(WorkerStatus aStatus) |
2421 | 0 | { |
2422 | 0 | if (aStatus >= Canceling) { |
2423 | 0 | // CloseNotificationRunnable blocks the worker by pushing a sync event loop |
2424 | 0 | // on the stack. Meanwhile, WorkerControlRunnables dispatched to the worker |
2425 | 0 | // can still continue running. One of these is |
2426 | 0 | // ReleaseNotificationControlRunnable that releases the notification, |
2427 | 0 | // invalidating the notification and this feature. We hold this reference to |
2428 | 0 | // keep the notification valid until we are done with it. |
2429 | 0 | // |
2430 | 0 | // An example of when the control runnable could get dispatched to the |
2431 | 0 | // worker is if a Notification is created and the worker is immediately |
2432 | 0 | // closed, but there is no permission to show it so that the main thread |
2433 | 0 | // immediately drops the NotificationRef. In this case, this function blocks |
2434 | 0 | // on the main thread, but the main thread dispatches the control runnable, |
2435 | 0 | // invalidating mNotification. |
2436 | 0 | RefPtr<Notification> kungFuDeathGrip = mNotification; |
2437 | 0 |
|
2438 | 0 | // Dispatched to main thread, blocks on closing the Notification. |
2439 | 0 | RefPtr<CloseNotificationRunnable> r = |
2440 | 0 | new CloseNotificationRunnable(kungFuDeathGrip); |
2441 | 0 | ErrorResult rv; |
2442 | 0 | r->Dispatch(Killing, rv); |
2443 | 0 | // XXXbz I'm told throwing and returning false from here is pointless (and |
2444 | 0 | // also that doing sync stuff from here is really weird), so I guess we just |
2445 | 0 | // suppress the exception on rv, if any. |
2446 | 0 | rv.SuppressException(); |
2447 | 0 |
|
2448 | 0 | // Only call ReleaseObject() to match the observer's NotificationRef |
2449 | 0 | // ownership (since CloseNotificationRunnable asked the observer to drop the |
2450 | 0 | // reference to the notification). |
2451 | 0 | if (r->HadObserver()) { |
2452 | 0 | kungFuDeathGrip->ReleaseObject(); |
2453 | 0 | } |
2454 | 0 |
|
2455 | 0 | // From this point we cannot touch properties of this feature because |
2456 | 0 | // ReleaseObject() may have led to the notification going away and the |
2457 | 0 | // notification owns this feature! |
2458 | 0 | } |
2459 | 0 | return true; |
2460 | 0 | } |
2461 | | |
2462 | | bool |
2463 | | Notification::RegisterWorkerHolder() |
2464 | 0 | { |
2465 | 0 | MOZ_ASSERT(mWorkerPrivate); |
2466 | 0 | mWorkerPrivate->AssertIsOnWorkerThread(); |
2467 | 0 | MOZ_ASSERT(!mWorkerHolder); |
2468 | 0 | mWorkerHolder = MakeUnique<NotificationWorkerHolder>(this); |
2469 | 0 | if (NS_WARN_IF(!mWorkerHolder->HoldWorker(mWorkerPrivate, Canceling))) { |
2470 | 0 | return false; |
2471 | 0 | } |
2472 | 0 | |
2473 | 0 | return true; |
2474 | 0 | } |
2475 | | |
2476 | | void |
2477 | | Notification::UnregisterWorkerHolder() |
2478 | 0 | { |
2479 | 0 | MOZ_ASSERT(mWorkerPrivate); |
2480 | 0 | mWorkerPrivate->AssertIsOnWorkerThread(); |
2481 | 0 | MOZ_ASSERT(mWorkerHolder); |
2482 | 0 | mWorkerHolder = nullptr; |
2483 | 0 | } |
2484 | | |
2485 | | /* |
2486 | | * Checks: |
2487 | | * 1) Is aWorker allowed to show a notification for scope? |
2488 | | * 2) Is aWorker an active worker? |
2489 | | * |
2490 | | * If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE. |
2491 | | */ |
2492 | | class CheckLoadRunnable final : public WorkerMainThreadRunnable |
2493 | | { |
2494 | | nsresult mRv; |
2495 | | nsCString mScope; |
2496 | | |
2497 | | public: |
2498 | | explicit CheckLoadRunnable(WorkerPrivate* aWorker, const nsACString& aScope) |
2499 | | : WorkerMainThreadRunnable(aWorker, |
2500 | | NS_LITERAL_CSTRING("Notification :: Check Load")) |
2501 | | , mRv(NS_ERROR_DOM_SECURITY_ERR) |
2502 | | , mScope(aScope) |
2503 | 0 | { } |
2504 | | |
2505 | | bool |
2506 | | MainThreadRun() override |
2507 | 0 | { |
2508 | 0 | nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); |
2509 | 0 | mRv = CheckScope(principal, mScope); |
2510 | 0 |
|
2511 | 0 | if (NS_FAILED(mRv)) { |
2512 | 0 | return true; |
2513 | 0 | } |
2514 | 0 | |
2515 | 0 | RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); |
2516 | 0 | if (!swm) { |
2517 | 0 | // browser shutdown began |
2518 | 0 | mRv = NS_ERROR_FAILURE; |
2519 | 0 | return true; |
2520 | 0 | } |
2521 | 0 | |
2522 | 0 | RefPtr<ServiceWorkerRegistrationInfo> registration = |
2523 | 0 | swm->GetRegistration(principal, mScope); |
2524 | 0 |
|
2525 | 0 | // This is coming from a ServiceWorkerRegistration. |
2526 | 0 | MOZ_ASSERT(registration); |
2527 | 0 |
|
2528 | 0 | if (!registration->GetActive() || |
2529 | 0 | registration->GetActive()->ID() != mWorkerPrivate->ServiceWorkerID()) { |
2530 | 0 | mRv = NS_ERROR_NOT_AVAILABLE; |
2531 | 0 | } |
2532 | 0 |
|
2533 | 0 | return true; |
2534 | 0 | } |
2535 | | |
2536 | | nsresult |
2537 | | Result() |
2538 | 0 | { |
2539 | 0 | return mRv; |
2540 | 0 | } |
2541 | | |
2542 | | }; |
2543 | | |
2544 | | /* static */ |
2545 | | already_AddRefed<Promise> |
2546 | | Notification::ShowPersistentNotification(JSContext* aCx, |
2547 | | nsIGlobalObject *aGlobal, |
2548 | | const nsAString& aScope, |
2549 | | const nsAString& aTitle, |
2550 | | const NotificationOptions& aOptions, |
2551 | | ErrorResult& aRv) |
2552 | 0 | { |
2553 | 0 | MOZ_ASSERT(aGlobal); |
2554 | 0 |
|
2555 | 0 | // Validate scope. |
2556 | 0 | // XXXnsm: This may be slow due to blocking the worker and waiting on the main |
2557 | 0 | // thread. On calls from content, we can be sure the scope is valid since |
2558 | 0 | // ServiceWorkerRegistrations have their scope set correctly. Can this be made |
2559 | 0 | // debug only? The problem is that there would be different semantics in |
2560 | 0 | // debug and non-debug builds in such a case. |
2561 | 0 | if (NS_IsMainThread()) { |
2562 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal); |
2563 | 0 | if (NS_WARN_IF(!sop)) { |
2564 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
2565 | 0 | return nullptr; |
2566 | 0 | } |
2567 | 0 | |
2568 | 0 | nsIPrincipal* principal = sop->GetPrincipal(); |
2569 | 0 | if (NS_WARN_IF(!principal)) { |
2570 | 0 | aRv.Throw(NS_ERROR_UNEXPECTED); |
2571 | 0 | return nullptr; |
2572 | 0 | } |
2573 | 0 | |
2574 | 0 | aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope)); |
2575 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2576 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
2577 | 0 | return nullptr; |
2578 | 0 | } |
2579 | 0 | } else { |
2580 | 0 | WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
2581 | 0 | MOZ_ASSERT(worker); |
2582 | 0 | worker->AssertIsOnWorkerThread(); |
2583 | 0 | RefPtr<CheckLoadRunnable> loadChecker = |
2584 | 0 | new CheckLoadRunnable(worker, NS_ConvertUTF16toUTF8(aScope)); |
2585 | 0 | loadChecker->Dispatch(Canceling, aRv); |
2586 | 0 | if (aRv.Failed()) { |
2587 | 0 | return nullptr; |
2588 | 0 | } |
2589 | 0 | |
2590 | 0 | if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) { |
2591 | 0 | if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) { |
2592 | 0 | aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(aScope); |
2593 | 0 | } else { |
2594 | 0 | aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
2595 | 0 | } |
2596 | 0 | return nullptr; |
2597 | 0 | } |
2598 | 0 | } |
2599 | 0 |
|
2600 | 0 |
|
2601 | 0 | RefPtr<Promise> p = Promise::Create(aGlobal, aRv); |
2602 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2603 | 0 | return nullptr; |
2604 | 0 | } |
2605 | 0 | |
2606 | 0 | // We check permission here rather than pass the Promise to NotificationTask |
2607 | 0 | // which leads to uglier code. |
2608 | 0 | NotificationPermission permission = GetPermission(aGlobal, aRv); |
2609 | 0 |
|
2610 | 0 | // "If permission for notification's origin is not "granted", reject promise with a TypeError exception, and terminate these substeps." |
2611 | 0 | if (NS_WARN_IF(aRv.Failed()) || permission == NotificationPermission::Denied) { |
2612 | 0 | ErrorResult result; |
2613 | 0 | result.ThrowTypeError<MSG_NOTIFICATION_PERMISSION_DENIED>(); |
2614 | 0 | p->MaybeReject(result); |
2615 | 0 | return p.forget(); |
2616 | 0 | } |
2617 | 0 | |
2618 | 0 | // "Otherwise, resolve promise with undefined." |
2619 | 0 | // The Notification may still not be shown due to other errors, but the spec |
2620 | 0 | // is not concerned with those. |
2621 | 0 | p->MaybeResolveWithUndefined(); |
2622 | 0 |
|
2623 | 0 | RefPtr<Notification> notification = |
2624 | 0 | CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv); |
2625 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2626 | 0 | return nullptr; |
2627 | 0 | } |
2628 | 0 | |
2629 | 0 | return p.forget(); |
2630 | 0 | } |
2631 | | |
2632 | | /* static */ already_AddRefed<Notification> |
2633 | | Notification::CreateAndShow(JSContext* aCx, |
2634 | | nsIGlobalObject* aGlobal, |
2635 | | const nsAString& aTitle, |
2636 | | const NotificationOptions& aOptions, |
2637 | | const nsAString& aScope, |
2638 | | ErrorResult& aRv) |
2639 | 0 | { |
2640 | 0 | MOZ_ASSERT(aGlobal); |
2641 | 0 |
|
2642 | 0 | RefPtr<Notification> notification = CreateInternal(aGlobal, EmptyString(), |
2643 | 0 | aTitle, aOptions); |
2644 | 0 |
|
2645 | 0 | // Make a structured clone of the aOptions.mData object |
2646 | 0 | JS::Rooted<JS::Value> data(aCx, aOptions.mData); |
2647 | 0 | notification->InitFromJSVal(aCx, data, aRv); |
2648 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
2649 | 0 | return nullptr; |
2650 | 0 | } |
2651 | 0 | |
2652 | 0 | notification->SetScope(aScope); |
2653 | 0 |
|
2654 | 0 | auto ref = MakeUnique<NotificationRef>(notification); |
2655 | 0 | if (NS_WARN_IF(!ref->Initialized())) { |
2656 | 0 | aRv.Throw(NS_ERROR_DOM_ABORT_ERR); |
2657 | 0 | return nullptr; |
2658 | 0 | } |
2659 | 0 | |
2660 | 0 | // Queue a task to show the notification. |
2661 | 0 | nsCOMPtr<nsIRunnable> showNotificationTask = |
2662 | 0 | new NotificationTask("Notification::CreateAndShow", std::move(ref), |
2663 | 0 | NotificationTask::eShow); |
2664 | 0 |
|
2665 | 0 | nsresult rv = |
2666 | 0 | notification->DispatchToMainThread(showNotificationTask.forget()); |
2667 | 0 |
|
2668 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2669 | 0 | notification->DispatchTrustedEvent(NS_LITERAL_STRING("error")); |
2670 | 0 | } |
2671 | 0 |
|
2672 | 0 | return notification.forget(); |
2673 | 0 | } |
2674 | | |
2675 | | /* static */ nsresult |
2676 | | Notification::RemovePermission(nsIPrincipal* aPrincipal) |
2677 | 0 | { |
2678 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
2679 | 0 | nsCOMPtr<nsIPermissionManager> permissionManager = |
2680 | 0 | mozilla::services::GetPermissionManager(); |
2681 | 0 | if (!permissionManager) { |
2682 | 0 | return NS_ERROR_FAILURE; |
2683 | 0 | } |
2684 | 0 | permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"); |
2685 | 0 | return NS_OK; |
2686 | 0 | } |
2687 | | |
2688 | | /* static */ nsresult |
2689 | | Notification::OpenSettings(nsIPrincipal* aPrincipal) |
2690 | 0 | { |
2691 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
2692 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
2693 | 0 | if (!obs) { |
2694 | 0 | return NS_ERROR_FAILURE; |
2695 | 0 | } |
2696 | 0 | // Notify other observers so they can show settings UI. |
2697 | 0 | obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr); |
2698 | 0 | return NS_OK; |
2699 | 0 | } |
2700 | | |
2701 | | NS_IMETHODIMP |
2702 | | Notification::Observe(nsISupports* aSubject, const char* aTopic, |
2703 | | const char16_t* aData) |
2704 | 0 | { |
2705 | 0 | AssertIsOnMainThread(); |
2706 | 0 |
|
2707 | 0 | if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) || |
2708 | 0 | !strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) { |
2709 | 0 |
|
2710 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = GetOwner(); |
2711 | 0 | if (SameCOMIdentity(aSubject, window)) { |
2712 | 0 | nsCOMPtr<nsIObserverService> obs = |
2713 | 0 | mozilla::services::GetObserverService(); |
2714 | 0 | if (obs) { |
2715 | 0 | obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); |
2716 | 0 | obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); |
2717 | 0 | } |
2718 | 0 |
|
2719 | 0 | CloseInternal(); |
2720 | 0 | } |
2721 | 0 | } |
2722 | 0 |
|
2723 | 0 | return NS_OK; |
2724 | 0 | } |
2725 | | |
2726 | | nsresult |
2727 | | Notification::DispatchToMainThread(already_AddRefed<nsIRunnable>&& aRunnable) |
2728 | 0 | { |
2729 | 0 | if (mWorkerPrivate) { |
2730 | 0 | return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable)); |
2731 | 0 | } |
2732 | 0 | AssertIsOnMainThread(); |
2733 | 0 | if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) { |
2734 | 0 | if (nsIEventTarget* target = global->EventTargetFor(TaskCategory::Other)) { |
2735 | 0 | return target->Dispatch(std::move(aRunnable), nsIEventTarget::DISPATCH_NORMAL); |
2736 | 0 | } |
2737 | 0 | } |
2738 | 0 | nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget(); |
2739 | 0 | MOZ_ASSERT(mainTarget); |
2740 | 0 | return mainTarget->Dispatch(std::move(aRunnable), nsIEventTarget::DISPATCH_NORMAL); |
2741 | 0 | } |
2742 | | |
2743 | | } // namespace dom |
2744 | | } // namespace mozilla |