gecko-dev/dom/webscheduling/WebTaskScheduler.cpp

668 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsTHashMap.h"
#include "WebTaskScheduler.h"
#include "WebTaskSchedulerWorker.h"
#include "WebTaskSchedulerMainThread.h"
#include "nsGlobalWindowInner.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/TimeoutManager.h"
namespace mozilla::dom {
// Keeps track of all the existings schedulers that
// share the same event loop.
MOZ_RUNINIT static LinkedList<WebTaskScheduler> gWebTaskSchedulersMainThread;
static Atomic<uint64_t> gWebTaskEnqueueOrder(0);
// According to
// https://github.com/WICG/scheduling-apis/issues/113#issuecomment-2596102676,
// tasks with User_blocking or User_visible needs to run before timers.
static bool IsNormalOrHighPriority(TaskPriority aPriority) {
return aPriority == TaskPriority::User_blocking ||
aPriority == TaskPriority::User_visible;
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue,
const char* aName, uint32_t aFlags = 0) {
ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags);
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
const WebTaskQueueHashKey& aField, const char* aName, uint32_t aFlags = 0) {
const WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey();
if (typeKey.is<RefPtr<TaskSignal>>()) {
ImplCycleCollectionTraverse(aCallback, typeKey.as<RefPtr<TaskSignal>>(),
aName, aFlags);
}
}
inline void ImplCycleCollectionUnlink(WebTaskQueueHashKey& aField) {
WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey();
if (typeKey.is<RefPtr<TaskSignal>>()) {
ImplCycleCollectionUnlink(typeKey.as<RefPtr<TaskSignal>>());
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(WebTaskSchedulingState)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTaskSchedulingState)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortSource, mPrioritySource);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTaskSchedulingState)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortSource, mPrioritySource);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_CLASS(WebTask)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTask)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskQueueHashKey)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSchedulingState)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTask)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskQueueHashKey)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSchedulingState)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTask)
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTask)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTask)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(DelayedWebTaskHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DelayedWebTaskHandler)
WebTask::WebTask(uint32_t aEnqueueOrder,
const Maybe<SchedulerPostTaskCallback&>& aCallback,
WebTaskSchedulingState* aSchedlingState, Promise* aPromise,
WebTaskScheduler* aWebTaskScheduler,
const WebTaskQueueHashKey& aHashKey)
: mEnqueueOrder(aEnqueueOrder),
mPromise(aPromise),
mHasScheduled(false),
mSchedulingState(aSchedlingState),
mScheduler(aWebTaskScheduler),
mWebTaskQueueHashKey(aHashKey) {
if (aCallback.isSome()) {
mCallback = &aCallback.ref();
}
}
void WebTask::RunAbortAlgorithm() {
// no-op if WebTask::Run has been called already
if (mPromise->State() == Promise::PromiseState::Pending) {
// There are two things that can keep a WebTask alive, either the abort
// signal or WebTaskQueue.
// It's possible that this task get cleared out from the WebTaskQueue first,
// and then the abort signal get aborted. For example, the callback function
// was async and there's a signal.abort() call in the callback.
if (isInList()) {
remove();
MOZ_ASSERT(mScheduler);
if (HasScheduled()) {
mScheduler->NotifyTaskWillBeRunOrAborted(this);
}
}
AutoJSAPI jsapi;
if (!jsapi.Init(mPromise->GetGlobalObject())) {
mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
} else {
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> reason(cx);
Signal()->GetReason(cx, &reason);
mPromise->MaybeReject(reason);
}
}
MOZ_ASSERT(!isInList());
}
bool WebTask::Run() {
MOZ_ASSERT(HasScheduled());
MOZ_ASSERT(mScheduler);
remove();
mScheduler->NotifyTaskWillBeRunOrAborted(this);
ClearWebTaskScheduler();
if (!mCallback) {
// Scheduler.yield
mPromise->MaybeResolveWithUndefined();
MOZ_ASSERT(!isInList());
return true;
}
MOZ_ASSERT(mSchedulingState);
ErrorResult error;
nsIGlobalObject* global = mPromise->GetGlobalObject();
if (!global || global->IsDying()) {
return false;
}
// 11.2.2 Set event loop’s current scheduling state to state.
global->SetWebTaskSchedulingState(mSchedulingState);
AutoJSAPI jsapi;
if (!jsapi.Init(global)) {
return false;
}
JS::Rooted<JS::Value> returnVal(jsapi.cx());
MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask",
CallbackFunction::eRethrowExceptions);
// 11.2.4 Set event loop’s current scheduling state to null.
global->SetWebTaskSchedulingState(nullptr);
error.WouldReportJSException();
#ifdef DEBUG
Promise::PromiseState promiseState = mPromise->State();
// If the state is Rejected, it means the above Call triggers the
// RunAbortAlgorithm method and rejected the promise
MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending,
promiseState == Promise::PromiseState::Rejected);
#endif
if (error.Failed()) {
if (!error.IsUncatchableException()) {
mPromise->MaybeReject(std::move(error));
} else {
error.SuppressException();
}
} else {
mPromise->MaybeResolve(returnVal);
}
MOZ_ASSERT(!isInList());
return true;
}
inline void ImplCycleCollectionUnlink(
nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField) {
aField.Clear();
}
inline void ImplCycleCollectionTraverse(
nsCycleCollectionTraversalCallback& aCallback,
nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField, const char* aName,
uint32_t aFlags = 0) {
for (auto& entry : aField) {
ImplCycleCollectionTraverse(
aCallback, entry.GetKey(),
"nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueueHashKey",
aFlags);
ImplCycleCollectionTraverse(
aCallback, *entry.GetModifiableData(),
"nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueue", aFlags);
}
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent, mWebTaskQueues)
/* static */
already_AddRefed<WebTaskSchedulerMainThread>
WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
RefPtr<WebTaskSchedulerMainThread> scheduler =
new WebTaskSchedulerMainThread(aWindow->AsGlobal());
gWebTaskSchedulersMainThread.insertBack(scheduler);
return scheduler.forget();
}
already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
WorkerPrivate* aWorkerPrivate) {
aWorkerPrivate->AssertIsOnWorkerThread();
RefPtr<WebTaskSchedulerWorker> scheduler =
WebTaskSchedulerWorker::Create(aWorkerPrivate);
return scheduler.forget();
}
WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
: mParent(aParent) {
MOZ_ASSERT(aParent);
}
JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
JS::Handle<JSObject*> aGivenProto) {
return Scheduler_Binding::Wrap(cx, this, aGivenProto);
}
static bool ShouldRejectPromiseWithReasonCausedByAbortSignal(
AbortSignal& aAbortSignal, nsIGlobalObject* aGlobal, Promise& aPromise) {
MOZ_ASSERT(aGlobal);
if (!aAbortSignal.Aborted()) {
return false;
}
AutoJSAPI jsapi;
if (!jsapi.Init(aGlobal)) {
aPromise.MaybeRejectWithNotSupportedError(
"Failed to initialize the JS context");
return true;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> reason(cx);
aAbortSignal.GetReason(cx, &reason);
aPromise.MaybeReject(reason);
return true;
}
// https://wicg.github.io/scheduling-apis/#sec-scheduler-alg-scheduling-tasks-and-continuations
already_AddRefed<Promise> WebTaskScheduler::PostTask(
SchedulerPostTaskCallback& aCallback,
const SchedulerPostTaskOptions& aOptions) {
const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal;
const Optional<TaskPriority>& taskPriority = aOptions.mPriority;
ErrorResult rv;
// Instead of making WebTaskScheduler::PostTask throws, we always
// create the promise and return it. This is because we need to
// create the promise explicitly to be able to reject it with
// signal's reason.
RefPtr<Promise> promise = Promise::Create(mParent, rv);
if (rv.Failed()) {
return nullptr;
}
nsIGlobalObject* global = GetParentObject();
if (!global || global->IsDying()) {
promise->MaybeRejectWithNotSupportedError("Current window is detached");
return promise.forget();
}
// 4. Let state be a new scheduling state.
RefPtr<WebTaskSchedulingState> newState = new WebTaskSchedulingState();
AbortSignal* signalValue = nullptr;
if (taskSignal.WasPassed()) {
signalValue = &taskSignal.Value();
// 3. If signal is not null and it is aborted, then reject result with
// signal’s abort reason and return result.
if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*signalValue, global,
*promise)) {
return promise.forget();
}
// 5. Set state’s abort source to signal.
newState->SetAbortSource(signalValue);
}
if (taskPriority.WasPassed()) {
// 6. If options["priority"] exists, then set state’s priority source to the
// result of creating a fixed priority unabortable task signal given
// options["priority"]
newState->SetPrioritySource(
TaskSignal::Create(GetParentObject(), taskPriority.Value()));
} else if (signalValue && signalValue->IsTaskSignal()) {
// 7. Otherwise if signal is not null and implements the TaskSignal
// interface, then set state’s priority source to signal.
newState->SetPrioritySource(
do_AddRef(static_cast<TaskSignal*>(signalValue)));
}
if (!newState->GetPrioritySource()) {
// 8. If state’s priority source is null, then set state’s priority
// source to the result of creating a fixed priority unabortable task
// signal given "user-visible".
newState->SetPrioritySource(
TaskSignal::Create(GetParentObject(), TaskPriority::User_visible));
}
MOZ_ASSERT(newState->GetPrioritySource());
// 9. Let handle be the result of creating a task handle given result and
// signal.
// 10. If signal is not null, then add handle’s abort steps to signal.
// 11. Let enqueueSteps be the following steps...
RefPtr<WebTask> task = CreateTask(signalValue, newState->GetPrioritySource(),
taskPriority, false /* aIsContinuation */,
SomeRef(aCallback), newState, promise);
const TaskSignal* finalPrioritySource = newState->GetPrioritySource();
// 12. Let delay be options["delay"].
const uint64_t delay = aOptions.mDelay;
// 13. If delay is greater than 0, then run steps after a timeout given
// scheduler’s relevant global object, "scheduler-postTask", delay, and the
// following steps...
if (delay > 0) {
nsresult rv = SetTimeoutForDelayedTask(
task, delay,
GetEventQueuePriority(finalPrioritySource->Priority(),
false /* aIsContinuation */));
if (NS_FAILED(rv)) {
promise->MaybeRejectWithUnknownError(
"Failed to setup timeout for delayed task");
}
return promise.forget();
}
// 14. Otherwise, run enqueueSteps.
if (!DispatchTask(task, GetEventQueuePriority(finalPrioritySource->Priority(),
false /* aIsContinuation */))) {
MOZ_ASSERT(task->isInList());
task->remove();
promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
return promise.forget();
}
return promise.forget();
}
// https://wicg.github.io/scheduling-apis/#schedule-a-yield-continuation
already_AddRefed<Promise> WebTaskScheduler::YieldImpl() {
ErrorResult rv;
// 1. Let result be a new promise.
RefPtr<Promise> promise = Promise::Create(mParent, rv);
if (rv.Failed()) {
return nullptr;
}
nsIGlobalObject* global = GetParentObject();
if (!global || global->IsDying()) {
promise->MaybeRejectWithNotSupportedError("Current window is detached");
return promise.forget();
}
RefPtr<AbortSignal> abortSource;
RefPtr<TaskSignal> prioritySource;
// 2. Let inheritedState be the scheduler’s relevant agent's event loop's
// current scheduling state.
if (auto* schedulingState = global->GetWebTaskSchedulingState()) {
// 3. Let abortSource be inheritedState’s abort source if inheritedState is
// not null, or otherwise null.
abortSource = schedulingState->GetAbortSource();
// 5. Let prioritySource be inheritedState’s priority source if
// inheritedState is not null, or otherwise null.
prioritySource = schedulingState->GetPrioritySource();
}
if (abortSource) {
// 4. If abortSource is not null and abortSource is aborted, then reject
// result with abortSource’s abort reason and return result.
if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*abortSource, global,
*promise)) {
return promise.forget();
}
}
if (!prioritySource) {
// 6. If prioritySource is null, then set prioritySource to the result of
// creating a fixed priority unabortable task signal given "user-visible".
prioritySource =
TaskSignal::Create(GetParentObject(), TaskPriority::User_visible);
}
// 7. Let handle be the result of creating a task handle given result and
// abortSource.
// 8. If abortSource is not null, then add handle’s abort steps to
// abortSource.
// 9. Set handle’s queue to the result of selecting the scheduler task queue
// for scheduler given prioritySource and true.
// 10. Schedule a task to invoke an algorithm for scheduler given handle and
// the following steps:
RefPtr<WebTask> task =
CreateTask(abortSource, prioritySource, {}, true /* aIsContinuation */,
Nothing(), nullptr, promise);
EventQueuePriority eventQueuePriority = GetEventQueuePriority(
prioritySource->Priority(), true /* aIsContinuation */);
if (!DispatchTask(task, eventQueuePriority)) {
MOZ_ASSERT(task->isInList());
// CreateTask adds the task to WebTaskScheduler's queue, so we
// need to remove from it when we failed to dispatch the runnable.
task->remove();
promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
return promise.forget();
}
return promise.forget();
}
already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
AbortSignal* aAbortSignal, TaskSignal* aTaskSignal,
const Optional<TaskPriority>& aPriority, bool aIsContinuation,
const Maybe<SchedulerPostTaskCallback&>& aCallback,
WebTaskSchedulingState* aSchedulingState, Promise* aPromise) {
WebTaskScheduler::SelectedTaskQueueData selectedTaskQueueData =
SelectTaskQueue(aTaskSignal, aPriority, aIsContinuation);
gWebTaskEnqueueOrder += 1;
RefPtr<WebTask> task =
new WebTask(gWebTaskEnqueueOrder, aCallback, aSchedulingState, aPromise,
this, selectedTaskQueueData.mSelectedQueueHashKey);
selectedTaskQueueData.mSelectedTaskQueue.AddTask(task);
if (aAbortSignal) {
task->Follow(aAbortSignal);
}
return task.forget();
}
bool WebTaskScheduler::DispatchTask(WebTask* aTask,
EventQueuePriority aPriority) {
if (!DispatchEventLoopRunnable(aPriority)) {
return false;
}
MOZ_ASSERT(!aTask->HasScheduled());
auto taskQueue = mWebTaskQueues.Lookup(aTask->TaskQueueHashKey());
MOZ_DIAGNOSTIC_ASSERT(taskQueue);
if (IsNormalOrHighPriority(aTask->Priority()) &&
!taskQueue->HasScheduledTasks()) {
// This is the first task that is scheduled for this queue.
IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
}
aTask->SetHasScheduled();
return true;
}
// https://wicg.github.io/scheduling-apis/#select-the-next-scheduler-task-queue-from-all-schedulers
WebTask* WebTaskScheduler::GetNextTask(bool aIsMainThread) {
// 1. Let queues be an empty set.
AutoTArray<nsTArray<WebTaskQueue*>, WebTaskQueue::EffectivePriorityCount>
allQueues;
allQueues.SetLength(WebTaskQueue::EffectivePriorityCount);
auto processScheduler = [&](WebTaskScheduler& aScheduler) {
for (auto iter = aScheduler.GetWebTaskQueues().Iter(); !iter.Done();
iter.Next()) {
auto& queue = iter.Data();
if (queue.HasScheduledTasks()) {
const WebTaskQueueHashKey& key = iter.Key();
nsTArray<WebTaskQueue*>& queuesForThisPriority =
allQueues[key.EffectivePriority()];
queuesForThisPriority.AppendElement(&queue);
}
}
};
// 3. For each scheduler in schedulers, extend queues with the result of
// getting the runnable task queues for scheduler.
if (aIsMainThread) {
// 2. Let schedulers be the set of all Scheduler objects whose relevant
// agent’s event loop is event loop and that have a runnable task.
for (const auto& scheduler : gWebTaskSchedulersMainThread) {
processScheduler(*scheduler);
}
} else {
// Workers don't share the same event loop.
processScheduler(*this);
}
if (allQueues.IsEmpty()) {
return nullptr;
}
// Reverse checking the queues, so it starts with the highest priority
for (auto& queues : Reversed(allQueues)) {
if (queues.IsEmpty()) {
continue;
}
WebTaskQueue* oldestQueue = nullptr;
for (auto& webTaskQueue : queues) {
MOZ_ASSERT(webTaskQueue->HasScheduledTasks());
if (!oldestQueue) {
oldestQueue = webTaskQueue;
} else {
WebTask* firstScheduledRunnableForCurrentQueue =
webTaskQueue->GetFirstScheduledTask();
WebTask* firstScheduledRunnableForOldQueue =
oldestQueue->GetFirstScheduledTask();
if (firstScheduledRunnableForOldQueue->EnqueueOrder() >
firstScheduledRunnableForCurrentQueue->EnqueueOrder()) {
oldestQueue = webTaskQueue;
}
}
}
MOZ_ASSERT(oldestQueue);
return oldestQueue->GetFirstScheduledTask();
}
return nullptr;
}
void WebTaskScheduler::Disconnect() {
if (isInList()) {
remove();
}
mWebTaskQueues.Clear();
}
void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
// aIsContinuation is always false because continued tasks,
// a.k.a yield(), can't change its priority.
WebTaskQueueHashKey key(aTaskSignal, false /* aIsContinuation */);
if (auto entry = mWebTaskQueues.Lookup(key)) {
if (IsNormalOrHighPriority(entry.Data().Priority()) !=
IsNormalOrHighPriority(key.Priority())) {
// The counter needs to be adjusted if it has scheduled tasks
// because this queue changes its priority.
if (entry.Data().HasScheduledTasks()) {
if (IsNormalOrHighPriority(key.Priority())) {
// Promoted from lower priority to high priority.
IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
} else {
// Demoted from high priority to low priority.
DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
}
}
}
entry.Data().SetPriority(aTaskSignal->Priority());
}
}
WebTaskScheduler::SelectedTaskQueueData WebTaskScheduler::SelectTaskQueue(
TaskSignal* aTaskSignal, const Optional<TaskPriority>& aPriority,
const bool aIsContinuation) {
bool useSignal = !aPriority.WasPassed() && aTaskSignal;
if (useSignal) {
WebTaskQueueHashKey signalHashKey(aTaskSignal, aIsContinuation);
WebTaskQueue& taskQueue =
mWebTaskQueues.LookupOrInsert(signalHashKey, this);
taskQueue.SetPriority(aTaskSignal->Priority());
aTaskSignal->SetWebTaskScheduler(this);
return SelectedTaskQueueData{WebTaskQueueHashKey(signalHashKey), taskQueue};
}
TaskPriority taskPriority =
aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
uint32_t staticTaskQueueMapKey = static_cast<uint32_t>(taskPriority);
WebTaskQueueHashKey staticHashKey(staticTaskQueueMapKey, aIsContinuation);
WebTaskQueue& taskQueue = mWebTaskQueues.LookupOrInsert(staticHashKey, this);
taskQueue.SetPriority(taskPriority);
return SelectedTaskQueueData{WebTaskQueueHashKey(staticHashKey), taskQueue};
}
EventQueuePriority WebTaskScheduler::GetEventQueuePriority(
const TaskPriority& aPriority, bool aIsContinuation) const {
switch (aPriority) {
case TaskPriority::User_blocking:
return EventQueuePriority::MediumHigh;
case TaskPriority::User_visible:
return aIsContinuation ? EventQueuePriority::MediumHigh
: EventQueuePriority::Normal;
case TaskPriority::Background:
return EventQueuePriority::Low;
default:
MOZ_ASSERT_UNREACHABLE("Invalid TaskPriority");
return EventQueuePriority::Normal;
}
}
void WebTaskScheduler::NotifyTaskWillBeRunOrAborted(const WebTask* aWebTask) {
const WebTaskQueueHashKey& hashKey = aWebTask->TaskQueueHashKey();
MOZ_ASSERT(mWebTaskQueues.Contains(hashKey));
if (auto entry = mWebTaskQueues.Lookup(hashKey)) {
const WebTaskQueue& taskQueue = *entry;
if (IsNormalOrHighPriority(taskQueue.Priority())) {
// If the taskQueue
// 1. is empty
// 2. or it's not empty but the existing tasks are
// not scheduled (delay tasks).
if (!taskQueue.HasScheduledTasks()) {
DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
}
}
if (taskQueue.IsEmpty()) {
DeleteEntryFromWebTaskQueueMap(hashKey);
}
}
}
WebTaskQueue::~WebTaskQueue() {
MOZ_ASSERT(mScheduler);
bool hasScheduledTask = false;
for (const auto& task : mTasks) {
if (!hasScheduledTask && task->HasScheduled()) {
hasScheduledTask = true;
}
task->ClearWebTaskScheduler();
}
mTasks.clear();
if (hasScheduledTask && IsNormalOrHighPriority(Priority())) {
mScheduler->DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled();
}
}
} // namespace mozilla::dom