tor-browser

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

PushNotifier.cpp (14338B)


      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 "PushNotifier.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/Preferences.h"
     11 #include "mozilla/Services.h"
     12 #include "mozilla/dom/BodyUtil.h"
     13 #include "mozilla/dom/ContentChild.h"
     14 #include "mozilla/dom/ContentParent.h"
     15 #include "mozilla/dom/ServiceWorkerManager.h"
     16 #include "nsCOMPtr.h"
     17 #include "nsContentUtils.h"
     18 #include "nsICategoryManager.h"
     19 #include "nsIPushService.h"
     20 #include "nsIXULRuntime.h"
     21 #include "nsNetUtil.h"
     22 #include "nsXPCOM.h"
     23 
     24 namespace mozilla::dom {
     25 
     26 PushNotifier::PushNotifier() = default;
     27 
     28 PushNotifier::~PushNotifier() = default;
     29 
     30 NS_IMPL_CYCLE_COLLECTION_0(PushNotifier)
     31 
     32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier)
     33  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushNotifier)
     34  NS_INTERFACE_MAP_ENTRY(nsIPushNotifier)
     35 NS_INTERFACE_MAP_END
     36 
     37 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier)
     38 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
     39 
     40 NS_IMETHODIMP
     41 PushNotifier::NotifyPushWithData(const nsACString& aScope,
     42                                 nsIPrincipal* aPrincipal,
     43                                 const nsAString& aMessageId,
     44                                 const nsTArray<uint8_t>& aData) {
     45  NS_ENSURE_ARG(aPrincipal);
     46  // We still need to do this copying business, if we want the copy to be
     47  // fallible.  Just passing Some(aData) would do an infallible copy at the
     48  // point where the Some() call happens.
     49  nsTArray<uint8_t> data;
     50  if (!data.AppendElements(aData, fallible)) {
     51    return NS_ERROR_OUT_OF_MEMORY;
     52  }
     53  PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId,
     54                                   Some(std::move(data)));
     55  return Dispatch(dispatcher);
     56 }
     57 
     58 NS_IMETHODIMP
     59 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
     60                         const nsAString& aMessageId) {
     61  NS_ENSURE_ARG(aPrincipal);
     62  PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
     63  return Dispatch(dispatcher);
     64 }
     65 
     66 NS_IMETHODIMP
     67 PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
     68                                       nsIPrincipal* aPrincipal,
     69                                       nsIPushSubscription* aOldSubscription) {
     70  NS_ENSURE_ARG(aPrincipal);
     71  PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal,
     72                                              aOldSubscription);
     73  return Dispatch(dispatcher);
     74 }
     75 
     76 NS_IMETHODIMP
     77 PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
     78                                         nsIPrincipal* aPrincipal) {
     79  NS_ENSURE_ARG(aPrincipal);
     80  PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
     81  return Dispatch(dispatcher);
     82 }
     83 
     84 NS_IMETHODIMP
     85 PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
     86                          const nsAString& aMessage, uint32_t aFlags) {
     87  NS_ENSURE_ARG(aPrincipal);
     88  PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags);
     89  return Dispatch(dispatcher);
     90 }
     91 
     92 nsresult PushNotifier::Dispatch(PushDispatcher& aDispatcher) {
     93  if (XRE_IsParentProcess()) {
     94    // Always notify XPCOM observers in the parent process.
     95    (void)NS_WARN_IF(NS_FAILED(aDispatcher.NotifyObservers()));
     96 
     97    // e10s is disabled; notify workers in the parent.
     98    return aDispatcher.NotifyWorkers();
     99  }
    100 
    101  // Otherwise, we're in the content process, so e10s must be enabled. Notify
    102  // observers and workers, then send a message to notify observers in the
    103  // parent.
    104  MOZ_ASSERT(XRE_IsContentProcess());
    105 
    106  nsresult rv = aDispatcher.NotifyObserversAndWorkers();
    107 
    108  ContentChild* parentActor = ContentChild::GetSingleton();
    109  if (!NS_WARN_IF(!parentActor)) {
    110    (void)NS_WARN_IF(!aDispatcher.SendToParent(parentActor));
    111  }
    112 
    113  return rv;
    114 }
    115 
    116 PushData::PushData(const nsTArray<uint8_t>& aData) : mData(aData.Clone()) {}
    117 
    118 PushData::~PushData() = default;
    119 
    120 NS_IMPL_CYCLE_COLLECTION_0(PushData)
    121 
    122 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData)
    123  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData)
    124  NS_INTERFACE_MAP_ENTRY(nsIPushData)
    125 NS_INTERFACE_MAP_END
    126 
    127 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData)
    128 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData)
    129 
    130 nsresult PushData::EnsureDecodedText() {
    131  if (mData.IsEmpty() || !mDecodedText.IsEmpty()) {
    132    return NS_OK;
    133  }
    134  nsresult rv = BodyUtil::ConsumeText(
    135      mData.Length(), reinterpret_cast<uint8_t*>(mData.Elements()),
    136      mDecodedText);
    137  if (NS_WARN_IF(NS_FAILED(rv))) {
    138    mDecodedText.Truncate();
    139    return rv;
    140  }
    141  return NS_OK;
    142 }
    143 
    144 NS_IMETHODIMP
    145 PushData::Text(nsAString& aText) {
    146  nsresult rv = EnsureDecodedText();
    147  if (NS_WARN_IF(NS_FAILED(rv))) {
    148    return rv;
    149  }
    150  aText = mDecodedText;
    151  return NS_OK;
    152 }
    153 
    154 NS_IMETHODIMP
    155 PushData::Json(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) {
    156  nsresult rv = EnsureDecodedText();
    157  if (NS_WARN_IF(NS_FAILED(rv))) {
    158    return rv;
    159  }
    160  ErrorResult error;
    161  BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error);
    162  return error.StealNSResult();
    163 }
    164 
    165 NS_IMETHODIMP
    166 PushData::Binary(nsTArray<uint8_t>& aData) {
    167  aData = mData.Clone();
    168  return NS_OK;
    169 }
    170 
    171 PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData)
    172    : mPrincipal(aPrincipal), mData(aData) {}
    173 
    174 PushMessage::~PushMessage() = default;
    175 
    176 NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData)
    177 
    178 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage)
    179  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage)
    180  NS_INTERFACE_MAP_ENTRY(nsIPushMessage)
    181 NS_INTERFACE_MAP_END
    182 
    183 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage)
    184 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage)
    185 
    186 NS_IMETHODIMP
    187 PushMessage::GetPrincipal(nsIPrincipal** aPrincipal) {
    188  NS_ENSURE_ARG_POINTER(aPrincipal);
    189 
    190  nsCOMPtr<nsIPrincipal> principal = mPrincipal;
    191  principal.forget(aPrincipal);
    192  return NS_OK;
    193 }
    194 
    195 NS_IMETHODIMP
    196 PushMessage::GetData(nsIPushData** aData) {
    197  NS_ENSURE_ARG_POINTER(aData);
    198 
    199  nsCOMPtr<nsIPushData> data = mData;
    200  data.forget(aData);
    201  return NS_OK;
    202 }
    203 
    204 PushDispatcher::PushDispatcher(const nsACString& aScope,
    205                               nsIPrincipal* aPrincipal)
    206    : mScope(aScope), mPrincipal(aPrincipal) {}
    207 
    208 PushDispatcher::~PushDispatcher() = default;
    209 
    210 nsresult PushDispatcher::HandleNoChildProcesses() { return NS_OK; }
    211 
    212 nsresult PushDispatcher::NotifyObserversAndWorkers() {
    213  (void)NS_WARN_IF(NS_FAILED(NotifyObservers()));
    214  return NotifyWorkers();
    215 }
    216 
    217 bool PushDispatcher::ShouldNotifyWorkers() {
    218  if (NS_WARN_IF(!mPrincipal)) {
    219    return false;
    220  }
    221 
    222  // System subscriptions use observer notifications instead of service worker
    223  // events. The `testing.notifyWorkers` pref disables worker events for
    224  // non-system subscriptions.
    225  if (mPrincipal->IsSystemPrincipal() ||
    226      !Preferences::GetBool("dom.push.testing.notifyWorkers", true)) {
    227    return false;
    228  }
    229 
    230  // If e10s is off, no need to worry about processes.
    231  if (!BrowserTabsRemoteAutostart()) {
    232    return true;
    233  }
    234 
    235  // We only want to notify in the parent process.
    236  bool isContentProcess = XRE_GetProcessType() == GeckoProcessType_Content;
    237  return !isContentProcess;
    238 }
    239 
    240 nsresult PushDispatcher::DoNotifyObservers(nsISupports* aSubject,
    241                                           const char* aTopic,
    242                                           const nsACString& aScope) {
    243  nsCOMPtr<nsIObserverService> obsService =
    244      mozilla::services::GetObserverService();
    245  if (!obsService) {
    246    return NS_ERROR_FAILURE;
    247  }
    248  // If there's a service for this push category, make sure it is alive.
    249  nsCOMPtr<nsICategoryManager> catMan =
    250      do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
    251  if (catMan) {
    252    nsCString contractId;
    253    nsresult rv = catMan->GetCategoryEntry("push", mScope, contractId);
    254    if (NS_SUCCEEDED(rv)) {
    255      // Ensure the service is created - we don't need to do anything with
    256      // it though - we assume the service constructor attaches a listener.
    257      nsCOMPtr<nsISupports> service = do_GetService(contractId.get());
    258    }
    259  }
    260  return obsService->NotifyObservers(aSubject, aTopic,
    261                                     NS_ConvertUTF8toUTF16(mScope).get());
    262 }
    263 
    264 PushMessageDispatcher::PushMessageDispatcher(
    265    const nsACString& aScope, nsIPrincipal* aPrincipal,
    266    const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData)
    267    : PushDispatcher(aScope, aPrincipal),
    268      mMessageId(aMessageId),
    269      mData(aData ? Some(aData->Clone()) : Nothing()) {}
    270 
    271 PushMessageDispatcher::~PushMessageDispatcher() = default;
    272 
    273 nsresult PushMessageDispatcher::NotifyObservers() {
    274  nsCOMPtr<nsIPushData> data;
    275  if (mData) {
    276    data = new PushData(mData.ref());
    277  }
    278  nsCOMPtr<nsIPushMessage> message = new PushMessage(mPrincipal, data);
    279  return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, mScope);
    280 }
    281 
    282 nsresult PushMessageDispatcher::NotifyWorkers() {
    283  if (!ShouldNotifyWorkers()) {
    284    return NS_OK;
    285  }
    286  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    287  if (!swm) {
    288    return NS_ERROR_FAILURE;
    289  }
    290  nsAutoCString originSuffix;
    291  nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
    292  if (NS_WARN_IF(NS_FAILED(rv))) {
    293    return rv;
    294  }
    295  return swm->SendPushEvent(originSuffix, mScope, mMessageId, mData);
    296 }
    297 
    298 bool PushMessageDispatcher::SendToParent(ContentChild* aParentActor) {
    299  if (mData) {
    300    return aParentActor->SendNotifyPushObserversWithData(
    301        mScope, mPrincipal, mMessageId, mData.ref());
    302  }
    303  return aParentActor->SendNotifyPushObservers(mScope, mPrincipal, mMessageId);
    304 }
    305 
    306 bool PushMessageDispatcher::SendToChild(ContentParent* aContentActor) {
    307  if (mData) {
    308    return aContentActor->SendPushWithData(mScope, mPrincipal, mMessageId,
    309                                           mData.ref());
    310  }
    311  return aContentActor->SendPush(mScope, mPrincipal, mMessageId);
    312 }
    313 
    314 PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher(
    315    const nsACString& aScope, nsIPrincipal* aPrincipal,
    316    nsIPushSubscription* aOldSubscription)
    317    : PushDispatcher(aScope, aPrincipal), mOldSubscription(aOldSubscription) {}
    318 
    319 PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() = default;
    320 
    321 nsresult PushSubscriptionChangeDispatcher::NotifyObservers() {
    322  return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
    323                           mScope);
    324 }
    325 
    326 nsresult PushSubscriptionChangeDispatcher::NotifyWorkers() {
    327  if (!ShouldNotifyWorkers()) {
    328    return NS_OK;
    329  }
    330  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    331  if (!swm) {
    332    return NS_ERROR_FAILURE;
    333  }
    334  nsAutoCString originSuffix;
    335  nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
    336  if (NS_WARN_IF(NS_FAILED(rv))) {
    337    return rv;
    338  }
    339  return swm->SendPushSubscriptionChangeEvent(originSuffix, mScope,
    340                                              mOldSubscription);
    341 }
    342 
    343 bool PushSubscriptionChangeDispatcher::SendToParent(
    344    ContentChild* aParentActor) {
    345  return true;
    346 }
    347 
    348 bool PushSubscriptionChangeDispatcher::SendToChild(
    349    ContentParent* aContentActor) {
    350  return true;
    351 }
    352 
    353 PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher(
    354    const nsACString& aScope, nsIPrincipal* aPrincipal)
    355    : PushDispatcher(aScope, aPrincipal) {}
    356 
    357 PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() =
    358    default;
    359 
    360 nsresult PushSubscriptionModifiedDispatcher::NotifyObservers() {
    361  return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,
    362                           mScope);
    363 }
    364 
    365 nsresult PushSubscriptionModifiedDispatcher::NotifyWorkers() { return NS_OK; }
    366 
    367 bool PushSubscriptionModifiedDispatcher::SendToParent(
    368    ContentChild* aParentActor) {
    369  return aParentActor->SendNotifyPushSubscriptionModifiedObservers(mScope,
    370                                                                   mPrincipal);
    371 }
    372 
    373 bool PushSubscriptionModifiedDispatcher::SendToChild(
    374    ContentParent* aContentActor) {
    375  return aContentActor->SendNotifyPushSubscriptionModifiedObservers(mScope,
    376                                                                    mPrincipal);
    377 }
    378 
    379 PushErrorDispatcher::PushErrorDispatcher(const nsACString& aScope,
    380                                         nsIPrincipal* aPrincipal,
    381                                         const nsAString& aMessage,
    382                                         uint32_t aFlags)
    383    : PushDispatcher(aScope, aPrincipal), mMessage(aMessage), mFlags(aFlags) {}
    384 
    385 PushErrorDispatcher::~PushErrorDispatcher() = default;
    386 
    387 nsresult PushErrorDispatcher::NotifyObservers() { return NS_OK; }
    388 
    389 nsresult PushErrorDispatcher::NotifyWorkers() {
    390  if (!ShouldNotifyWorkers() &&
    391      (!mPrincipal || mPrincipal->IsSystemPrincipal())) {
    392    // For system subscriptions, log the error directly to the browser console.
    393    return nsContentUtils::ReportToConsoleNonLocalized(
    394        mMessage, mFlags, "Push"_ns, nullptr, /* aDocument */
    395        SourceLocation());
    396  }
    397 
    398  // For service worker subscriptions, report the error to all clients.
    399  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    400  if (swm) {
    401    swm->ReportToAllClients(mScope, mMessage, mScope, /* aFilename */
    402                            u""_ns,                   /* aLine */
    403                            0,                        /* aLineNumber */
    404                            0,                        /* aColumnNumber */
    405                            mFlags);
    406  }
    407  return NS_OK;
    408 }
    409 
    410 bool PushErrorDispatcher::SendToParent(ContentChild* aContentActor) {
    411  return aContentActor->SendPushError(mScope, mPrincipal, mMessage, mFlags);
    412 }
    413 
    414 bool PushErrorDispatcher::SendToChild(ContentParent* aContentActor) {
    415  return aContentActor->SendPushError(mScope, mPrincipal, mMessage, mFlags);
    416 }
    417 
    418 nsresult PushErrorDispatcher::HandleNoChildProcesses() {
    419  // Report to the console if no content processes are active.
    420  nsCOMPtr<nsIURI> scopeURI;
    421  nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope);
    422  if (NS_WARN_IF(NS_FAILED(rv))) {
    423    return rv;
    424  }
    425  return nsContentUtils::ReportToConsoleNonLocalized(
    426      mMessage, mFlags, "Push"_ns, /* aDocument = */ nullptr,
    427      SourceLocation(scopeURI.get()));
    428 }
    429 
    430 }  // namespace mozilla::dom