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