tor-browser

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

ServiceWorkerUpdateJob.cpp (19019B)


      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 "ServiceWorkerUpdateJob.h"
      8 
      9 #include "ServiceWorkerManager.h"
     10 #include "ServiceWorkerPrivate.h"
     11 #include "ServiceWorkerRegistrationInfo.h"
     12 #include "ServiceWorkerScriptCache.h"
     13 #include "mozilla/ProfilerMarkers.h"
     14 #include "mozilla/dom/WorkerCommon.h"
     15 #include "nsIScriptError.h"
     16 #include "nsIURL.h"
     17 #include "nsNetUtil.h"
     18 #include "nsProxyRelease.h"
     19 
     20 namespace mozilla::dom {
     21 
     22 using serviceWorkerScriptCache::OnFailure;
     23 
     24 namespace {
     25 
     26 /**
     27 * The spec mandates slightly different behaviors for computing the scope
     28 * prefix string in case a Service-Worker-Allowed header is specified versus
     29 * when it's not available.
     30 *
     31 * With the header:
     32 *   "Set maxScopeString to "/" concatenated with the strings in maxScope's
     33 *    path (including empty strings), separated from each other by "/"."
     34 * Without the header:
     35 *   "Set maxScopeString to "/" concatenated with the strings, except the last
     36 *    string that denotes the script's file name, in registration's registering
     37 *    script url's path (including empty strings), separated from each other by
     38 *    "/"."
     39 *
     40 * In simpler terms, if the header is not present, we should only use the
     41 * "directory" part of the pathname, and otherwise the entire pathname should be
     42 * used.  ScopeStringPrefixMode allows the caller to specify the desired
     43 * behavior.
     44 */
     45 enum ScopeStringPrefixMode { eUseDirectory, eUsePath };
     46 
     47 nsresult GetRequiredScopeStringPrefix(nsIURI* aScriptURI, nsACString& aPrefix,
     48                                      ScopeStringPrefixMode aPrefixMode) {
     49  nsresult rv;
     50  if (aPrefixMode == eUseDirectory) {
     51    nsCOMPtr<nsIURL> scriptURL(do_QueryInterface(aScriptURI));
     52    if (NS_WARN_IF(!scriptURL)) {
     53      return NS_ERROR_FAILURE;
     54    }
     55 
     56    rv = scriptURL->GetDirectory(aPrefix);
     57    if (NS_WARN_IF(NS_FAILED(rv))) {
     58      return rv;
     59    }
     60  } else if (aPrefixMode == eUsePath) {
     61    rv = aScriptURI->GetPathQueryRef(aPrefix);
     62    if (NS_WARN_IF(NS_FAILED(rv))) {
     63      return rv;
     64    }
     65  } else {
     66    MOZ_ASSERT_UNREACHABLE("Invalid value for aPrefixMode");
     67  }
     68  return NS_OK;
     69 }
     70 
     71 }  // anonymous namespace
     72 
     73 class ServiceWorkerUpdateJob::CompareCallback final
     74    : public serviceWorkerScriptCache::CompareCallback {
     75  RefPtr<ServiceWorkerUpdateJob> mJob;
     76 
     77  ~CompareCallback() = default;
     78 
     79 public:
     80  explicit CompareCallback(ServiceWorkerUpdateJob* aJob) : mJob(aJob) {
     81    MOZ_ASSERT(mJob);
     82  }
     83 
     84  virtual void ComparisonResult(nsresult aStatus, bool aInCacheAndEqual,
     85                                OnFailure aOnFailure,
     86                                const nsAString& aNewCacheName,
     87                                const nsACString& aMaxScope,
     88                                nsLoadFlags aLoadFlags) override {
     89    mJob->ComparisonResult(aStatus, aInCacheAndEqual, aOnFailure, aNewCacheName,
     90                           aMaxScope, aLoadFlags);
     91  }
     92 
     93  NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateJob::CompareCallback, override)
     94 };
     95 
     96 class ServiceWorkerUpdateJob::ContinueUpdateRunnable final
     97    : public LifeCycleEventCallback {
     98  nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
     99  bool mSuccess;
    100 
    101 public:
    102  explicit ContinueUpdateRunnable(
    103      const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
    104      : mJob(aJob), mSuccess(false) {
    105    MOZ_ASSERT(NS_IsMainThread());
    106  }
    107 
    108  void SetResult(bool aResult) override { mSuccess = aResult; }
    109 
    110  NS_IMETHOD
    111  Run() override {
    112    MOZ_ASSERT(NS_IsMainThread());
    113    mJob->ContinueUpdateAfterScriptEval(mSuccess);
    114    mJob = nullptr;
    115    return NS_OK;
    116  }
    117 };
    118 
    119 class ServiceWorkerUpdateJob::ContinueInstallRunnable final
    120    : public LifeCycleEventCallback {
    121  nsMainThreadPtrHandle<ServiceWorkerUpdateJob> mJob;
    122  bool mSuccess;
    123 
    124 public:
    125  explicit ContinueInstallRunnable(
    126      const nsMainThreadPtrHandle<ServiceWorkerUpdateJob>& aJob)
    127      : mJob(aJob), mSuccess(false) {
    128    MOZ_ASSERT(NS_IsMainThread());
    129  }
    130 
    131  void SetResult(bool aResult) override { mSuccess = aResult; }
    132 
    133  NS_IMETHOD
    134  Run() override {
    135    MOZ_ASSERT(NS_IsMainThread());
    136    mJob->ContinueAfterInstallEvent(mSuccess);
    137    mJob = nullptr;
    138    return NS_OK;
    139  }
    140 };
    141 
    142 ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(
    143    nsIPrincipal* aPrincipal, const nsACString& aScope, nsCString aScriptSpec,
    144    ServiceWorkerUpdateViaCache aUpdateViaCache,
    145    const ServiceWorkerLifetimeExtension& aLifetimeExtension)
    146    : ServiceWorkerUpdateJob(Type::Update, aPrincipal, aScope,
    147                             std::move(aScriptSpec), aUpdateViaCache,
    148                             aLifetimeExtension) {}
    149 
    150 already_AddRefed<ServiceWorkerRegistrationInfo>
    151 ServiceWorkerUpdateJob::GetRegistration() const {
    152  MOZ_ASSERT(NS_IsMainThread());
    153  RefPtr<ServiceWorkerRegistrationInfo> ref = mRegistration;
    154  return ref.forget();
    155 }
    156 
    157 ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(
    158    Type aType, nsIPrincipal* aPrincipal, const nsACString& aScope,
    159    nsCString aScriptSpec, ServiceWorkerUpdateViaCache aUpdateViaCache,
    160    const ServiceWorkerLifetimeExtension& aLifetimeExtension)
    161    : ServiceWorkerJob(aType, aPrincipal, aScope, std::move(aScriptSpec)),
    162      mUpdateViaCache(aUpdateViaCache),
    163      mLifetimeExtension(aLifetimeExtension),
    164      mOnFailure(serviceWorkerScriptCache::OnFailure::DoNothing) {}
    165 
    166 ServiceWorkerUpdateJob::~ServiceWorkerUpdateJob() = default;
    167 
    168 void ServiceWorkerUpdateJob::FailUpdateJob(ErrorResult& aRv) {
    169  MOZ_ASSERT(NS_IsMainThread());
    170  MOZ_ASSERT(aRv.Failed());
    171 
    172  // Cleanup after a failed installation.  This essentially implements
    173  // step 13 of the Install algorithm.
    174  //
    175  //  https://w3c.github.io/ServiceWorker/#installation-algorithm
    176  //
    177  // The spec currently only runs this after an install event fails,
    178  // but we must handle many more internal errors.  So we check for
    179  // cleanup on every non-successful exit.
    180  if (mRegistration) {
    181    // Some kinds of failures indicate there is something broken in the
    182    // currently installed registration.  In these cases we want to fully
    183    // unregister.
    184    if (mOnFailure == OnFailure::Uninstall) {
    185      mRegistration->ClearAsCorrupt();
    186    }
    187 
    188    // Otherwise just clear the workers we may have created as part of the
    189    // update process.
    190    else {
    191      mRegistration->ClearEvaluating();
    192      mRegistration->ClearInstalling();
    193    }
    194 
    195    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    196    if (swm) {
    197      swm->MaybeRemoveRegistration(mRegistration);
    198 
    199      // Also clear the registration on disk if we are forcing uninstall
    200      // due to a particularly bad failure.
    201      if (mOnFailure == OnFailure::Uninstall) {
    202        swm->MaybeSendUnregister(mRegistration->Principal(),
    203                                 mRegistration->Scope());
    204      }
    205    }
    206  }
    207 
    208  mRegistration = nullptr;
    209 
    210  Finish(aRv);
    211 }
    212 
    213 void ServiceWorkerUpdateJob::FailUpdateJob(nsresult aRv) {
    214  ErrorResult rv(aRv);
    215  FailUpdateJob(rv);
    216  // This signature is intentionally about not using the result, so we do need
    217  // to suppress the exception.
    218  rv.SuppressException();
    219 }
    220 
    221 void ServiceWorkerUpdateJob::AsyncExecute() {
    222  AUTO_PROFILER_MARKER_UNTYPED("ServiceWorkerUpdateJob::AsyncExecute", DOM, {});
    223 
    224  MOZ_ASSERT(NS_IsMainThread());
    225  MOZ_ASSERT(GetType() == Type::Update);
    226 
    227  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    228  if (Canceled() || !swm) {
    229    FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    230    return;
    231  }
    232 
    233  // Invoke Update algorithm:
    234  // https://w3c.github.io/ServiceWorker/#update-algorithm
    235  //
    236  // "Let registration be the result of running the Get Registration algorithm
    237  // passing job’s scope url as the argument."
    238  RefPtr<ServiceWorkerRegistrationInfo> registration =
    239      swm->GetRegistration(mPrincipal, mScope);
    240 
    241  if (!registration) {
    242    ErrorResult rv;
    243    rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(mScope, "uninstalled");
    244    FailUpdateJob(rv);
    245    return;
    246  }
    247 
    248  // "Let newestWorker be the result of running Get Newest Worker algorithm
    249  // passing registration as the argument."
    250  RefPtr<ServiceWorkerInfo> newest = registration->Newest();
    251 
    252  // "If job’s job type is update, and newestWorker is not null and its script
    253  // url does not equal job’s script url, then:
    254  //   1. Invoke Reject Job Promise with job and TypeError.
    255  //   2. Invoke Finish Job with job and abort these steps."
    256  if (newest && !newest->ScriptSpec().Equals(mScriptSpec)) {
    257    ErrorResult rv;
    258    rv.ThrowTypeError<MSG_SW_UPDATE_BAD_REGISTRATION>(mScope, "changed");
    259    FailUpdateJob(rv);
    260    return;
    261  }
    262 
    263  SetRegistration(registration);
    264  Update();
    265 }
    266 
    267 void ServiceWorkerUpdateJob::SetRegistration(
    268    ServiceWorkerRegistrationInfo* aRegistration) {
    269  MOZ_ASSERT(NS_IsMainThread());
    270 
    271  MOZ_ASSERT(!mRegistration);
    272  MOZ_ASSERT(aRegistration);
    273  mRegistration = aRegistration;
    274 }
    275 
    276 void ServiceWorkerUpdateJob::Update() {
    277  AUTO_PROFILER_MARKER_UNTYPED("ServiceWorkerUpdateJob::Update", DOM, {});
    278 
    279  MOZ_ASSERT(NS_IsMainThread());
    280  MOZ_ASSERT(!Canceled());
    281 
    282  // SetRegistration() must be called before Update().
    283  MOZ_ASSERT(mRegistration);
    284  MOZ_ASSERT(!mRegistration->GetInstalling());
    285 
    286  // Begin the script download and comparison steps starting at step 5
    287  // of the Update algorithm.
    288 
    289  RefPtr<ServiceWorkerInfo> workerInfo = mRegistration->Newest();
    290  nsAutoString cacheName;
    291 
    292  // If the script has not changed, we need to perform a byte-for-byte
    293  // comparison.
    294  if (workerInfo && workerInfo->ScriptSpec().Equals(mScriptSpec)) {
    295    cacheName = workerInfo->CacheName();
    296  }
    297 
    298  RefPtr<CompareCallback> callback = new CompareCallback(this);
    299 
    300  nsresult rv = serviceWorkerScriptCache::Compare(
    301      mRegistration, mPrincipal, cacheName, mScriptSpec, callback);
    302  if (NS_WARN_IF(NS_FAILED(rv))) {
    303    FailUpdateJob(rv);
    304    return;
    305  }
    306 }
    307 
    308 ServiceWorkerUpdateViaCache ServiceWorkerUpdateJob::GetUpdateViaCache() const {
    309  return mUpdateViaCache;
    310 }
    311 
    312 void ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
    313                                              bool aInCacheAndEqual,
    314                                              OnFailure aOnFailure,
    315                                              const nsAString& aNewCacheName,
    316                                              const nsACString& aMaxScope,
    317                                              nsLoadFlags aLoadFlags) {
    318  MOZ_ASSERT(NS_IsMainThread());
    319 
    320  mOnFailure = aOnFailure;
    321 
    322  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    323  if (NS_WARN_IF(Canceled() || !swm)) {
    324    FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    325    return;
    326  }
    327 
    328  // Handle failure of the download or comparison.  This is part of Update
    329  // step 5 as "If the algorithm asynchronously completes with null, then:".
    330  if (NS_WARN_IF(NS_FAILED(aStatus))) {
    331    FailUpdateJob(aStatus);
    332    return;
    333  }
    334 
    335  // The spec validates the response before performing the byte-for-byte check.
    336  // Here we perform the comparison in another module and then validate the
    337  // script URL and scope.  Make sure to do this validation before accepting
    338  // an byte-for-byte match since the service-worker-allowed header might have
    339  // changed since the last time it was installed.
    340 
    341  // This is step 2 the "validate response" section of Update algorithm step 5.
    342  // Step 1 is performed in the serviceWorkerScriptCache code.
    343 
    344  nsCOMPtr<nsIURI> scriptURI;
    345  nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), mScriptSpec);
    346  if (NS_WARN_IF(NS_FAILED(rv))) {
    347    FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
    348    return;
    349  }
    350 
    351  nsCOMPtr<nsIURI> maxScopeURI;
    352  if (!aMaxScope.IsEmpty()) {
    353    rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope, nullptr, scriptURI);
    354    if (NS_WARN_IF(NS_FAILED(rv))) {
    355      FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
    356      return;
    357    }
    358  }
    359 
    360  nsAutoCString defaultAllowedPrefix;
    361  rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix,
    362                                    eUseDirectory);
    363  if (NS_WARN_IF(NS_FAILED(rv))) {
    364    FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
    365    return;
    366  }
    367 
    368  nsAutoCString maxPrefix(defaultAllowedPrefix);
    369  if (maxScopeURI) {
    370    rv = GetRequiredScopeStringPrefix(maxScopeURI, maxPrefix, eUsePath);
    371    if (NS_WARN_IF(NS_FAILED(rv))) {
    372      FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
    373      return;
    374    }
    375  }
    376 
    377  nsCOMPtr<nsIURI> scopeURI;
    378  rv = NS_NewURI(getter_AddRefs(scopeURI), mRegistration->Scope(), nullptr,
    379                 scriptURI);
    380  if (NS_WARN_IF(NS_FAILED(rv))) {
    381    FailUpdateJob(NS_ERROR_FAILURE);
    382    return;
    383  }
    384 
    385  nsAutoCString scopeString;
    386  rv = scopeURI->GetPathQueryRef(scopeString);
    387  if (NS_WARN_IF(NS_FAILED(rv))) {
    388    FailUpdateJob(NS_ERROR_FAILURE);
    389    return;
    390  }
    391 
    392  if (!StringBeginsWith(scopeString, maxPrefix)) {
    393    nsAutoString message;
    394    NS_ConvertUTF8toUTF16 reportScope(mRegistration->Scope());
    395    NS_ConvertUTF8toUTF16 reportMaxPrefix(maxPrefix);
    396 
    397    rv = nsContentUtils::FormatLocalizedString(
    398        message, nsContentUtils::eDOM_PROPERTIES,
    399        "ServiceWorkerScopePathMismatch", reportScope, reportMaxPrefix);
    400    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to format localized string");
    401    swm->ReportToAllClients(mScope, message, ""_ns, u""_ns, 0, 0,
    402                            nsIScriptError::errorFlag);
    403    FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
    404    return;
    405  }
    406 
    407  // The response has been validated, so now we can consider if its a
    408  // byte-for-byte match.  This is step 6 of the Update algorithm.
    409  if (aInCacheAndEqual) {
    410    Finish(NS_OK);
    411    return;
    412  }
    413 
    414  // Begin step 7 of the Update algorithm to evaluate the new script.
    415  nsLoadFlags flags = aLoadFlags;
    416  if (GetUpdateViaCache() == ServiceWorkerUpdateViaCache::None) {
    417    flags |= nsIRequest::VALIDATE_ALWAYS;
    418  }
    419 
    420  RefPtr<ServiceWorkerInfo> sw = new ServiceWorkerInfo(
    421      mRegistration->Principal(), mRegistration->Scope(), mRegistration->Type(),
    422      mRegistration->Id(), mRegistration->Version(), mScriptSpec, aNewCacheName,
    423      flags);
    424 
    425  // If the registration is corrupt enough to force an uninstall if the
    426  // upgrade fails, then we want to make sure the upgrade takes effect
    427  // if it succeeds.  Therefore force the skip-waiting flag on to replace
    428  // the broken worker after install.
    429  if (aOnFailure == OnFailure::Uninstall) {
    430    sw->SetSkipWaitingFlag();
    431  }
    432 
    433  mRegistration->SetEvaluating(sw);
    434 
    435  nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
    436      new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(
    437          "ServiceWorkerUpdateJob", this));
    438  RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
    439 
    440  ServiceWorkerPrivate* workerPrivate = sw->WorkerPrivate();
    441  MOZ_ASSERT(workerPrivate);
    442  // Note that there are some synchronous failure cases that may immediately
    443  // invoke the callback, meaning that FailUpdateJob may have already been
    444  // called before this method returns.
    445  rv = workerPrivate->CheckScriptEvaluation(mLifetimeExtension, callback);
    446 
    447  // We call FailUpdateJob because it is idempotent and as defense-in-depth
    448  // against early errors returns potentially being introduced above that return
    449  // with ensuring that the passed-in callback will be invoked (such as those
    450  // that are frequently added for shutdown phases).
    451  if (NS_WARN_IF(NS_FAILED(rv))) {
    452    FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    453    return;
    454  }
    455 }
    456 
    457 void ServiceWorkerUpdateJob::ContinueUpdateAfterScriptEval(
    458    bool aScriptEvaluationResult) {
    459  MOZ_ASSERT(NS_IsMainThread());
    460 
    461  RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
    462  if (Canceled() || !swm) {
    463    FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    464    return;
    465  }
    466 
    467  // Step 7.5 of the Update algorithm verifying that the script evaluated
    468  // successfully.
    469 
    470  if (NS_WARN_IF(!aScriptEvaluationResult)) {
    471    ErrorResult error;
    472    error.ThrowTypeError<MSG_SW_SCRIPT_THREW>(mScriptSpec,
    473                                              mRegistration->Scope());
    474    FailUpdateJob(error);
    475    return;
    476  }
    477 
    478  Install();
    479 }
    480 
    481 void ServiceWorkerUpdateJob::Install() {
    482  AUTO_PROFILER_MARKER_UNTYPED("ServiceWorkerUpdateJob::Install", DOM, {});
    483 
    484  MOZ_ASSERT(NS_IsMainThread());
    485  MOZ_DIAGNOSTIC_ASSERT(!Canceled());
    486 
    487  MOZ_ASSERT(!mRegistration->GetInstalling());
    488 
    489  // Begin step 2 of the Install algorithm.
    490  //
    491  //  https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#installation-algorithm
    492 
    493  mRegistration->TransitionEvaluatingToInstalling();
    494 
    495  // Step 6 of the Install algorithm resolving the job promise.
    496  InvokeResultCallbacks(NS_OK);
    497 
    498  // Queue a task to fire an event named updatefound at all the
    499  // ServiceWorkerRegistration.
    500  mRegistration->FireUpdateFound();
    501 
    502  nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
    503      new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(
    504          "ServiceWorkerUpdateJob", this));
    505  RefPtr<LifeCycleEventCallback> callback = new ContinueInstallRunnable(handle);
    506 
    507  // Send the install event to the worker thread
    508  ServiceWorkerPrivate* workerPrivate =
    509      mRegistration->GetInstalling()->WorkerPrivate();
    510  nsresult rv = workerPrivate->SendLifeCycleEvent(u"install"_ns,
    511                                                  mLifetimeExtension, callback);
    512  if (NS_WARN_IF(NS_FAILED(rv))) {
    513    ContinueAfterInstallEvent(false /* aSuccess */);
    514  }
    515 }
    516 
    517 void ServiceWorkerUpdateJob::ContinueAfterInstallEvent(
    518    bool aInstallEventSuccess) {
    519  if (Canceled()) {
    520    return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    521  }
    522 
    523  // If we haven't been canceled we should have a registration.  There appears
    524  // to be a path where it gets cleared before we call into here.  Assert
    525  // to try to catch this condition, but don't crash in release.
    526  MOZ_DIAGNOSTIC_ASSERT(mRegistration);
    527  if (!mRegistration) {
    528    return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    529  }
    530 
    531  // Continue executing the Install algorithm at step 12.
    532 
    533  // "If installFailed is true"
    534  if (NS_WARN_IF(!aInstallEventSuccess)) {
    535    // The installing worker is cleaned up by FailUpdateJob().
    536    FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    537    return;
    538  }
    539 
    540  // Abort the update Job if the installWorker is null (e.g. when an extension
    541  // is shutting down and all its workers have been terminated).
    542  if (!mRegistration->GetInstalling()) {
    543    return FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
    544  }
    545 
    546  mRegistration->TransitionInstallingToWaiting();
    547 
    548  Finish(NS_OK);
    549 
    550  // Step 20 calls for explicitly waiting for queued event tasks to fire.
    551  // Instead, we simply queue a runnable to execute Activate.  This ensures the
    552  // events are flushed from the queue before proceeding.
    553 
    554  // Step 22 of the Install algorithm.  Activate is executed after the
    555  // completion of this job.  The controlling client and skipWaiting checks are
    556  // performed in TryToActivate().
    557  mRegistration->TryToActivateAsync(mLifetimeExtension);
    558 }
    559 
    560 }  // namespace mozilla::dom