tor-browser

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

WebTaskScheduler.cpp (23478B)


      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 "WebTaskScheduler.h"
      8 
      9 #include "WebTaskSchedulerMainThread.h"
     10 #include "WebTaskSchedulerWorker.h"
     11 #include "mozilla/dom/TimeoutManager.h"
     12 #include "mozilla/dom/WorkerPrivate.h"
     13 #include "nsGlobalWindowInner.h"
     14 #include "nsTHashMap.h"
     15 
     16 namespace mozilla::dom {
     17 
     18 // Keeps track of all the existings schedulers that
     19 // share the same event loop.
     20 MOZ_RUNINIT static LinkedList<WebTaskScheduler> gWebTaskSchedulersMainThread;
     21 
     22 static Atomic<uint64_t> gWebTaskEnqueueOrder(0);
     23 
     24 // According to
     25 // https://github.com/WICG/scheduling-apis/issues/113#issuecomment-2596102676,
     26 // tasks with User_blocking or User_visible needs to run before timers.
     27 static bool IsNormalOrHighPriority(TaskPriority aPriority) {
     28  return aPriority == TaskPriority::User_blocking ||
     29         aPriority == TaskPriority::User_visible;
     30 }
     31 
     32 inline void ImplCycleCollectionTraverse(
     33    nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue,
     34    const char* aName, uint32_t aFlags = 0) {
     35  ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags);
     36 }
     37 
     38 inline void ImplCycleCollectionTraverse(
     39    nsCycleCollectionTraversalCallback& aCallback,
     40    const WebTaskQueueHashKey& aField, const char* aName, uint32_t aFlags = 0) {
     41  const WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey();
     42  if (typeKey.is<RefPtr<TaskSignal>>()) {
     43    ImplCycleCollectionTraverse(aCallback, typeKey.as<RefPtr<TaskSignal>>(),
     44                                aName, aFlags);
     45  }
     46 }
     47 
     48 inline void ImplCycleCollectionUnlink(WebTaskQueueHashKey& aField) {
     49  WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey();
     50  if (typeKey.is<RefPtr<TaskSignal>>()) {
     51    ImplCycleCollectionUnlink(typeKey.as<RefPtr<TaskSignal>>());
     52  }
     53 }
     54 
     55 NS_IMPL_CYCLE_COLLECTION_CLASS(WebTaskSchedulingState)
     56 
     57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTaskSchedulingState)
     58  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortSource, mPrioritySource);
     59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     60 
     61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTaskSchedulingState)
     62  NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortSource, mPrioritySource);
     63 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     64 
     65 NS_IMPL_CYCLE_COLLECTION_CLASS(WebTask)
     66 
     67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTask)
     68  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
     69  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
     70  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskQueueHashKey)
     71  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSchedulingState)
     72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     73 
     74 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTask)
     75  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
     76  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
     77  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskQueueHashKey)
     78  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSchedulingState)
     79  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
     80 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     81 
     82 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTask)
     83 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTask)
     84 
     85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTask)
     86  NS_INTERFACE_MAP_ENTRY(nsISupports)
     87 NS_INTERFACE_MAP_END
     88 
     89 NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler)
     90 
     91 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler)
     92  NS_INTERFACE_MAP_ENTRY(nsISupports)
     93 NS_INTERFACE_MAP_END
     94 
     95 NS_IMPL_CYCLE_COLLECTING_ADDREF(DelayedWebTaskHandler)
     96 NS_IMPL_CYCLE_COLLECTING_RELEASE(DelayedWebTaskHandler)
     97 
     98 WebTask::WebTask(uint32_t aEnqueueOrder,
     99                 const Maybe<SchedulerPostTaskCallback&>& aCallback,
    100                 WebTaskSchedulingState* aSchedlingState, Promise* aPromise,
    101                 WebTaskScheduler* aWebTaskScheduler,
    102                 const WebTaskQueueHashKey& aHashKey)
    103    : mEnqueueOrder(aEnqueueOrder),
    104      mPromise(aPromise),
    105      mHasScheduled(false),
    106      mSchedulingState(aSchedlingState),
    107      mScheduler(aWebTaskScheduler),
    108      mWebTaskQueueHashKey(aHashKey) {
    109  if (aCallback.isSome()) {
    110    mCallback = &aCallback.ref();
    111  }
    112 }
    113 
    114 void WebTask::RunAbortAlgorithm() {
    115  // no-op if WebTask::Run has been called already
    116  if (mPromise->State() == Promise::PromiseState::Pending) {
    117    // There are two things that can keep a WebTask alive, either the abort
    118    // signal or WebTaskQueue.
    119    // It's possible that this task get cleared out from the WebTaskQueue first,
    120    // and then the abort signal get aborted. For example, the callback function
    121    // was async and there's a signal.abort() call in the callback.
    122    if (isInList()) {
    123      remove();
    124      MOZ_ASSERT(mScheduler);
    125      if (HasScheduled()) {
    126        mScheduler->NotifyTaskWillBeRunOrAborted(this);
    127      }
    128    }
    129 
    130    AutoJSAPI jsapi;
    131    if (!jsapi.Init(mPromise->GetGlobalObject())) {
    132      mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
    133    } else {
    134      JSContext* cx = jsapi.cx();
    135      JS::Rooted<JS::Value> reason(cx);
    136      Signal()->GetReason(cx, &reason);
    137      mPromise->MaybeReject(reason);
    138    }
    139  }
    140 
    141  MOZ_ASSERT(!isInList());
    142 }
    143 
    144 bool WebTask::Run() {
    145  MOZ_ASSERT(HasScheduled());
    146  MOZ_ASSERT(mScheduler);
    147  remove();
    148 
    149  mScheduler->NotifyTaskWillBeRunOrAborted(this);
    150  ClearWebTaskScheduler();
    151 
    152  if (!mCallback) {
    153    // Scheduler.yield
    154    mPromise->MaybeResolveWithUndefined();
    155    MOZ_ASSERT(!isInList());
    156    return true;
    157  }
    158 
    159  MOZ_ASSERT(mSchedulingState);
    160 
    161  ErrorResult error;
    162 
    163  nsIGlobalObject* global = mPromise->GetGlobalObject();
    164  if (!global || global->IsDying()) {
    165    return false;
    166  }
    167 
    168  // 11.2.2 Set event loop’s current scheduling state to state.
    169  global->SetWebTaskSchedulingState(mSchedulingState);
    170 
    171  AutoJSAPI jsapi;
    172  if (!jsapi.Init(global)) {
    173    return false;
    174  }
    175 
    176  JS::Rooted<JS::Value> returnVal(jsapi.cx());
    177 
    178  MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
    179 
    180  MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask",
    181                                 CallbackFunction::eRethrowExceptions);
    182 
    183  // 11.2.4 Set event loop’s current scheduling state to null.
    184  global->SetWebTaskSchedulingState(nullptr);
    185 
    186  error.WouldReportJSException();
    187 
    188 #ifdef DEBUG
    189  Promise::PromiseState promiseState = mPromise->State();
    190 
    191  // If the state is Rejected, it means the above Call triggers the
    192  // RunAbortAlgorithm method and rejected the promise
    193  MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending,
    194                promiseState == Promise::PromiseState::Rejected);
    195 #endif
    196 
    197  if (error.Failed()) {
    198    if (!error.IsUncatchableException()) {
    199      mPromise->MaybeReject(std::move(error));
    200    } else {
    201      error.SuppressException();
    202    }
    203  } else {
    204    mPromise->MaybeResolve(returnVal);
    205  }
    206 
    207  MOZ_ASSERT(!isInList());
    208  return true;
    209 }
    210 
    211 inline void ImplCycleCollectionUnlink(
    212    nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField) {
    213  aField.Clear();
    214 }
    215 
    216 inline void ImplCycleCollectionTraverse(
    217    nsCycleCollectionTraversalCallback& aCallback,
    218    nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField, const char* aName,
    219    uint32_t aFlags = 0) {
    220  for (auto& entry : aField) {
    221    ImplCycleCollectionTraverse(
    222        aCallback, entry.GetKey(),
    223        "nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueueHashKey",
    224        aFlags);
    225    ImplCycleCollectionTraverse(
    226        aCallback, *entry.GetModifiableData(),
    227        "nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueue", aFlags);
    228  }
    229 }
    230 
    231 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent, mWebTaskQueues)
    232 
    233 /* static */
    234 already_AddRefed<WebTaskSchedulerMainThread>
    235 WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
    236  RefPtr<WebTaskSchedulerMainThread> scheduler =
    237      new WebTaskSchedulerMainThread(aWindow->AsGlobal());
    238  gWebTaskSchedulersMainThread.insertBack(scheduler);
    239  return scheduler.forget();
    240 }
    241 
    242 already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
    243    WorkerPrivate* aWorkerPrivate) {
    244  aWorkerPrivate->AssertIsOnWorkerThread();
    245  RefPtr<WebTaskSchedulerWorker> scheduler =
    246      WebTaskSchedulerWorker::Create(aWorkerPrivate);
    247  return scheduler.forget();
    248 }
    249 
    250 WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
    251    : mParent(aParent) {
    252  MOZ_ASSERT(aParent);
    253 }
    254 
    255 JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
    256                                       JS::Handle<JSObject*> aGivenProto) {
    257  return Scheduler_Binding::Wrap(cx, this, aGivenProto);
    258 }
    259 
    260 static bool ShouldRejectPromiseWithReasonCausedByAbortSignal(
    261    AbortSignal& aAbortSignal, nsIGlobalObject* aGlobal, Promise& aPromise) {
    262  MOZ_ASSERT(aGlobal);
    263  if (!aAbortSignal.Aborted()) {
    264    return false;
    265  }
    266 
    267  AutoJSAPI jsapi;
    268  if (!jsapi.Init(aGlobal)) {
    269    aPromise.MaybeRejectWithNotSupportedError(
    270        "Failed to initialize the JS context");
    271    return true;
    272  }
    273 
    274  JSContext* cx = jsapi.cx();
    275  JS::Rooted<JS::Value> reason(cx);
    276  aAbortSignal.GetReason(cx, &reason);
    277  aPromise.MaybeReject(reason);
    278  return true;
    279 }
    280 
    281 // https://wicg.github.io/scheduling-apis/#sec-scheduler-alg-scheduling-tasks-and-continuations
    282 already_AddRefed<Promise> WebTaskScheduler::PostTask(
    283    SchedulerPostTaskCallback& aCallback,
    284    const SchedulerPostTaskOptions& aOptions) {
    285  const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal;
    286  const Optional<TaskPriority>& taskPriority = aOptions.mPriority;
    287 
    288  ErrorResult rv;
    289  // Instead of making WebTaskScheduler::PostTask throws, we always
    290  // create the promise and return it. This is because we need to
    291  // create the promise explicitly to be able to reject it with
    292  // signal's reason.
    293  RefPtr<Promise> promise = Promise::Create(mParent, rv);
    294  if (rv.Failed()) {
    295    return nullptr;
    296  }
    297 
    298  nsIGlobalObject* global = GetParentObject();
    299  if (!global || global->IsDying()) {
    300    promise->MaybeRejectWithNotSupportedError("Current window is detached");
    301    return promise.forget();
    302  }
    303 
    304  // 4. Let state be a new scheduling state.
    305  RefPtr<WebTaskSchedulingState> newState = new WebTaskSchedulingState();
    306  AbortSignal* signalValue = nullptr;
    307  if (taskSignal.WasPassed()) {
    308    signalValue = &taskSignal.Value();
    309    // 3. If signal is not null and it is aborted, then reject result with
    310    // signal’s abort reason and return result.
    311    if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*signalValue, global,
    312                                                         *promise)) {
    313      return promise.forget();
    314    }
    315 
    316    // 5. Set state’s abort source to signal.
    317    newState->SetAbortSource(signalValue);
    318  }
    319 
    320  if (taskPriority.WasPassed()) {
    321    // 6. If options["priority"] exists, then set state’s priority source to the
    322    // result of creating a fixed priority unabortable task signal given
    323    // options["priority"]
    324    newState->SetPrioritySource(
    325        TaskSignal::Create(GetParentObject(), taskPriority.Value()));
    326  } else if (signalValue && signalValue->IsTaskSignal()) {
    327    // 7. Otherwise if signal is not null and implements the TaskSignal
    328    // interface, then set state’s priority source to signal.
    329    newState->SetPrioritySource(
    330        do_AddRef(static_cast<TaskSignal*>(signalValue)));
    331  }
    332 
    333  if (!newState->GetPrioritySource()) {
    334    // 8. If state’s priority source is null, then set state’s priority
    335    // source to the result of creating a fixed priority unabortable task
    336    // signal given "user-visible".
    337    newState->SetPrioritySource(
    338        TaskSignal::Create(GetParentObject(), TaskPriority::User_visible));
    339  }
    340 
    341  MOZ_ASSERT(newState->GetPrioritySource());
    342 
    343  // 9. Let handle be the result of creating a task handle given result and
    344  // signal.
    345  // 10. If signal is not null, then add handle’s abort steps to signal.
    346  // 11. Let enqueueSteps be the following steps...
    347  RefPtr<WebTask> task = CreateTask(signalValue, newState->GetPrioritySource(),
    348                                    taskPriority, false /* aIsContinuation */,
    349                                    SomeRef(aCallback), newState, promise);
    350 
    351  const TaskSignal* finalPrioritySource = newState->GetPrioritySource();
    352  // 12. Let delay be options["delay"].
    353  const uint64_t delay = aOptions.mDelay;
    354 
    355  // 13. If delay is greater than 0, then run steps after a timeout given
    356  // scheduler’s relevant global object, "scheduler-postTask", delay, and the
    357  // following steps...
    358  if (delay > 0) {
    359    nsresult rv = SetTimeoutForDelayedTask(
    360        task, delay,
    361        GetEventQueuePriority(finalPrioritySource->Priority(),
    362                              false /* aIsContinuation */));
    363    if (NS_FAILED(rv)) {
    364      promise->MaybeRejectWithUnknownError(
    365          "Failed to setup timeout for delayed task");
    366    }
    367    return promise.forget();
    368  }
    369 
    370  // 14. Otherwise, run enqueueSteps.
    371  if (!DispatchTask(task, GetEventQueuePriority(finalPrioritySource->Priority(),
    372                                                false /* aIsContinuation */))) {
    373    MOZ_ASSERT(task->isInList());
    374    task->remove();
    375 
    376    promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
    377    return promise.forget();
    378  }
    379 
    380  return promise.forget();
    381 }
    382 
    383 // https://wicg.github.io/scheduling-apis/#schedule-a-yield-continuation
    384 already_AddRefed<Promise> WebTaskScheduler::YieldImpl() {
    385  ErrorResult rv;
    386  // 1. Let result be a new promise.
    387  RefPtr<Promise> promise = Promise::Create(mParent, rv);
    388  if (rv.Failed()) {
    389    return nullptr;
    390  }
    391 
    392  nsIGlobalObject* global = GetParentObject();
    393  if (!global || global->IsDying()) {
    394    promise->MaybeRejectWithNotSupportedError("Current window is detached");
    395    return promise.forget();
    396  }
    397 
    398  RefPtr<AbortSignal> abortSource;
    399  RefPtr<TaskSignal> prioritySource;
    400  // 2. Let inheritedState be the scheduler’s relevant agent's event loop's
    401  // current scheduling state.
    402  if (auto* schedulingState = global->GetWebTaskSchedulingState()) {
    403    // 3. Let abortSource be inheritedState’s abort source if inheritedState is
    404    // not null, or otherwise null.
    405    abortSource = schedulingState->GetAbortSource();
    406    // 5. Let prioritySource be inheritedState’s priority source if
    407    // inheritedState is not null, or otherwise null.
    408    prioritySource = schedulingState->GetPrioritySource();
    409  }
    410 
    411  if (abortSource) {
    412    // 4. If abortSource is not null and abortSource is aborted, then reject
    413    // result with abortSource’s abort reason and return result.
    414    if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*abortSource, global,
    415                                                         *promise)) {
    416      return promise.forget();
    417    }
    418  }
    419 
    420  if (!prioritySource) {
    421    // 6. If prioritySource is null, then set prioritySource to the result of
    422    // creating a fixed priority unabortable task signal given "user-visible".
    423    prioritySource =
    424        TaskSignal::Create(GetParentObject(), TaskPriority::User_visible);
    425  }
    426 
    427  // 7. Let handle be the result of creating a task handle given result and
    428  // abortSource.
    429  // 8. If abortSource is not null, then add handle’s abort steps to
    430  // abortSource.
    431  // 9. Set handle’s queue to the result of selecting the scheduler task queue
    432  // for scheduler given prioritySource and true.
    433  // 10. Schedule a task to invoke an algorithm for scheduler given handle and
    434  // the following steps:
    435  RefPtr<WebTask> task =
    436      CreateTask(abortSource, prioritySource, {}, true /* aIsContinuation */,
    437                 Nothing(), nullptr, promise);
    438 
    439  EventQueuePriority eventQueuePriority = GetEventQueuePriority(
    440      prioritySource->Priority(), true /* aIsContinuation */);
    441  if (!DispatchTask(task, eventQueuePriority)) {
    442    MOZ_ASSERT(task->isInList());
    443    // CreateTask adds the task to WebTaskScheduler's queue, so we
    444    // need to remove from it when we failed to dispatch the runnable.
    445    task->remove();
    446 
    447    promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
    448    return promise.forget();
    449  }
    450 
    451  return promise.forget();
    452 }
    453 
    454 already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
    455    AbortSignal* aAbortSignal, TaskSignal* aTaskSignal,
    456    const Optional<TaskPriority>& aPriority, bool aIsContinuation,
    457    const Maybe<SchedulerPostTaskCallback&>& aCallback,
    458    WebTaskSchedulingState* aSchedulingState, Promise* aPromise) {
    459  WebTaskScheduler::SelectedTaskQueueData selectedTaskQueueData =
    460      SelectTaskQueue(aTaskSignal, aPriority, aIsContinuation);
    461 
    462  gWebTaskEnqueueOrder += 1;
    463  RefPtr<WebTask> task =
    464      new WebTask(gWebTaskEnqueueOrder, aCallback, aSchedulingState, aPromise,
    465                  this, selectedTaskQueueData.mSelectedQueueHashKey);
    466 
    467  selectedTaskQueueData.mSelectedTaskQueue.AddTask(task);
    468 
    469  if (aAbortSignal) {
    470    task->Follow(aAbortSignal);
    471  }
    472 
    473  return task.forget();
    474 }
    475 
    476 bool WebTaskScheduler::DispatchTask(WebTask* aTask,
    477                                    EventQueuePriority aPriority) {
    478  if (!DispatchEventLoopRunnable(aPriority)) {
    479    return false;
    480  }
    481  MOZ_ASSERT(!aTask->HasScheduled());
    482 
    483  auto taskQueue = mWebTaskQueues.Lookup(aTask->TaskQueueHashKey());
    484  MOZ_DIAGNOSTIC_ASSERT(taskQueue);
    485 
    486  if (IsNormalOrHighPriority(aTask->Priority()) &&
    487      !taskQueue->HasScheduledTasks()) {
    488    // This is the first task that is scheduled for this queue.
    489    IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
    490  }
    491 
    492  aTask->SetHasScheduled();
    493  return true;
    494 }
    495 
    496 // https://wicg.github.io/scheduling-apis/#select-the-next-scheduler-task-queue-from-all-schedulers
    497 WebTask* WebTaskScheduler::GetNextTask(bool aIsMainThread) {
    498  // 1. Let queues be an empty set.
    499  AutoTArray<nsTArray<WebTaskQueue*>, WebTaskQueue::EffectivePriorityCount>
    500      allQueues;
    501  allQueues.SetLength(WebTaskQueue::EffectivePriorityCount);
    502 
    503  auto processScheduler = [&](WebTaskScheduler& aScheduler) {
    504    for (auto iter = aScheduler.GetWebTaskQueues().Iter(); !iter.Done();
    505         iter.Next()) {
    506      auto& queue = iter.Data();
    507      if (queue.HasScheduledTasks()) {
    508        const WebTaskQueueHashKey& key = iter.Key();
    509        nsTArray<WebTaskQueue*>& queuesForThisPriority =
    510            allQueues[key.EffectivePriority()];
    511        queuesForThisPriority.AppendElement(&queue);
    512      }
    513    }
    514  };
    515  // 3. For each scheduler in schedulers, extend queues with the result of
    516  // getting the runnable task queues for scheduler.
    517  if (aIsMainThread) {
    518    // 2. Let schedulers be the set of all Scheduler objects whose relevant
    519    // agent’s event loop is event loop and that have a runnable task.
    520    for (const auto& scheduler : gWebTaskSchedulersMainThread) {
    521      processScheduler(*scheduler);
    522    }
    523  } else {
    524    // Workers don't share the same event loop.
    525    processScheduler(*this);
    526  }
    527 
    528  if (allQueues.IsEmpty()) {
    529    return nullptr;
    530  }
    531 
    532  // Reverse checking the queues, so it starts with the highest priority
    533  for (auto& queues : Reversed(allQueues)) {
    534    if (queues.IsEmpty()) {
    535      continue;
    536    }
    537    WebTaskQueue* oldestQueue = nullptr;
    538    for (auto& webTaskQueue : queues) {
    539      MOZ_ASSERT(webTaskQueue->HasScheduledTasks());
    540      if (!oldestQueue) {
    541        oldestQueue = webTaskQueue;
    542      } else {
    543        WebTask* firstScheduledRunnableForCurrentQueue =
    544            webTaskQueue->GetFirstScheduledTask();
    545        WebTask* firstScheduledRunnableForOldQueue =
    546            oldestQueue->GetFirstScheduledTask();
    547        if (firstScheduledRunnableForOldQueue->EnqueueOrder() >
    548            firstScheduledRunnableForCurrentQueue->EnqueueOrder()) {
    549          oldestQueue = webTaskQueue;
    550        }
    551      }
    552    }
    553    MOZ_ASSERT(oldestQueue);
    554    return oldestQueue->GetFirstScheduledTask();
    555  }
    556  return nullptr;
    557 }
    558 
    559 void WebTaskScheduler::Disconnect() {
    560  if (isInList()) {
    561    remove();
    562  }
    563  mWebTaskQueues.Clear();
    564 }
    565 
    566 void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
    567  // aIsContinuation is always false because continued tasks,
    568  // a.k.a yield(), can't change its priority.
    569  WebTaskQueueHashKey key(aTaskSignal, false /* aIsContinuation */);
    570  if (auto entry = mWebTaskQueues.Lookup(key)) {
    571    if (IsNormalOrHighPriority(entry.Data().Priority()) !=
    572        IsNormalOrHighPriority(key.Priority())) {
    573      // The counter needs to be adjusted if it has scheduled tasks
    574      // because this queue changes its priority.
    575      if (entry.Data().HasScheduledTasks()) {
    576        if (IsNormalOrHighPriority(key.Priority())) {
    577          // Promoted from lower priority to high priority.
    578          IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
    579        } else {
    580          // Demoted from high priority to low priority.
    581          DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
    582        }
    583      }
    584    }
    585    entry.Data().SetPriority(aTaskSignal->Priority());
    586  }
    587 }
    588 
    589 WebTaskScheduler::SelectedTaskQueueData WebTaskScheduler::SelectTaskQueue(
    590    TaskSignal* aTaskSignal, const Optional<TaskPriority>& aPriority,
    591    const bool aIsContinuation) {
    592  bool useSignal = !aPriority.WasPassed() && aTaskSignal;
    593 
    594  if (useSignal) {
    595    WebTaskQueueHashKey signalHashKey(aTaskSignal, aIsContinuation);
    596    WebTaskQueue& taskQueue =
    597        mWebTaskQueues.LookupOrInsert(signalHashKey, this);
    598 
    599    taskQueue.SetPriority(aTaskSignal->Priority());
    600    aTaskSignal->SetWebTaskScheduler(this);
    601 
    602    return SelectedTaskQueueData{WebTaskQueueHashKey(signalHashKey), taskQueue};
    603  }
    604 
    605  TaskPriority taskPriority =
    606      aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
    607 
    608  uint32_t staticTaskQueueMapKey = static_cast<uint32_t>(taskPriority);
    609  WebTaskQueueHashKey staticHashKey(staticTaskQueueMapKey, aIsContinuation);
    610  WebTaskQueue& taskQueue = mWebTaskQueues.LookupOrInsert(staticHashKey, this);
    611  taskQueue.SetPriority(taskPriority);
    612 
    613  return SelectedTaskQueueData{WebTaskQueueHashKey(staticHashKey), taskQueue};
    614 }
    615 
    616 EventQueuePriority WebTaskScheduler::GetEventQueuePriority(
    617    const TaskPriority& aPriority, bool aIsContinuation) const {
    618  switch (aPriority) {
    619    case TaskPriority::User_blocking:
    620      return EventQueuePriority::MediumHigh;
    621    case TaskPriority::User_visible:
    622      return aIsContinuation ? EventQueuePriority::MediumHigh
    623                             : EventQueuePriority::Normal;
    624    case TaskPriority::Background:
    625      return EventQueuePriority::Low;
    626    default:
    627      MOZ_ASSERT_UNREACHABLE("Invalid TaskPriority");
    628      return EventQueuePriority::Normal;
    629  }
    630 }
    631 
    632 void WebTaskScheduler::NotifyTaskWillBeRunOrAborted(const WebTask* aWebTask) {
    633  const WebTaskQueueHashKey& hashKey = aWebTask->TaskQueueHashKey();
    634  MOZ_ASSERT(mWebTaskQueues.Contains(hashKey));
    635  if (auto entry = mWebTaskQueues.Lookup(hashKey)) {
    636    const WebTaskQueue& taskQueue = *entry;
    637    if (IsNormalOrHighPriority(taskQueue.Priority())) {
    638      // If the taskQueue
    639      //   1. is empty
    640      //   2. or it's not empty but the existing tasks are
    641      //   not scheduled (delay tasks).
    642      if (!taskQueue.HasScheduledTasks()) {
    643        DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
    644      }
    645    }
    646    if (taskQueue.IsEmpty()) {
    647      DeleteEntryFromWebTaskQueueMap(hashKey);
    648    }
    649  }
    650 }
    651 
    652 WebTaskQueue::~WebTaskQueue() {
    653  MOZ_ASSERT(mScheduler);
    654 
    655  bool hasScheduledTask = false;
    656  for (const auto& task : mTasks) {
    657    if (!hasScheduledTask && task->HasScheduled()) {
    658      hasScheduledTask = true;
    659    }
    660    task->ClearWebTaskScheduler();
    661  }
    662  mTasks.clear();
    663 
    664  if (hasScheduledTask && IsNormalOrHighPriority(Priority())) {
    665    mScheduler->DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
    666  }
    667 }
    668 }  // namespace mozilla::dom