tor-browser

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

ServiceWorker.cpp (10730B)


      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 "ServiceWorker.h"
      8 
      9 #include "ServiceWorkerChild.h"
     10 #include "ServiceWorkerCloneData.h"
     11 #include "ServiceWorkerManager.h"
     12 #include "ServiceWorkerPrivate.h"
     13 #include "ServiceWorkerRegistration.h"
     14 #include "ServiceWorkerUtils.h"
     15 #include "mozilla/BasePrincipal.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/dom/ClientIPCTypes.h"
     18 #include "mozilla/dom/ClientState.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/MessagePortBinding.h"
     21 #include "mozilla/dom/Navigator.h"
     22 #include "mozilla/dom/Promise.h"
     23 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
     24 #include "mozilla/dom/WorkerPrivate.h"
     25 #include "mozilla/ipc/BackgroundChild.h"
     26 #include "mozilla/ipc/PBackgroundChild.h"
     27 #include "nsGlobalWindowInner.h"
     28 #include "nsPIDOMWindow.h"
     29 
     30 #ifdef XP_WIN
     31 #  undef PostMessage
     32 #endif
     33 
     34 using mozilla::ipc::BackgroundChild;
     35 using mozilla::ipc::PBackgroundChild;
     36 
     37 namespace mozilla::dom {
     38 
     39 // static
     40 already_AddRefed<ServiceWorker> ServiceWorker::Create(
     41    nsIGlobalObject* aOwner, const ServiceWorkerDescriptor& aDescriptor) {
     42  RefPtr<ServiceWorker> ref = new ServiceWorker(aOwner, aDescriptor);
     43  return ref.forget();
     44 }
     45 
     46 ServiceWorker::ServiceWorker(nsIGlobalObject* aGlobal,
     47                             const ServiceWorkerDescriptor& aDescriptor)
     48    : DOMEventTargetHelper(aGlobal),
     49      mDescriptor(aDescriptor),
     50      mShutdown(false),
     51      mLastNotifiedState(ServiceWorkerState::Installing) {
     52  MOZ_DIAGNOSTIC_ASSERT(aGlobal);
     53 
     54  PBackgroundChild* parentActor =
     55      BackgroundChild::GetOrCreateForCurrentThread();
     56  if (NS_WARN_IF(!parentActor)) {
     57    Shutdown();
     58    return;
     59  }
     60 
     61  RefPtr<ServiceWorkerChild> actor = ServiceWorkerChild::Create();
     62  if (NS_WARN_IF(!actor)) {
     63    Shutdown();
     64    return;
     65  }
     66 
     67  PServiceWorkerChild* sentActor =
     68      parentActor->SendPServiceWorkerConstructor(actor, aDescriptor.ToIPC());
     69  if (NS_WARN_IF(!sentActor)) {
     70    Shutdown();
     71    return;
     72  }
     73  MOZ_DIAGNOSTIC_ASSERT(sentActor == actor);
     74 
     75  mActor = std::move(actor);
     76  mActor->SetOwner(this);
     77 
     78  KeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
     79 
     80  // The error event handler is required by the spec currently, but is not used
     81  // anywhere.  Don't keep the object alive in that case.
     82 
     83  // Attempt to get an existing binding object for the registration
     84  // associated with this ServiceWorker.
     85  RefPtr<ServiceWorkerRegistration> reg =
     86      aGlobal->GetServiceWorkerRegistration(ServiceWorkerRegistrationDescriptor(
     87          mDescriptor.RegistrationId(), mDescriptor.RegistrationVersion(),
     88          mDescriptor.PrincipalInfo(), mDescriptor.Scope(), mDescriptor.Type(),
     89          ServiceWorkerUpdateViaCache::Imports));
     90 
     91  if (reg) {
     92    MaybeAttachToRegistration(reg);
     93    // Following codes are commented since GetRegistration has no
     94    // implementation. If we can not get an existing binding object, probably
     95    // need to create one to associate to it.
     96    // https://bugzilla.mozilla.org/show_bug.cgi?id=1769652
     97    /*
     98    } else {
     99 
    100      RefPtr<ServiceWorker> self = this;
    101      GetRegistration(
    102          [self = std::move(self)](
    103              const ServiceWorkerRegistrationDescriptor& aDescriptor) {
    104            nsIGlobalObject* global = self->GetParentObject();
    105            NS_ENSURE_TRUE_VOID(global);
    106            RefPtr<ServiceWorkerRegistration> reg =
    107                global->GetOrCreateServiceWorkerRegistration(aDescriptor);
    108            self->MaybeAttachToRegistration(reg);
    109          },
    110          [](ErrorResult&& aRv) {
    111            // do nothing
    112            aRv.SuppressException();
    113          });
    114    */
    115  }
    116 }
    117 
    118 ServiceWorker::~ServiceWorker() { Shutdown(); }
    119 
    120 NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker, DOMEventTargetHelper,
    121                                   mRegistration);
    122 
    123 NS_IMPL_ADDREF_INHERITED(ServiceWorker, DOMEventTargetHelper)
    124 NS_IMPL_RELEASE_INHERITED(ServiceWorker, DOMEventTargetHelper)
    125 
    126 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorker)
    127  NS_INTERFACE_MAP_ENTRY_CONCRETE(ServiceWorker)
    128 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
    129 
    130 JSObject* ServiceWorker::WrapObject(JSContext* aCx,
    131                                    JS::Handle<JSObject*> aGivenProto) {
    132  return ServiceWorker_Binding::Wrap(aCx, this, aGivenProto);
    133 }
    134 
    135 ServiceWorkerState ServiceWorker::State() const { return mDescriptor.State(); }
    136 
    137 void ServiceWorker::SetState(ServiceWorkerState aState) {
    138  NS_ENSURE_TRUE_VOID(aState >= mDescriptor.State());
    139  mDescriptor.SetState(aState);
    140 }
    141 
    142 void ServiceWorker::MaybeDispatchStateChangeEvent() {
    143  if (mDescriptor.State() <= mLastNotifiedState || !GetParentObject()) {
    144    return;
    145  }
    146  mLastNotifiedState = mDescriptor.State();
    147 
    148  DOMEventTargetHelper::DispatchTrustedEvent(u"statechange"_ns);
    149 
    150  // Once we have transitioned to the redundant state then no
    151  // more statechange events will occur.  We can allow the DOM
    152  // object to GC if script is not holding it alive.
    153  if (mLastNotifiedState == ServiceWorkerState::Redundant) {
    154    IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onstatechange);
    155  }
    156 }
    157 
    158 void ServiceWorker::GetScriptURL(nsString& aURL) const {
    159  CopyUTF8toUTF16(mDescriptor.ScriptURL(), aURL);
    160 }
    161 
    162 void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
    163                                const Sequence<JSObject*>& aTransferable,
    164                                ErrorResult& aRv) {
    165  // Step 6.1 of
    166  // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options
    167  // invokes
    168  // https://w3c.github.io/ServiceWorker/#run-service-worker
    169  // which returns failure in step 3 if the ServiceWorker state is redundant.
    170  // This will result in the "in parallel" step 6.1 of postMessage itself early
    171  // returning without starting the ServiceWorker and without throwing an error.
    172  if (State() == ServiceWorkerState::Redundant) {
    173    return;
    174  }
    175 
    176  nsIGlobalObject* global = GetOwnerGlobal();
    177  if (NS_WARN_IF(!global)) {
    178    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    179    return;
    180  }
    181 
    182  if (!ServiceWorkersStorageAllowedForGlobal(global)) {
    183    ServiceWorkerManager::LocalizeAndReportToAllClients(
    184        mDescriptor.Scope(), "ServiceWorkerPostMessageStorageError",
    185        nsTArray<nsString>{NS_ConvertUTF8toUTF16(mDescriptor.Scope())});
    186    aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
    187    return;
    188  }
    189 
    190  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
    191  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
    192                                                          &transferable);
    193  if (aRv.Failed()) {
    194    return;
    195  }
    196 
    197  // Window-to-SW messages do not allow memory sharing since they are not in the
    198  // same agent cluster group, but we do not want to throw an error during the
    199  // serialization. Because of this, ServiceWorkerCloneData will propagate an
    200  // error message data if the SameProcess serialization is required. So that
    201  // the receiver (service worker) knows that it needs to throw while
    202  // deserialization and sharing memory objects are not propagated to the other
    203  // process.
    204  JS::CloneDataPolicy clonePolicy;
    205  if (global->IsSharedMemoryAllowed()) {
    206    clonePolicy.allowSharedMemoryObjects();
    207  }
    208 
    209  RefPtr<ServiceWorkerCloneData> data = new ServiceWorkerCloneData();
    210  data->Write(aCx, aMessage, transferable, clonePolicy, aRv);
    211  if (aRv.Failed()) {
    212    return;
    213  }
    214 
    215  // If StructuredCloneData::Write() ended up deciding on a scope of SameProcess
    216  // then we must convert this to an error on deserialization.  This is because
    217  // such payloads fundamentally can't be sent cross-process (they involve
    218  // pointers / local resources).  However, this will also correlate with the
    219  // spec for situations like SharedArrayBuffer which are limited to being sent
    220  // within the same agent cluster and where ServiceWorkers are always spawned
    221  // in their own agent cluster.
    222  if (data->CloneScope() ==
    223      StructuredCloneHolder::StructuredCloneScope::SameProcess) {
    224    data->SetAsErrorMessageData();
    225  }
    226 
    227  if (!mActor) {
    228    return;
    229  }
    230 
    231  ClonedOrErrorMessageData clonedData;
    232  if (!data->BuildClonedMessageData(clonedData)) {
    233    return;
    234  }
    235 
    236  // ServiceWorkersStorageAllowedForGlobal will have already validated these as
    237  // both being isSome().
    238  Maybe<ClientInfo> clientInfo = global->GetClientInfo();
    239  Maybe<ClientState> clientState = global->GetClientState();
    240 
    241  // If this global is a ServiceWorker, we need this global's
    242  // ServiceWorkerDescriptor.  While we normally try and normalize things
    243  // through nsIGlobalObject, this is fairly one-off right now, so starting from
    244  // worker-specific logic.
    245  PostMessageSource source;
    246  if (WorkerPrivate* wp = GetCurrentThreadWorkerPrivate()) {
    247    if (wp->IsServiceWorker()) {
    248      source = wp->GetServiceWorkerDescriptor().ToIPC();
    249    } else {
    250      source = ClientInfoAndState(clientInfo.ref().ToIPC(),
    251                                  clientState.ref().ToIPC());
    252    }
    253  } else {
    254    source =
    255        ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC());
    256  }
    257 
    258  mActor->SendPostMessage(clonedData, source);
    259 }
    260 
    261 void ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
    262                                const StructuredSerializeOptions& aOptions,
    263                                ErrorResult& aRv) {
    264  PostMessage(aCx, aMessage, aOptions.mTransfer, aRv);
    265 }
    266 
    267 const ServiceWorkerDescriptor& ServiceWorker::Descriptor() const {
    268  return mDescriptor;
    269 }
    270 
    271 void ServiceWorker::DisconnectFromOwner() {
    272  DOMEventTargetHelper::DisconnectFromOwner();
    273 }
    274 
    275 void ServiceWorker::RevokeActor(ServiceWorkerChild* aActor) {
    276  MOZ_DIAGNOSTIC_ASSERT(mActor);
    277  MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
    278  mActor->RevokeOwner(this);
    279  mActor = nullptr;
    280 
    281  mShutdown = true;
    282 }
    283 
    284 void ServiceWorker::MaybeAttachToRegistration(
    285    ServiceWorkerRegistration* aRegistration) {
    286  MOZ_DIAGNOSTIC_ASSERT(aRegistration);
    287  MOZ_DIAGNOSTIC_ASSERT(!mRegistration);
    288 
    289  // If the registration no longer actually references this ServiceWorker
    290  // then we must be in the redundant state.
    291  if (!aRegistration->Descriptor().HasWorker(mDescriptor)) {
    292    SetState(ServiceWorkerState::Redundant);
    293    MaybeDispatchStateChangeEvent();
    294    return;
    295  }
    296 
    297  mRegistration = aRegistration;
    298 }
    299 
    300 void ServiceWorker::Shutdown() {
    301  if (mShutdown) {
    302    return;
    303  }
    304  mShutdown = true;
    305 
    306  if (mActor) {
    307    mActor->RevokeOwner(this);
    308    mActor->MaybeStartTeardown();
    309    mActor = nullptr;
    310  }
    311 }
    312 
    313 }  // namespace mozilla::dom