Coverage Report

Created: 2018-09-25 14:53

/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