tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Notification.cpp (29472B)


      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 <utility>
     10 
     11 #include "Navigator.h"
     12 #include "NotificationUtils.h"
     13 #include "mozilla/HoldDropJSObjects.h"
     14 #include "mozilla/OwningNonNull.h"
     15 #include "mozilla/StaticPrefs_dom.h"
     16 #include "mozilla/UseCounter.h"
     17 #include "mozilla/dom/BindingUtils.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/Promise-inl.h"
     20 #include "mozilla/dom/Promise.h"
     21 #include "mozilla/dom/RootedDictionary.h"
     22 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
     23 #include "mozilla/dom/WorkerRunnable.h"
     24 #include "mozilla/dom/WorkerScope.h"
     25 #include "mozilla/glean/DomNotificationMetrics.h"
     26 #include "mozilla/ipc/BackgroundChild.h"
     27 #include "mozilla/ipc/BackgroundUtils.h"
     28 #include "mozilla/ipc/PBackgroundChild.h"
     29 #include "nsContentPermissionHelper.h"
     30 #include "nsContentUtils.h"
     31 #include "nsGlobalWindowInner.h"
     32 #include "nsIContentPermissionPrompt.h"
     33 #include "nsIScriptError.h"
     34 #include "nsNetUtil.h"
     35 #include "nsStructuredCloneContainer.h"
     36 
     37 namespace mozilla::dom {
     38 
     39 using namespace notification;
     40 
     41 class NotificationPermissionRequest : public ContentPermissionRequestBase,
     42                                      public nsIRunnable,
     43                                      public nsINamed {
     44 public:
     45  NS_DECL_NSIRUNNABLE
     46  NS_DECL_ISUPPORTS_INHERITED
     47  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
     48                                           ContentPermissionRequestBase)
     49 
     50  // nsIContentPermissionRequest
     51  NS_IMETHOD Cancel(void) override;
     52  NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;
     53 
     54  NotificationPermissionRequest(nsIPrincipal* aPrincipal,
     55                                nsIPrincipal* aEffectiveStoragePrincipal,
     56                                nsPIDOMWindowInner* aWindow, Promise* aPromise,
     57                                NotificationPermissionCallback* aCallback)
     58      : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
     59                                     "desktop-notification"_ns),
     60        mEffectiveStoragePrincipal(aEffectiveStoragePrincipal),
     61        mPermission(NotificationPermission::Default),
     62        mPromise(aPromise),
     63        mCallback(aCallback) {
     64    MOZ_ASSERT(aPromise);
     65  }
     66 
     67  NS_IMETHOD GetName(nsACString& aName) override {
     68    aName.AssignLiteral("NotificationPermissionRequest");
     69    return NS_OK;
     70  }
     71 
     72 protected:
     73  ~NotificationPermissionRequest() = default;
     74 
     75  MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
     76  nsresult DispatchResolvePromise();
     77  nsCOMPtr<nsIPrincipal> mEffectiveStoragePrincipal;
     78  NotificationPermission mPermission;
     79  RefPtr<Promise> mPromise;
     80  RefPtr<NotificationPermissionCallback> mCallback;
     81 };
     82 
     83 namespace {
     84 class GetPermissionRunnable final : public WorkerMainThreadRunnable {
     85 public:
     86  explicit GetPermissionRunnable(WorkerPrivate* aWorker,
     87                                 bool aUseRegularPrincipal,
     88                                 PermissionCheckPurpose aPurpose)
     89      : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns),
     90        mUseRegularPrincipal(aUseRegularPrincipal),
     91        mPurpose(aPurpose) {}
     92 
     93  bool MainThreadRun() override {
     94    MOZ_ASSERT(mWorkerRef);
     95    WorkerPrivate* workerPrivate = mWorkerRef->Private();
     96    nsIPrincipal* principal = workerPrivate->GetPrincipal();
     97    nsIPrincipal* effectiveStoragePrincipal =
     98        mUseRegularPrincipal ? principal
     99                             : workerPrivate->GetPartitionedPrincipal();
    100    mPermission =
    101        GetNotificationPermission(principal, effectiveStoragePrincipal,
    102                                  workerPrivate->IsSecureContext(), mPurpose);
    103    return true;
    104  }
    105 
    106  NotificationPermission GetPermission() { return mPermission; }
    107 
    108 private:
    109  NotificationPermission mPermission = NotificationPermission::Denied;
    110  bool mUseRegularPrincipal;
    111  PermissionCheckPurpose mPurpose;
    112 };
    113 
    114 }  // anonymous namespace
    115 
    116 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
    117                                   ContentPermissionRequestBase, mCallback)
    118 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
    119                         ContentPermissionRequestBase)
    120 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
    121                          ContentPermissionRequestBase)
    122 
    123 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
    124    NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
    125    nsINamed)
    126 
    127 NS_IMETHODIMP
    128 NotificationPermissionRequest::Run() {
    129  if (IsNotificationAllowedFor(mPrincipal)) {
    130    mPermission = NotificationPermission::Granted;
    131  } else if (IsNotificationForbiddenFor(
    132                 mPrincipal, mEffectiveStoragePrincipal,
    133                 mWindow->IsSecureContext(),
    134                 PermissionCheckPurpose::PermissionRequest,
    135                 mWindow->GetExtantDoc())) {
    136    mPermission = NotificationPermission::Denied;
    137  } else if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
    138             !mPrincipal->Subsumes(mTopLevelPrincipal)) {
    139    mPermission = NotificationPermission::Denied;
    140  }
    141 
    142  // We can't call ShowPrompt() directly here since our logic for determining
    143  // whether to display a prompt depends on the checks above as well as the
    144  // result of CheckPromptPrefs().  So we have to manually check the prompt
    145  // prefs and decide what to do based on that.
    146  PromptResult pr = CheckPromptPrefs();
    147  switch (pr) {
    148    case PromptResult::Granted:
    149      mPermission = NotificationPermission::Granted;
    150      break;
    151    case PromptResult::Denied:
    152      mPermission = NotificationPermission::Denied;
    153      break;
    154    default:
    155      // ignore
    156      break;
    157  }
    158 
    159  if (!mHasValidTransientUserGestureActivation &&
    160      !StaticPrefs::dom_webnotifications_requireuserinteraction()) {
    161    nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    162    if (doc) {
    163      doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
    164    }
    165  }
    166 
    167  if (mPermission != NotificationPermission::Default) {
    168    return DispatchResolvePromise();
    169  }
    170 
    171  return nsContentPermissionUtils::AskPermission(this, mWindow);
    172 }
    173 
    174 NS_IMETHODIMP
    175 NotificationPermissionRequest::Cancel() {
    176  // `Cancel` is called if the user denied permission or dismissed the
    177  // permission request. To distinguish between the two, we set the
    178  // permission to "default" and query the permission manager in
    179  // `ResolvePromise`.
    180  mPermission = NotificationPermission::Default;
    181  return DispatchResolvePromise();
    182 }
    183 
    184 NS_IMETHODIMP
    185 NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
    186  MOZ_ASSERT(aChoices.isUndefined());
    187 
    188  mPermission = NotificationPermission::Granted;
    189  return DispatchResolvePromise();
    190 }
    191 
    192 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
    193  nsCOMPtr<nsIRunnable> resolver =
    194      NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
    195                        this, &NotificationPermissionRequest::ResolvePromise);
    196  return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget());
    197 }
    198 
    199 nsresult NotificationPermissionRequest::ResolvePromise() {
    200  nsresult rv = NS_OK;
    201  // This will still be "default" if the user dismissed the doorhanger,
    202  // or "denied" otherwise.
    203  if (mPermission == NotificationPermission::Default) {
    204    // When the front-end has decided to deny the permission request
    205    // automatically and we are not handling user input, then log a
    206    // warning in the current document that this happened because
    207    // Notifications require a user gesture.
    208    if (!mHasValidTransientUserGestureActivation &&
    209        StaticPrefs::dom_webnotifications_requireuserinteraction()) {
    210      nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    211      if (doc) {
    212        nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
    213                                        doc, nsContentUtils::eDOM_PROPERTIES,
    214                                        "NotificationsRequireUserGesture");
    215      }
    216    }
    217 
    218    mPermission = GetRawNotificationPermission(mPrincipal);
    219  }
    220  if (mCallback) {
    221    ErrorResult error;
    222    RefPtr<NotificationPermissionCallback> callback(mCallback);
    223    callback->Call(mPermission, error);
    224    rv = error.StealNSResult();
    225  }
    226  mPromise->MaybeResolve(mPermission);
    227  return rv;
    228 }
    229 
    230 // static
    231 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
    232  return StaticPrefs::dom_webnotifications_enabled();
    233 }
    234 
    235 Notification::Notification(nsIGlobalObject* aGlobal,
    236                           const IPCNotification& aIPCNotification,
    237                           const nsAString& aScope)
    238    : DOMEventTargetHelper(aGlobal),
    239      mIPCNotification(aIPCNotification),
    240      mData(JS::NullValue()),
    241      mScope(aScope) {
    242  KeepAliveIfHasListenersFor(nsGkAtoms::onclick);
    243  KeepAliveIfHasListenersFor(nsGkAtoms::onshow);
    244  KeepAliveIfHasListenersFor(nsGkAtoms::onerror);
    245  KeepAliveIfHasListenersFor(nsGkAtoms::onclose);
    246 }
    247 
    248 // May be called on any thread.
    249 // static
    250 already_AddRefed<Notification> Notification::Constructor(
    251    const GlobalObject& aGlobal, const nsAString& aTitle,
    252    const NotificationOptions& aOptions, ErrorResult& aRv) {
    253  // FIXME(nsm): If the sticky flag is set, throw an error.
    254  RefPtr<ServiceWorkerGlobalScope> scope;
    255  UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
    256  if (scope) {
    257    aRv.ThrowTypeError(
    258        "Notification constructor cannot be used in ServiceWorkerGlobalScope. "
    259        "Use registration.showNotification() instead.");
    260    return nullptr;
    261  }
    262 
    263  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    264  RefPtr<Notification> notification = ValidateAndCreate(
    265      aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
    266  if (NS_WARN_IF(aRv.Failed())) {
    267    return nullptr;
    268  }
    269 
    270  RefPtr<Promise> promise = Promise::CreateInfallible(global);
    271  promise->AddCallbacksWithCycleCollectedArgs(
    272      [](JSContext*, JS::Handle<JS::Value>, ErrorResult&,
    273         Notification* aNotification) {
    274        aNotification->DispatchTrustedEvent(u"show"_ns);
    275      },
    276      [](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&,
    277         Notification* aNotification) {
    278        aNotification->DispatchTrustedEvent(u"error"_ns);
    279        aNotification->Deactivate();
    280      },
    281      notification);
    282  if (!notification->CreateActor() || !notification->SendShow(promise)) {
    283    notification->Deactivate();
    284    return nullptr;
    285  }
    286 
    287  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclick);
    288  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onshow);
    289  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onerror);
    290  notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclose);
    291 
    292  return notification.forget();
    293 }
    294 
    295 // NOTE(krosylight): Maybe move this check to the parent process?
    296 Result<Ok, nsresult> ValidateBase64Data(const nsAString& aData) {
    297  if (aData.IsEmpty()) {
    298    return Ok();
    299  }
    300 
    301  // To and from to ensure it is valid base64.
    302  RefPtr<nsStructuredCloneContainer> container =
    303      new nsStructuredCloneContainer();
    304  MOZ_TRY(container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION));
    305 
    306  nsString result;
    307  MOZ_TRY(container->GetDataAsBase64(result));
    308 
    309  return Ok();
    310 }
    311 
    312 // static
    313 Result<already_AddRefed<Notification>, nsresult> Notification::ConstructFromIPC(
    314    nsIGlobalObject* aGlobal, const IPCNotification& aIPCNotification,
    315    const nsAString& aServiceWorkerRegistrationScope) {
    316  MOZ_ASSERT(aGlobal);
    317 
    318  MOZ_TRY(ValidateBase64Data(aIPCNotification.options().dataSerialized()));
    319 
    320  RefPtr<Notification> notification = new Notification(
    321      aGlobal, aIPCNotification, aServiceWorkerRegistrationScope);
    322 
    323  return notification.forget();
    324 }
    325 
    326 void Notification::MaybeNotifyClose() {
    327  if (mIsClosed) {
    328    return;
    329  }
    330  mIsClosed = true;
    331  DispatchTrustedEvent(u"close"_ns);
    332 }
    333 
    334 static Result<nsString, nsresult> SerializeDataAsBase64(
    335    JSContext* aCx, JS::Handle<JS::Value> aData) {
    336  if (aData.isNull()) {
    337    return nsString();
    338  }
    339  RefPtr<nsStructuredCloneContainer> dataObjectContainer =
    340      new nsStructuredCloneContainer();
    341  MOZ_TRY(dataObjectContainer->InitFromJSVal(aData, aCx));
    342 
    343  nsString result;
    344  MOZ_TRY(dataObjectContainer->GetDataAsBase64(result));
    345 
    346  return result;
    347 }
    348 
    349 #define SetUseCounterIf(wasUsed, memberName)                             \
    350  if (wasUsed) {                                                         \
    351    if (NS_IsMainThread()) {                                             \
    352      SetUseCounter(aGlobal->GetGlobalJSObject(),                        \
    353                    eUseCounter_NotificationOptions_##memberName);       \
    354    } else {                                                             \
    355      SetUseCounter(UseCounterWorker::NotificationOptions_##memberName); \
    356    }                                                                    \
    357  }
    358 
    359 /* static */
    360 // https://notifications.spec.whatwg.org/#create-a-notification
    361 already_AddRefed<Notification> Notification::ValidateAndCreate(
    362    JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
    363    const NotificationOptions& aOptions, const nsAString& aScope,
    364    ErrorResult& aRv) {
    365  MOZ_ASSERT(aGlobal);
    366 
    367  SetUseCounterIf(aOptions.mNavigate.WasPassed(), navigate);
    368  SetUseCounterIf(aOptions.mImage.WasPassed(), image);
    369  SetUseCounterIf(aOptions.mBadge.WasPassed(), badge);
    370  SetUseCounterIf(aOptions.mVibrate.WasPassed(), vibrate);
    371  SetUseCounterIf(aOptions.mTimestamp.WasPassed(), timestamp);
    372  SetUseCounterIf(aOptions.mRenotify, renotify);
    373  SetUseCounterIf(aOptions.mRequireInteraction, requireInteraction);
    374  SetUseCounterIf(!aOptions.mActions.IsEmpty(), actions);
    375 
    376  // Step 4: Set notification’s data to
    377  // StructuredSerializeForStorage(options["data"]).
    378  JS::Rooted<JS::Value> data(aCx, aOptions.mData);
    379  Result<nsString, nsresult> dataResult = SerializeDataAsBase64(aCx, data);
    380  if (dataResult.isErr()) {
    381    aRv = dataResult.unwrapErr();
    382    return nullptr;
    383  }
    384 
    385  // Step 17: Set notification’s silent preference to options["silent"].
    386  bool silent = false;
    387  if (StaticPrefs::dom_webnotifications_silent_enabled()) {
    388    silent = aOptions.mSilent;
    389  }
    390 
    391  nsTArray<uint32_t> vibrate;
    392  if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
    393      aOptions.mVibrate.WasPassed()) {
    394    // Step 2: If options["silent"] is true and options["vibrate"] exists, then
    395    // throw a TypeError.
    396    if (silent) {
    397      aRv.ThrowTypeError(
    398          "Silent notifications must not specify vibration patterns.");
    399      return nullptr;
    400    }
    401 
    402    // Step 14: If options["vibrate"] exists, then validate and normalize it and
    403    // set notification’s vibration pattern to the return value.
    404    const OwningUnsignedLongOrUnsignedLongSequence& value =
    405        aOptions.mVibrate.Value();
    406    if (value.IsUnsignedLong()) {
    407      AutoTArray<uint32_t, 1> array;
    408      array.AppendElement(value.GetAsUnsignedLong());
    409      vibrate = SanitizeVibratePattern(array);
    410    } else {
    411      vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence());
    412    }
    413  }
    414 
    415  // Step 12: If options["icon"] exists, then parse it using baseURL, and if
    416  // that does not return failure, set notification’s icon URL to the return
    417  // value. (Otherwise icon URL is not set.)
    418  RefPtr<nsIURI> iconUrl = ResolveIconURL(aGlobal, aOptions.mIcon);
    419 
    420  // Step 19: Set notification’s actions to « ».
    421  nsTArray<IPCNotificationAction> actions;
    422  if (StaticPrefs::dom_webnotifications_actions_enabled()) {
    423    // Step 20: For each entry in options["actions"], up to the maximum number
    424    // of actions supported (skip any excess entries):
    425    for (const auto& entry : aOptions.mActions) {
    426      // Step 20.1: Let action be a new notification action.
    427      IPCNotificationAction action;
    428      // Step 20.2: Set action’s name to entry["action"].
    429      action.name() = entry.mAction;
    430      // Step 20.3: Set action’s title to entry["title"].
    431      action.title() = entry.mTitle;
    432      // Step 20.4: (Skipping icon support, see
    433      // https://github.com/whatwg/notifications/issues/233)
    434      // Step 20.5: Append action to notification’s actions.
    435      actions.AppendElement(std::move(action));
    436      if (actions.Length() == kMaxActions) {
    437        break;
    438      }
    439    }
    440  }
    441 
    442  IPCNotification ipcNotification(
    443      nsString(), IPCNotificationOptions(
    444                      nsString(aTitle), aOptions.mDir, nsString(aOptions.mLang),
    445                      nsString(aOptions.mBody), nsString(aOptions.mTag),
    446                      iconUrl, aOptions.mRequireInteraction, silent, vibrate,
    447                      nsString(dataResult.unwrap()), actions));
    448 
    449  RefPtr<Notification> notification =
    450      new Notification(aGlobal, ipcNotification, aScope);
    451  return notification.forget();
    452 }
    453 
    454 Notification::~Notification() { mozilla::DropJSObjects(this); }
    455 
    456 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
    457 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
    458                                                DOMEventTargetHelper)
    459  tmp->mData.setUndefined();
    460  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
    461 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    462 
    463 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
    464                                                  DOMEventTargetHelper)
    465 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    466 
    467 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
    468                                               DOMEventTargetHelper)
    469  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
    470 NS_IMPL_CYCLE_COLLECTION_TRACE_END
    471 
    472 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
    473 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
    474 
    475 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
    476 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    477 
    478 /* static */
    479 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx,
    480                                                    JSObject* /* unused */) {
    481  // requestPermission() is not allowed on workers. The calling page should ask
    482  // for permission on the worker's behalf. This is to prevent 'which window
    483  // should show the browser pop-up'. See discussion:
    484  // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html
    485  return NS_IsMainThread();
    486 }
    487 
    488 // static
    489 already_AddRefed<Promise> Notification::RequestPermission(
    490    const GlobalObject& aGlobal,
    491    const Optional<OwningNonNull<NotificationPermissionCallback>>& aCallback,
    492    ErrorResult& aRv) {
    493  AssertIsOnMainThread();
    494 
    495  // Get principal from global to make permission request for notifications.
    496  nsCOMPtr<nsPIDOMWindowInner> window =
    497      do_QueryInterface(aGlobal.GetAsSupports());
    498  nsCOMPtr<nsIScriptObjectPrincipal> sop =
    499      do_QueryInterface(aGlobal.GetAsSupports());
    500  if (!sop || !window) {
    501    aRv.Throw(NS_ERROR_UNEXPECTED);
    502    return nullptr;
    503  }
    504 
    505  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
    506  nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal =
    507      sop->GetEffectiveStoragePrincipal();
    508  if (!principal || !effectiveStoragePrincipal) {
    509    aRv.Throw(NS_ERROR_UNEXPECTED);
    510    return nullptr;
    511  }
    512 
    513  RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
    514  if (aRv.Failed()) {
    515    return nullptr;
    516  }
    517  NotificationPermissionCallback* permissionCallback = nullptr;
    518  if (aCallback.WasPassed()) {
    519    permissionCallback = &aCallback.Value();
    520  }
    521  nsCOMPtr<nsIRunnable> request =
    522      new NotificationPermissionRequest(principal, effectiveStoragePrincipal,
    523                                        window, promise, permissionCallback);
    524 
    525  window->AsGlobal()->Dispatch(request.forget());
    526 
    527  return promise.forget();
    528 }
    529 
    530 // static
    531 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal,
    532                                                   ErrorResult& aRv) {
    533  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
    534  return GetPermission(global, PermissionCheckPurpose::PermissionAttribute,
    535                       aRv);
    536 }
    537 
    538 // static
    539 NotificationPermission Notification::GetPermission(
    540    nsIGlobalObject* aGlobal, PermissionCheckPurpose aPurpose,
    541    ErrorResult& aRv) {
    542  if (NS_IsMainThread()) {
    543    return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aPurpose, aRv);
    544  }
    545 
    546  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
    547  MOZ_ASSERT(worker);
    548  RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(
    549      worker, worker->UseRegularPrincipal(), aPurpose);
    550  r->Dispatch(worker, Canceling, aRv);
    551  if (aRv.Failed()) {
    552    return NotificationPermission::Denied;
    553  }
    554 
    555  return r->GetPermission();
    556 }
    557 
    558 /* static */
    559 NotificationPermission Notification::GetPermissionInternal(
    560    nsPIDOMWindowInner* aWindow, PermissionCheckPurpose aPurpose,
    561    ErrorResult& aRv) {
    562  // Get principal from global to check permission for notifications.
    563  nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
    564  if (!sop) {
    565    aRv.Throw(NS_ERROR_UNEXPECTED);
    566    return NotificationPermission::Denied;
    567  }
    568 
    569  nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
    570  nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal =
    571      sop->GetEffectiveStoragePrincipal();
    572  if (!principal || !effectiveStoragePrincipal) {
    573    aRv.Throw(NS_ERROR_UNEXPECTED);
    574    return NotificationPermission::Denied;
    575  }
    576 
    577  return GetNotificationPermission(principal, effectiveStoragePrincipal,
    578                                   aWindow->IsSecureContext(), aPurpose);
    579 }
    580 
    581 uint32_t Notification::MaxActions(const GlobalObject& aGlobal) {
    582  return kMaxActions;
    583 }
    584 
    585 already_AddRefed<nsIURI> Notification::ResolveIconURL(
    586    nsIGlobalObject* aGlobal, const nsACString& aIconUrl) {
    587  nsresult rv = NS_OK;
    588 
    589  if (aIconUrl.IsEmpty()) {
    590    return nullptr;
    591  }
    592 
    593  nsCOMPtr<nsIURI> baseUri = aGlobal->GetBaseURI();
    594  if (!baseUri) {
    595    return nullptr;
    596  }
    597 
    598  nsCOMPtr<nsIURI> srcUri;
    599  rv = NS_NewURI(getter_AddRefs(srcUri), aIconUrl, nullptr, baseUri);
    600  if (NS_FAILED(rv)) {
    601    return nullptr;
    602  }
    603 
    604  return srcUri.forget();
    605 }
    606 
    607 JSObject* Notification::WrapObject(JSContext* aCx,
    608                                   JS::Handle<JSObject*> aGivenProto) {
    609  return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
    610 }
    611 
    612 void Notification::Close() {
    613  if (mIsClosed) {
    614    return;
    615  }
    616  if (!mActor) {
    617    CreateActor();
    618  }
    619  if (mActor) {
    620    (void)mActor->SendClose();
    621  }
    622 }
    623 
    624 bool Notification::RequireInteraction() const {
    625  return mIPCNotification.options().requireInteraction();
    626 }
    627 
    628 bool Notification::Silent() const {
    629  return mIPCNotification.options().silent();
    630 }
    631 
    632 void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
    633  aRetval = mIPCNotification.options().vibrate().Clone();
    634 }
    635 
    636 void Notification::GetData(JSContext* aCx,
    637                           JS::MutableHandle<JS::Value> aRetval) {
    638  const nsString& dataSerialized = mIPCNotification.options().dataSerialized();
    639  if (mData.isNull() && !dataSerialized.IsEmpty()) {
    640    nsresult rv;
    641    RefPtr<nsStructuredCloneContainer> container =
    642        new nsStructuredCloneContainer();
    643    rv = container->InitFromBase64(dataSerialized, JS_STRUCTURED_CLONE_VERSION);
    644    if (NS_WARN_IF(NS_FAILED(rv))) {
    645      aRetval.setNull();
    646      return;
    647    }
    648 
    649    JS::Rooted<JS::Value> data(aCx);
    650    rv = container->DeserializeToJsval(aCx, &data);
    651    if (NS_WARN_IF(NS_FAILED(rv))) {
    652      aRetval.setNull();
    653      return;
    654    }
    655 
    656    if (data.isGCThing()) {
    657      mozilla::HoldJSObjects(this);
    658    }
    659    mData = data;
    660  }
    661  if (mData.isNull()) {
    662    aRetval.setNull();
    663    return;
    664  }
    665 
    666  aRetval.set(mData);
    667 }
    668 
    669 void Notification::GetActions(nsTArray<NotificationAction>& aRetVal) {
    670  aRetVal.Clear();
    671  for (const IPCNotificationAction& entry :
    672       mIPCNotification.options().actions()) {
    673    RootedDictionary<NotificationAction> action(RootingCx());
    674    action.mAction = entry.name();
    675    action.mTitle = entry.title();
    676    aRetVal.AppendElement(action);
    677  }
    678 }
    679 
    680 // Steps 2-5 of
    681 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification
    682 /* static */
    683 already_AddRefed<Promise> Notification::ShowPersistentNotification(
    684    JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
    685    const nsAString& aTitle, const NotificationOptions& aOptions,
    686    const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) {
    687  MOZ_ASSERT(aGlobal);
    688 
    689  // Step 2: Let promise be a new promise in this’s relevant Realm.
    690  RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
    691  if (NS_WARN_IF(aRv.Failed())) {
    692    return nullptr;
    693  }
    694 
    695  // Step 3: If this’s active worker is null, then reject promise with a
    696  // TypeError and return promise.
    697  if (!aDescriptor.GetActive()) {
    698    aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
    699    return nullptr;
    700  }
    701 
    702  // Step 4: Let notification be the result of creating a notification with a
    703  // settings object given title, options, and this’s relevant settings object.
    704  // If this threw an exception, then reject promise with that exception and
    705  // return promise.
    706  //
    707  // Step 5: Set notification’s service worker registration to this.
    708  //
    709  // Note: We currently use the scope as the unique identifier for the
    710  // registration (and there currently is no durable registration identifier,
    711  // so this is necessary), which is why we pass in the scope.  See
    712  // https://github.com/whatwg/notifications/issues/205 for some scope-related
    713  // discussion.
    714  //
    715  // XXX: We create Notification object almost solely to share the parameter
    716  // normalization steps. It would be nice to export that and skip creating
    717  // object here.
    718  RefPtr<Notification> notification =
    719      ValidateAndCreate(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
    720  if (NS_WARN_IF(aRv.Failed())) {
    721    return nullptr;
    722  }
    723 
    724  if (!notification->CreateActor() || !notification->SendShow(p)) {
    725    return nullptr;
    726  }
    727 
    728  return p.forget();
    729 }
    730 
    731 bool Notification::CreateActor() {
    732  mozilla::ipc::PBackgroundChild* backgroundActor =
    733      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
    734 
    735  // Note: We are not using the typical PBackground managed actor here as we
    736  // want the actor to be in the main thread of the main process. Instead we
    737  // pass the endpoint to PBackground, it dispatches a runnable to the main
    738  // thread, and the endpoint is bound there.
    739 
    740  mozilla::ipc::Endpoint<notification::PNotificationParent> parentEndpoint;
    741  mozilla::ipc::Endpoint<notification::PNotificationChild> childEndpoint;
    742  notification::PNotification::CreateEndpoints(&parentEndpoint, &childEndpoint);
    743 
    744  bool persistent = !mScope.IsEmpty();
    745  RefPtr<nsPIDOMWindowInner> window = GetOwnerWindow();
    746  mActor = new notification::NotificationChild(
    747      persistent ? nullptr : this,
    748      window ? window->GetWindowGlobalChild() : nullptr);
    749 
    750  nsISerialEventTarget* target = nullptr;
    751  nsIPrincipal* principal;
    752  nsIPrincipal* effectiveStoragePrincipal;
    753  bool isSecureContext;
    754 
    755  // TODO: Should get nsIGlobalObject methods for each method
    756  if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
    757    target = workerPrivate->HybridEventTarget();
    758    principal = workerPrivate->GetPrincipal();
    759    effectiveStoragePrincipal = workerPrivate->GetEffectiveStoragePrincipal();
    760    isSecureContext = workerPrivate->IsSecureContext();
    761  } else {
    762    nsGlobalWindowInner* win = GetOwnerWindow();
    763    NS_ENSURE_TRUE(win, false);
    764    principal = win->GetPrincipal();
    765    effectiveStoragePrincipal = win->GetEffectiveStoragePrincipal();
    766    isSecureContext = win->IsSecureContext();
    767  }
    768 
    769  if (!childEndpoint.Bind(mActor, target)) {
    770    return false;
    771  }
    772 
    773  (void)backgroundActor->SendCreateNotificationParent(
    774      std::move(parentEndpoint), WrapNotNull(principal),
    775      WrapNotNull(effectiveStoragePrincipal), isSecureContext, mScope,
    776      mIPCNotification);
    777 
    778  return true;
    779 }
    780 
    781 bool Notification::SendShow(Promise* aPromise) {
    782  mActor->SendShow()->Then(
    783      GetCurrentSerialEventTarget(), __func__,
    784      [self = RefPtr{this}, promise = RefPtr(aPromise)](
    785          notification::PNotificationChild::ShowPromise::ResolveOrRejectValue&&
    786              aResult) {
    787        if (aResult.IsReject()) {
    788          promise->MaybeRejectWithUnknownError("Failed to open notification");
    789          self->Deactivate();
    790          return;
    791        }
    792 
    793        CopyableErrorResult rv = aResult.ResolveValue();
    794        if (rv.Failed()) {
    795          promise->MaybeReject(std::move(rv));
    796          self->Deactivate();
    797          return;
    798        }
    799 
    800        if (promise) {
    801          promise->MaybeResolveWithUndefined();
    802        } else {
    803          self->DispatchTrustedEvent(u"show"_ns);
    804        }
    805      });
    806 
    807  return true;
    808 }
    809 
    810 void Notification::Deactivate() {
    811  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclick);
    812  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onshow);
    813  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onerror);
    814  IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclose);
    815  mIsClosed = true;
    816  if (mActor) {
    817    mActor->Close();
    818    mActor = nullptr;
    819  }
    820 }
    821 
    822 nsresult Notification::DispatchToMainThread(
    823    already_AddRefed<nsIRunnable>&& aRunnable) {
    824  if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
    825    return workerPrivate->DispatchToMainThread(std::move(aRunnable));
    826  }
    827  AssertIsOnMainThread();
    828  return NS_DispatchToCurrentThread(std::move(aRunnable));
    829 }
    830 
    831 }  // namespace mozilla::dom