tor-browser

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

NotificationHandler.cpp (5770B)


      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 file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "NotificationHandler.h"
      8 
      9 #include "NotificationUtils.h"
     10 #include "mozilla/ClearOnShutdown.h"
     11 #include "mozilla/dom/ClientIPCTypes.h"
     12 #include "mozilla/dom/ClientOpenWindowUtils.h"
     13 #include "mozilla/dom/DOMTypes.h"
     14 #include "mozilla/dom/Promise-inl.h"
     15 #include "mozilla/dom/Promise.h"
     16 #include "mozilla/dom/ServiceWorkerManager.h"
     17 #include "mozilla/ipc/PBackgroundSharedTypes.h"
     18 #include "xpcprivate.h"
     19 
     20 namespace mozilla::dom::notification {
     21 
     22 nsresult RespondOnClick(nsIPrincipal* aPrincipal, const nsAString& aScope,
     23                        const IPCNotification& aNotification,
     24                        const nsAString& aActionName) {
     25  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     26  if (!swm) {
     27    return NS_ERROR_FAILURE;
     28  }
     29 
     30  nsAutoCString originSuffix;
     31  MOZ_TRY(aPrincipal->GetOriginSuffix(originSuffix));
     32 
     33  nsresult rv = swm->SendNotificationClickEvent(originSuffix, aScope,
     34                                                aNotification, aActionName);
     35  if (NS_FAILED(rv)) {
     36    // No active service worker, let's do the last resort
     37    // TODO(krosylight): We should prevent entering this path as much as
     38    // possible and ultimately remove this. See bug 1972120.
     39    return OpenWindowFor(aPrincipal);
     40  }
     41  return NS_OK;
     42 }
     43 
     44 nsresult OpenWindowFor(nsIPrincipal* aPrincipal) {
     45  nsAutoCString origin;
     46  MOZ_TRY(aPrincipal->GetOriginNoSuffix(origin));
     47 
     48  // XXX: We should be able to just pass nsIPrincipal directly
     49  mozilla::ipc::PrincipalInfo info{};
     50  MOZ_TRY(PrincipalToPrincipalInfo(aPrincipal, &info));
     51 
     52  (void)ClientOpenWindow(nullptr,
     53                         ClientOpenWindowArgs(info, Nothing(), ""_ns, origin));
     54  return NS_OK;
     55 }
     56 
     57 NS_IMPL_ISUPPORTS(NotificationHandler, nsINotificationHandler);
     58 
     59 StaticRefPtr<NotificationHandler> sHandler;
     60 
     61 already_AddRefed<NotificationHandler> NotificationHandler::GetSingleton() {
     62  if (!sHandler) {
     63    sHandler = new NotificationHandler();
     64    ClearOnShutdown(&sHandler);
     65  }
     66 
     67  return do_AddRef(sHandler);
     68 }
     69 
     70 struct NotificationActionComparator {
     71  bool Equals(const IPCNotificationAction& aAction,
     72              const nsAString& aActionName) const {
     73    return aAction.name() == aActionName;
     74  }
     75 };
     76 
     77 NS_IMETHODIMP NotificationHandler::RespondOnClick(
     78    nsIPrincipal* aPrincipal, const nsAString& aNotificationId,
     79    const nsAString& aActionName, bool aAutoClosed, Promise** aResult) {
     80  if (aPrincipal->IsSystemPrincipal()) {
     81    // This function is only designed for web notifications.
     82    return NS_ERROR_INVALID_ARG;
     83  }
     84 
     85  nsAutoCString origin;
     86  MOZ_TRY(aPrincipal->GetOrigin(origin));
     87  if (!StringBeginsWith(origin, "https://"_ns)) {
     88    // We expect only secure context origins for web notifications.
     89    // (Simple https check is sufficient for this case, as we do not expect
     90    // chrome script nor webextensions to hit this path as they are expected to
     91    // use different APIs that do not involve service workers.)
     92    return NS_ERROR_INVALID_ARG;
     93  }
     94 
     95  bool isPrivate = aPrincipal->GetIsInPrivateBrowsing();
     96  nsCOMPtr<nsINotificationStorage> storage = GetNotificationStorage(isPrivate);
     97 
     98  RefPtr<Promise> promise;
     99  storage->GetById(origin, aNotificationId, getter_AddRefs(promise));
    100 
    101  if (aAutoClosed) {
    102    // The system already closed the notification, let's purge the entry here.
    103    //
    104    // It is guaranteed that Delete will happen only immediately after GetById
    105    // as NotificationDB manages each request with an internal job queue.
    106    //
    107    // XXX(krosylight): We should use AUTF8String for all NotificationStorage
    108    // methods.
    109    storage->Delete(NS_ConvertUTF8toUTF16(origin), aNotificationId);
    110  }
    111 
    112  RefPtr<Promise> result = MOZ_TRY(promise->ThenWithoutCycleCollection(
    113      [actionName = nsString(aActionName), principal = nsCOMPtr(aPrincipal)](
    114          JSContext* aCx, JS::Handle<JS::Value> aValue,
    115          ErrorResult& aRv) mutable -> already_AddRefed<Promise> {
    116        auto tryable = [&]() -> nsresult {
    117          if (aValue.isUndefined()) {
    118            // No storage entry, open a new window as a fallback
    119            return OpenWindowFor(principal);
    120          }
    121 
    122          MOZ_ASSERT(aValue.isObject());
    123          JSObject* obj = &aValue.toObject();
    124 
    125          nsCOMPtr<nsINotificationStorageEntry> entry;
    126          MOZ_TRY(nsXPConnect::XPConnect()->WrapJS(
    127              aCx, obj, NS_GET_IID(nsINotificationStorageEntry),
    128              getter_AddRefs(entry)));
    129          if (!entry) {
    130            return NS_ERROR_FAILURE;
    131          }
    132 
    133          nsAutoString scope;
    134          MOZ_TRY(entry->GetServiceWorkerRegistrationScope(scope));
    135 
    136          IPCNotification notification =
    137              MOZ_TRY(NotificationStorageEntry::ToIPC(*entry));
    138 
    139          if (!actionName.IsEmpty()) {
    140            bool contains = notification.options().actions().Contains(
    141                actionName, NotificationActionComparator());
    142            if (!contains) {
    143              // Invalid action, so pretend it had no action
    144              actionName.Truncate();
    145            }
    146          }
    147 
    148          return notification::RespondOnClick(principal, scope, notification,
    149                                              actionName);
    150        };
    151 
    152        nsresult rv = tryable();
    153        if (NS_FAILED(rv)) {
    154          aRv.Throw(rv);
    155          return nullptr;
    156        }
    157 
    158        return nullptr;
    159      }));
    160 
    161  if (aResult) {
    162    result.forget(aResult);
    163  }
    164 
    165  return NS_OK;
    166 }
    167 
    168 }  // namespace mozilla::dom::notification