tor-browser

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

WorkerRunnable.cpp (25305B)


      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 "WorkerRunnable.h"
      8 
      9 #include "WorkerScope.h"
     10 #include "js/RootingAPI.h"
     11 #include "jsapi.h"
     12 #include "jsfriendapi.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/CycleCollectedJSContext.h"
     15 #include "mozilla/DebugOnly.h"
     16 #include "mozilla/ErrorResult.h"
     17 #include "mozilla/Logging.h"
     18 #include "mozilla/Maybe.h"
     19 #include "mozilla/TelemetryHistogramEnums.h"
     20 #include "mozilla/TimeStamp.h"
     21 #include "mozilla/dom/ScriptSettings.h"
     22 #include "mozilla/dom/Worker.h"
     23 #include "mozilla/dom/WorkerCommon.h"
     24 #include "mozilla/glean/DomWorkersMetrics.h"
     25 #include "nsDebug.h"
     26 #include "nsGlobalWindowInner.h"
     27 #include "nsID.h"
     28 #include "nsIEventTarget.h"
     29 #include "nsIGlobalObject.h"
     30 #include "nsIRunnable.h"
     31 #include "nsThreadUtils.h"
     32 #include "nsWrapperCacheInlines.h"
     33 
     34 namespace mozilla::dom {
     35 
     36 static mozilla::LazyLogModule sWorkerRunnableLog("WorkerRunnable");
     37 
     38 #ifdef LOG
     39 #  undef LOG
     40 #endif
     41 #define LOG(args) MOZ_LOG(sWorkerRunnableLog, LogLevel::Verbose, args);
     42 
     43 namespace {
     44 
     45 const nsIID kWorkerRunnableIID = {
     46    0x320cc0b5,
     47    0xef12,
     48    0x4084,
     49    {0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68}};
     50 
     51 }  // namespace
     52 
     53 #ifdef DEBUG
     54 WorkerRunnable::WorkerRunnable(const char* aName)
     55 #  ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
     56    : mName(aName) {
     57  LOG(("WorkerRunnable::WorkerRunnable [%p] (%s)", this, mName));
     58 }
     59 #  else
     60 {
     61  LOG(("WorkerRunnable::WorkerRunnable [%p]", this));
     62 }
     63 #  endif
     64 #endif
     65 
     66 // static
     67 WorkerRunnable* WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) {
     68  MOZ_ASSERT(aRunnable);
     69 
     70  WorkerRunnable* runnable;
     71  nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID,
     72                                          reinterpret_cast<void**>(&runnable));
     73  if (NS_FAILED(rv)) {
     74    return nullptr;
     75  }
     76 
     77  MOZ_ASSERT(runnable);
     78  return runnable;
     79 }
     80 
     81 bool WorkerRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) {
     82  LOG(("WorkerRunnable::Dispatch [%p] aWorkerPrivate: %p", this,
     83       aWorkerPrivate));
     84  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
     85  bool ok = PreDispatch(aWorkerPrivate);
     86  if (ok) {
     87    ok = DispatchInternal(aWorkerPrivate);
     88  }
     89  PostDispatch(aWorkerPrivate, ok);
     90  return ok;
     91 }
     92 
     93 NS_IMETHODIMP WorkerRunnable::Run() { return NS_OK; }
     94 
     95 NS_IMPL_ADDREF(WorkerRunnable)
     96 NS_IMPL_RELEASE(WorkerRunnable)
     97 
     98 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
     99 NS_IMETHODIMP
    100 WorkerRunnable::GetName(nsACString& aName) {
    101  if (mName) {
    102    aName.AssignASCII(mName);
    103  } else {
    104    aName.Truncate();
    105  }
    106  return NS_OK;
    107 }
    108 #endif
    109 
    110 NS_INTERFACE_MAP_BEGIN(WorkerRunnable)
    111  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
    112 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
    113  NS_INTERFACE_MAP_ENTRY(nsINamed)
    114 #endif
    115  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
    116  // kWorkerRunnableIID is special in that it does not AddRef its result.
    117  if (aIID.Equals(kWorkerRunnableIID)) {
    118    *aInstancePtr = this;
    119    return NS_OK;
    120  } else
    121 NS_INTERFACE_MAP_END
    122 
    123 WorkerParentThreadRunnable::WorkerParentThreadRunnable(const char* aName)
    124    : WorkerRunnable(aName) {
    125  LOG(("WorkerParentThreadRunnable::WorkerParentThreadRunnable [%p]", this));
    126 }
    127 
    128 WorkerParentThreadRunnable::~WorkerParentThreadRunnable() = default;
    129 
    130 bool WorkerParentThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
    131 #ifdef DEBUG
    132  MOZ_ASSERT(aWorkerPrivate);
    133  aWorkerPrivate->AssertIsOnWorkerThread();
    134 #endif
    135  return true;
    136 }
    137 
    138 bool WorkerParentThreadRunnable::DispatchInternal(
    139    WorkerPrivate* aWorkerPrivate) {
    140  LOG(("WorkerParentThreadRunnable::DispatchInternal [%p]", this));
    141  mWorkerParentRef = aWorkerPrivate->GetWorkerParentRef();
    142  RefPtr<WorkerParentThreadRunnable> runnable(this);
    143  return NS_SUCCEEDED(aWorkerPrivate->DispatchToParent(runnable.forget()));
    144 }
    145 
    146 void WorkerParentThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    147                                              bool aDispatchResult) {
    148 #ifdef DEBUG
    149  MOZ_ASSERT(aWorkerPrivate);
    150  aWorkerPrivate->AssertIsOnWorkerThread();
    151 #endif
    152 }
    153 
    154 bool WorkerParentThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
    155  return true;
    156 }
    157 
    158 void WorkerParentThreadRunnable::PostRun(JSContext* aCx,
    159                                         WorkerPrivate* aWorkerPrivate,
    160                                         bool aRunResult) {
    161  MOZ_ASSERT(aCx);
    162 #ifdef DEBUG
    163  MOZ_ASSERT(aWorkerPrivate);
    164  aWorkerPrivate->AssertIsOnParentThread();
    165 #endif
    166 }
    167 
    168 NS_IMETHODIMP
    169 WorkerParentThreadRunnable::Run() {
    170  LOG(("WorkerParentThreadRunnable::Run [%p]", this));
    171  RefPtr<WorkerPrivate> workerPrivate;
    172  MOZ_ASSERT(mWorkerParentRef);
    173  workerPrivate = mWorkerParentRef->Private();
    174  if (!workerPrivate) {
    175    NS_WARNING("Worker has already shut down!!!");
    176    return NS_OK;
    177  }
    178 #ifdef DEBUG
    179  workerPrivate->AssertIsOnParentThread();
    180 #endif
    181 
    182  WorkerPrivate* parent = workerPrivate->GetParent();
    183  bool isOnMainThread = !parent;
    184  bool result = PreRun(workerPrivate);
    185  MOZ_ASSERT(result);
    186 
    187  LOG(("WorkerParentThreadRunnable::Run [%p] WorkerPrivate: %p, parent: %p",
    188       this, workerPrivate.get(), parent));
    189 
    190  // Track down the appropriate global, if any, to use for the AutoEntryScript.
    191  nsCOMPtr<nsIGlobalObject> globalObject;
    192  if (isOnMainThread) {
    193    MOZ_ASSERT(isOnMainThread == NS_IsMainThread());
    194    globalObject = nsGlobalWindowInner::Cast(workerPrivate->GetWindow());
    195  } else {
    196    MOZ_ASSERT(parent == GetCurrentThreadWorkerPrivate());
    197    globalObject = parent->GlobalScope();
    198    MOZ_DIAGNOSTIC_ASSERT(globalObject);
    199  }
    200  // We might run script as part of WorkerRun, so we need an AutoEntryScript.
    201  // This is part of the HTML spec for workers at:
    202  // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
    203  // If we don't have a globalObject we have to use an AutoJSAPI instead, but
    204  // this is OK as we won't be running script in these circumstances.
    205  Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
    206  Maybe<mozilla::dom::AutoEntryScript> aes;
    207  JSContext* cx;
    208  AutoJSAPI* jsapi;
    209 
    210  if (globalObject) {
    211    aes.emplace(globalObject, "Worker parent thread runnable", isOnMainThread);
    212    jsapi = aes.ptr();
    213    cx = aes->cx();
    214  } else {
    215    maybeJSAPI.emplace();
    216    maybeJSAPI->Init();
    217    jsapi = maybeJSAPI.ptr();
    218    cx = jsapi->cx();
    219  }
    220 
    221  // Note that we can't assert anything about
    222  // workerPrivate->ParentEventTargetRef()->GetWrapper()
    223  // existing, since it may in fact have been GCed (and we may be one of the
    224  // runnables cleaning up the worker as a result).
    225 
    226  // If we are on the parent thread and that thread is not the main thread,
    227  // then we must be a dedicated worker (because there are no
    228  // Shared/ServiceWorkers whose parent is itself a worker) and then we
    229  // definitely have a globalObject.  If it _is_ the main thread, globalObject
    230  // can be null for workers started from JSMs or other non-window contexts,
    231  // sadly.
    232  MOZ_ASSERT_IF(!isOnMainThread,
    233                workerPrivate->IsDedicatedWorker() && globalObject);
    234 
    235  // If we're on the parent thread we might be in a null realm in the
    236  // situation described above when globalObject is null.  Make sure to enter
    237  // the realm of the worker's reflector if there is one.  There might
    238  // not be one if we're just starting to compile the script for this worker.
    239  Maybe<JSAutoRealm> ar;
    240  if (workerPrivate->IsDedicatedWorker() &&
    241      workerPrivate->ParentEventTargetRef() &&
    242      workerPrivate->ParentEventTargetRef()->GetWrapper()) {
    243    JSObject* wrapper = workerPrivate->ParentEventTargetRef()->GetWrapper();
    244 
    245    // If we're on the parent thread and have a reflector and a globalObject,
    246    // then the realms of cx, globalObject, and the worker's reflector
    247    // should all match.
    248    MOZ_ASSERT_IF(globalObject,
    249                  js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx));
    250    MOZ_ASSERT_IF(globalObject,
    251                  js::GetNonCCWObjectRealm(wrapper) ==
    252                      js::GetNonCCWObjectRealm(
    253                          globalObject->GetGlobalJSObjectPreserveColor()));
    254 
    255    // If we're on the parent thread and have a reflector, then our
    256    // JSContext had better be either in the null realm (and hence
    257    // have no globalObject) or in the realm of our reflector.
    258    MOZ_ASSERT(!js::GetContextRealm(cx) ||
    259                   js::GetNonCCWObjectRealm(wrapper) == js::GetContextRealm(cx),
    260               "Must either be in the null compartment or in our reflector "
    261               "compartment");
    262 
    263    ar.emplace(cx, wrapper);
    264  }
    265 
    266  MOZ_ASSERT(!jsapi->HasException());
    267  result = WorkerRun(cx, workerPrivate);
    268  jsapi->ReportException();
    269 
    270  // It would be nice to avoid passing a JSContext to PostRun, but in the case
    271  // of ScriptExecutorRunnable we need to know the current compartment on the
    272  // JSContext (the one we set up based on the global returned from PreRun) so
    273  // that we can sanely do exception reporting.  In particular, we want to make
    274  // sure that we do our JS_SetPendingException while still in that compartment,
    275  // because otherwise we might end up trying to create a cross-compartment
    276  // wrapper when we try to move the JS exception from our runnable's
    277  // ErrorResult to the JSContext, and that's not desirable in this case.
    278  //
    279  // We _could_ skip passing a JSContext here and then in
    280  // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
    281  // and looking at its current compartment.  But that seems like slightly weird
    282  // action-at-a-distance...
    283  //
    284  // In any case, we do NOT try to change the compartment on the JSContext at
    285  // this point; in the one case in which we could do that
    286  // (CompileScriptRunnable) it actually doesn't matter which compartment we're
    287  // in for PostRun.
    288  PostRun(cx, workerPrivate, result);
    289  MOZ_ASSERT(!jsapi->HasException());
    290 
    291  return result ? NS_OK : NS_ERROR_FAILURE;
    292 }
    293 
    294 nsresult WorkerParentThreadRunnable::Cancel() {
    295  LOG(("WorkerParentThreadRunnable::Cancel [%p]", this));
    296  return NS_OK;
    297 }
    298 
    299 WorkerParentControlRunnable::WorkerParentControlRunnable(const char* aName)
    300    : WorkerParentThreadRunnable(aName) {}
    301 
    302 WorkerParentControlRunnable::~WorkerParentControlRunnable() = default;
    303 
    304 nsresult WorkerParentControlRunnable::Cancel() {
    305  LOG(("WorkerParentControlRunnable::Cancel [%p]", this));
    306  if (NS_FAILED(Run())) {
    307    NS_WARNING("WorkerParentControlRunnable::Run() failed.");
    308  }
    309  return NS_OK;
    310 }
    311 
    312 WorkerThreadRunnable::WorkerThreadRunnable(const char* aName)
    313    : WorkerRunnable(aName), mCallingCancelWithinRun(false) {
    314  LOG(("WorkerThreadRunnable::WorkerThreadRunnable [%p]", this));
    315 }
    316 
    317 nsIGlobalObject* WorkerThreadRunnable::DefaultGlobalObject(
    318    WorkerPrivate* aWorkerPrivate) const {
    319  MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
    320  if (IsDebuggerRunnable()) {
    321    return aWorkerPrivate->DebuggerGlobalScope();
    322  }
    323  return aWorkerPrivate->GlobalScope();
    324 }
    325 
    326 bool WorkerThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
    327  MOZ_ASSERT(aWorkerPrivate);
    328 #ifdef DEBUG
    329  aWorkerPrivate->AssertIsOnParentThread();
    330 #endif
    331  return true;
    332 }
    333 
    334 bool WorkerThreadRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) {
    335  LOG(("WorkerThreadRunnable::DispatchInternal [%p]", this));
    336  RefPtr<WorkerThreadRunnable> runnable(this);
    337  return NS_SUCCEEDED(aWorkerPrivate->Dispatch(runnable.forget()));
    338 }
    339 
    340 void WorkerThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    341                                        bool aDispatchResult) {
    342  MOZ_ASSERT(aWorkerPrivate);
    343 #ifdef DEBUG
    344  aWorkerPrivate->AssertIsOnParentThread();
    345 #endif
    346 }
    347 
    348 bool WorkerThreadRunnable::PreRun(WorkerPrivate* aWorkerPrivate) {
    349  return true;
    350 }
    351 
    352 void WorkerThreadRunnable::PostRun(JSContext* aCx,
    353                                   WorkerPrivate* aWorkerPrivate,
    354                                   bool aRunResult) {
    355  MOZ_ASSERT(aCx);
    356  MOZ_ASSERT(aWorkerPrivate);
    357 
    358 #ifdef DEBUG
    359  aWorkerPrivate->AssertIsOnWorkerThread();
    360 #endif
    361 }
    362 
    363 NS_IMETHODIMP
    364 WorkerThreadRunnable::Run() {
    365  LOG(("WorkerThreadRunnable::Run [%p]", this));
    366 
    367  // The Worker initialization fails, there is no valid WorkerPrivate and
    368  // WorkerJSContext to run this WorkerThreadRunnable.
    369  if (mCleanPreStartDispatching) {
    370    LOG(("Clean the pre-start dispatched WorkerThreadRunnable [%p]", this));
    371    return NS_OK;
    372  }
    373 
    374  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    375  MOZ_ASSERT_DEBUG_OR_FUZZING(workerPrivate);
    376 #ifdef DEBUG
    377  workerPrivate->AssertIsOnWorkerThread();
    378 #endif
    379 
    380  if (!mCallingCancelWithinRun &&
    381      workerPrivate->CancelBeforeWorkerScopeConstructed()) {
    382    mCallingCancelWithinRun = true;
    383    Cancel();
    384    mCallingCancelWithinRun = false;
    385    return NS_OK;
    386  }
    387 
    388  bool result = PreRun(workerPrivate);
    389  if (!result) {
    390    workerPrivate->AssertIsOnWorkerThread();
    391    MOZ_ASSERT(!JS_IsExceptionPending(workerPrivate->GetJSContext()));
    392    // We can't enter a useful realm on the JSContext here; just pass it
    393    // in as-is.
    394    PostRun(workerPrivate->GetJSContext(), workerPrivate, false);
    395    return NS_ERROR_FAILURE;
    396  }
    397 
    398  // Track down the appropriate global, if any, to use for the AutoEntryScript.
    399  nsCOMPtr<nsIGlobalObject> globalObject =
    400      workerPrivate->GetCurrentEventLoopGlobal();
    401  if (!globalObject) {
    402    globalObject = DefaultGlobalObject(workerPrivate);
    403    // Our worker thread may not be in a good state here if there is no
    404    // JSContext avaliable.  The way this manifests itself is that
    405    // globalObject ends up null (though it's not clear to me how we can be
    406    // running runnables at all when default globalObject(DebuggerGlobalScope
    407    // for debugger runnable, and GlobalScope for normal runnables) is returning
    408    // false!) and then when we try to init the AutoJSAPI either
    409    // CycleCollectedJSContext::Get() returns null or it has a null JSContext.
    410    // In any case, we used to have a check for
    411    // GetCurrentWorkerThreadJSContext() being non-null here and that seems to
    412    // avoid the problem, so let's keep doing that check even if we don't need
    413    // the JSContext here at all.
    414    if (NS_WARN_IF(!globalObject && !GetCurrentWorkerThreadJSContext())) {
    415      return NS_ERROR_FAILURE;
    416    }
    417  }
    418 
    419  // We might run script as part of WorkerRun, so we need an AutoEntryScript.
    420  // This is part of the HTML spec for workers at:
    421  // http://www.whatwg.org/specs/web-apps/current-work/#run-a-worker
    422  // If we don't have a globalObject we have to use an AutoJSAPI instead, but
    423  // this is OK as we won't be running script in these circumstances.
    424  Maybe<mozilla::dom::AutoJSAPI> maybeJSAPI;
    425  Maybe<mozilla::dom::AutoEntryScript> aes;
    426  JSContext* cx;
    427  AutoJSAPI* jsapi;
    428  if (globalObject) {
    429    aes.emplace(globalObject, "Worker runnable", false);
    430    jsapi = aes.ptr();
    431    cx = aes->cx();
    432  } else {
    433    maybeJSAPI.emplace();
    434    maybeJSAPI->Init();
    435    jsapi = maybeJSAPI.ptr();
    436    cx = jsapi->cx();
    437  }
    438 
    439  MOZ_ASSERT(!jsapi->HasException());
    440  result = WorkerRun(cx, workerPrivate);
    441  jsapi->ReportException();
    442 
    443  // We can't even assert that this didn't create our global, since in the case
    444  // of CompileScriptRunnable it _does_.
    445 
    446  // It would be nice to avoid passing a JSContext to PostRun, but in the case
    447  // of ScriptExecutorRunnable we need to know the current compartment on the
    448  // JSContext (the one we set up based on the global returned from PreRun) so
    449  // that we can sanely do exception reporting.  In particular, we want to make
    450  // sure that we do our JS_SetPendingException while still in that compartment,
    451  // because otherwise we might end up trying to create a cross-compartment
    452  // wrapper when we try to move the JS exception from our runnable's
    453  // ErrorResult to the JSContext, and that's not desirable in this case.
    454  //
    455  // We _could_ skip passing a JSContext here and then in
    456  // ScriptExecutorRunnable::PostRun end up grabbing it from the WorkerPrivate
    457  // and looking at its current compartment.  But that seems like slightly weird
    458  // action-at-a-distance...
    459  //
    460  // In any case, we do NOT try to change the compartment on the JSContext at
    461  // this point; in the one case in which we could do that
    462  // (CompileScriptRunnable) it actually doesn't matter which compartment we're
    463  // in for PostRun.
    464  PostRun(cx, workerPrivate, result);
    465  MOZ_ASSERT(!jsapi->HasException());
    466 
    467  return result ? NS_OK : NS_ERROR_FAILURE;
    468 }
    469 
    470 nsresult WorkerThreadRunnable::Cancel() {
    471  LOG(("WorkerThreadRunnable::Cancel [%p]", this));
    472  return NS_OK;
    473 }
    474 
    475 void WorkerDebuggerRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    476                                          bool aDispatchResult) {}
    477 
    478 WorkerSyncRunnable::WorkerSyncRunnable(nsIEventTarget* aSyncLoopTarget,
    479                                       const char* aName)
    480    : WorkerThreadRunnable(aName), mSyncLoopTarget(aSyncLoopTarget) {}
    481 
    482 WorkerSyncRunnable::WorkerSyncRunnable(
    483    nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, const char* aName)
    484    : WorkerThreadRunnable(aName),
    485      mSyncLoopTarget(std::move(aSyncLoopTarget)) {}
    486 
    487 WorkerSyncRunnable::~WorkerSyncRunnable() = default;
    488 
    489 bool WorkerSyncRunnable::DispatchInternal(WorkerPrivate* aWorkerPrivate) {
    490  if (mSyncLoopTarget) {
    491 #ifdef DEBUG
    492    aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
    493 #endif
    494    RefPtr<WorkerSyncRunnable> runnable(this);
    495    return NS_SUCCEEDED(
    496        mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
    497  }
    498 
    499  return WorkerThreadRunnable::DispatchInternal(aWorkerPrivate);
    500 }
    501 
    502 void MainThreadWorkerSyncRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    503                                                bool aDispatchResult) {}
    504 
    505 MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable(
    506    nsCOMPtr<nsIEventTarget>&& aSyncLoopTarget, nsresult aResult)
    507    : WorkerSyncRunnable(std::move(aSyncLoopTarget)), mResult(aResult) {
    508  LOG(("MainThreadStopSyncLoopRunnable::MainThreadStopSyncLoopRunnable [%p]",
    509       this));
    510 
    511  AssertIsOnMainThread();
    512 }
    513 
    514 nsresult MainThreadStopSyncLoopRunnable::Cancel() {
    515  LOG(("MainThreadStopSyncLoopRunnable::Cancel [%p]", this));
    516  nsresult rv = Run();
    517  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Run() failed");
    518 
    519  return rv;
    520 }
    521 
    522 bool MainThreadStopSyncLoopRunnable::WorkerRun(JSContext* aCx,
    523                                               WorkerPrivate* aWorkerPrivate) {
    524  aWorkerPrivate->AssertIsOnWorkerThread();
    525  MOZ_ASSERT(mSyncLoopTarget);
    526 
    527  nsCOMPtr<nsIEventTarget> syncLoopTarget;
    528  mSyncLoopTarget.swap(syncLoopTarget);
    529 
    530  aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult);
    531  return true;
    532 }
    533 
    534 bool MainThreadStopSyncLoopRunnable::DispatchInternal(
    535    WorkerPrivate* aWorkerPrivate) {
    536  MOZ_ASSERT(mSyncLoopTarget);
    537 #ifdef DEBUG
    538  aWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget);
    539 #endif
    540  RefPtr<MainThreadStopSyncLoopRunnable> runnable(this);
    541  return NS_SUCCEEDED(
    542      mSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL));
    543 }
    544 
    545 void MainThreadStopSyncLoopRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    546                                                  bool aDispatchResult) {}
    547 
    548 WorkerControlRunnable::WorkerControlRunnable(const char* aName)
    549    : WorkerThreadRunnable(aName) {}
    550 
    551 nsresult WorkerControlRunnable::Cancel() {
    552  LOG(("WorkerControlRunnable::Cancel [%p]", this));
    553  if (NS_FAILED(Run())) {
    554    NS_WARNING("WorkerControlRunnable::Run() failed.");
    555  }
    556 
    557  return NS_OK;
    558 }
    559 
    560 WorkerMainThreadRunnable::WorkerMainThreadRunnable(
    561    WorkerPrivate* aWorkerPrivate, const nsACString& aTelemetryKey,
    562    const char* const aName)
    563    : mozilla::Runnable("dom::WorkerMainThreadRunnable"),
    564      mTelemetryKey(aTelemetryKey),
    565      mName(aName) {
    566  aWorkerPrivate->AssertIsOnWorkerThread();
    567 }
    568 
    569 WorkerMainThreadRunnable::~WorkerMainThreadRunnable() = default;
    570 
    571 void WorkerMainThreadRunnable::Dispatch(WorkerPrivate* aWorkerPrivate,
    572                                        WorkerStatus aFailStatus,
    573                                        mozilla::ErrorResult& aRv) {
    574  aWorkerPrivate->AssertIsOnWorkerThread();
    575 
    576  TimeStamp startTime = TimeStamp::NowLoRes();
    577 
    578  RefPtr<StrongWorkerRef> workerRef;
    579  if (aFailStatus < Canceling) {
    580    // Nothing but logging debugging messages in the WorkerRef's
    581    // shutdown callback.
    582    // Stopping syncLoop in the shutdown callback could cause memory leaks or
    583    // UAF when the main thread job completes.
    584    workerRef =
    585        StrongWorkerRef::Create(aWorkerPrivate, mName, [self = RefPtr{this}]() {
    586          LOG(
    587              ("WorkerMainThreadRunnable::Dispatch [%p](%s) Worker starts to "
    588               "shutdown while underlying SyncLoop is still running",
    589               self.get(), self->mName));
    590        });
    591  } else {
    592    LOG(
    593        ("WorkerMainThreadRunnable::Dispatch [%p](%s) Creating a SyncLoop when"
    594         "the Worker is shutting down",
    595         this, mName));
    596    workerRef = StrongWorkerRef::CreateForcibly(aWorkerPrivate, mName);
    597  }
    598  if (!workerRef) {
    599    // WorkerRef creation can fail if the worker is not in a valid status.
    600    aRv.ThrowInvalidStateError("The worker has already shut down");
    601    return;
    602  }
    603  mWorkerRef = MakeRefPtr<ThreadSafeWorkerRef>(workerRef);
    604 
    605  AutoSyncLoopHolder syncLoop(aWorkerPrivate, aFailStatus);
    606 
    607  mSyncLoopTarget = syncLoop.GetSerialEventTarget();
    608  if (!mSyncLoopTarget) {
    609    // SyncLoop creation can fail if the worker is shutting down.
    610    aRv.ThrowInvalidStateError("The worker is shutting down");
    611    return;
    612  }
    613 
    614  DebugOnly<nsresult> rv = aWorkerPrivate->DispatchToMainThread(this);
    615  MOZ_ASSERT(
    616      NS_SUCCEEDED(rv),
    617      "Should only fail after xpcom-shutdown-threads and we're gone by then");
    618 
    619  bool success = NS_SUCCEEDED(syncLoop.Run());
    620 
    621  // syncLoop is done, release WorkerRef to unblock shutdown.
    622  mWorkerRef = nullptr;
    623 
    624  glean::workers::sync_worker_operation.Get(mTelemetryKey)
    625      .AccumulateRawDuration(TimeStamp::NowLoRes() - startTime);
    626 
    627  (void)startTime;  // Shut the compiler up.
    628 
    629  if (!success) {
    630    aRv.ThrowUncatchableException();
    631  }
    632 }
    633 
    634 NS_IMETHODIMP
    635 WorkerMainThreadRunnable::Run() {
    636  AssertIsOnMainThread();
    637 
    638  bool runResult = MainThreadRun();
    639 
    640  RefPtr<MainThreadStopSyncLoopRunnable> response =
    641      new MainThreadStopSyncLoopRunnable(std::move(mSyncLoopTarget),
    642                                         runResult ? NS_OK : NS_ERROR_FAILURE);
    643 
    644  MOZ_ASSERT(mWorkerRef);
    645  MOZ_ALWAYS_TRUE(response->Dispatch(mWorkerRef->Private()));
    646 
    647  return NS_OK;
    648 }
    649 
    650 bool WorkerSameThreadRunnable::PreDispatch(WorkerPrivate* aWorkerPrivate) {
    651  aWorkerPrivate->AssertIsOnWorkerThread();
    652  return true;
    653 }
    654 
    655 void WorkerSameThreadRunnable::PostDispatch(WorkerPrivate* aWorkerPrivate,
    656                                            bool aDispatchResult) {
    657  aWorkerPrivate->AssertIsOnWorkerThread();
    658 }
    659 
    660 WorkerProxyToMainThreadRunnable::WorkerProxyToMainThreadRunnable()
    661    : mozilla::Runnable("dom::WorkerProxyToMainThreadRunnable") {}
    662 
    663 WorkerProxyToMainThreadRunnable::~WorkerProxyToMainThreadRunnable() = default;
    664 
    665 bool WorkerProxyToMainThreadRunnable::Dispatch(WorkerPrivate* aWorkerPrivate) {
    666  MOZ_ASSERT(aWorkerPrivate);
    667  aWorkerPrivate->AssertIsOnWorkerThread();
    668 
    669  RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
    670      aWorkerPrivate, "WorkerProxyToMainThreadRunnable");
    671  if (NS_WARN_IF(!workerRef)) {
    672    RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
    673    return false;
    674  }
    675 
    676  MOZ_ASSERT(!mWorkerRef);
    677  mWorkerRef = new ThreadSafeWorkerRef(workerRef);
    678 
    679  if (ForMessaging()
    680          ? NS_WARN_IF(NS_FAILED(
    681                aWorkerPrivate->DispatchToMainThreadForMessaging(this)))
    682          : NS_WARN_IF(NS_FAILED(aWorkerPrivate->DispatchToMainThread(this)))) {
    683    ReleaseWorker();
    684    RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
    685    return false;
    686  }
    687 
    688  return true;
    689 }
    690 
    691 NS_IMETHODIMP
    692 WorkerProxyToMainThreadRunnable::Run() {
    693  AssertIsOnMainThread();
    694  RunOnMainThread(mWorkerRef->Private());
    695  PostDispatchOnMainThread();
    696  return NS_OK;
    697 }
    698 
    699 void WorkerProxyToMainThreadRunnable::PostDispatchOnMainThread() {
    700  class ReleaseRunnable final : public MainThreadWorkerControlRunnable {
    701    RefPtr<WorkerProxyToMainThreadRunnable> mRunnable;
    702 
    703   public:
    704    explicit ReleaseRunnable(WorkerProxyToMainThreadRunnable* aRunnable)
    705        : MainThreadWorkerControlRunnable("ReleaseRunnable"),
    706          mRunnable(aRunnable) {
    707      MOZ_ASSERT(aRunnable);
    708    }
    709 
    710    virtual nsresult Cancel() override {
    711      MOZ_ASSERT(GetCurrentThreadWorkerPrivate());
    712      (void)WorkerRun(nullptr, GetCurrentThreadWorkerPrivate());
    713      return NS_OK;
    714    }
    715 
    716    virtual bool WorkerRun(JSContext* aCx,
    717                           WorkerPrivate* aWorkerPrivate) override {
    718      MOZ_ASSERT(aWorkerPrivate);
    719      aWorkerPrivate->AssertIsOnWorkerThread();
    720 
    721      if (mRunnable) {
    722        mRunnable->RunBackOnWorkerThreadForCleanup(aWorkerPrivate);
    723 
    724        // Let's release the worker thread.
    725        mRunnable->ReleaseWorker();
    726        mRunnable = nullptr;
    727      }
    728 
    729      return true;
    730    }
    731 
    732   private:
    733    ~ReleaseRunnable() = default;
    734  };
    735 
    736  RefPtr<WorkerControlRunnable> runnable = new ReleaseRunnable(this);
    737  (void)NS_WARN_IF(!runnable->Dispatch(mWorkerRef->Private()));
    738 }
    739 
    740 void WorkerProxyToMainThreadRunnable::ReleaseWorker() { mWorkerRef = nullptr; }
    741 
    742 }  // namespace mozilla::dom