summaryrefslogtreecommitdiffstats
path: root/dom/webscheduling
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/webscheduling/TaskSignal.h63
-rw-r--r--dom/webscheduling/WebTaskController.cpp70
-rw-r--r--dom/webscheduling/WebTaskController.h35
-rw-r--r--dom/webscheduling/WebTaskScheduler.cpp383
-rw-r--r--dom/webscheduling/WebTaskScheduler.h207
-rw-r--r--dom/webscheduling/WebTaskSchedulerMainThread.cpp54
-rw-r--r--dom/webscheduling/WebTaskSchedulerMainThread.h42
-rw-r--r--dom/webscheduling/WebTaskSchedulerWorker.cpp72
-rw-r--r--dom/webscheduling/WebTaskSchedulerWorker.h48
-rw-r--r--dom/webscheduling/moz.build27
10 files changed, 1001 insertions, 0 deletions
diff --git a/dom/webscheduling/TaskSignal.h b/dom/webscheduling/TaskSignal.h
new file mode 100644
index 0000000000..2cc02cc2fc
--- /dev/null
+++ b/dom/webscheduling/TaskSignal.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef mozilla_dom_TaskSignal_h
+#define mozilla_dom_TaskSignal_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/WebTaskSchedulingBinding.h"
+#include "WebTaskScheduler.h"
+
+namespace mozilla::dom {
+class TaskSignal : public AbortSignal {
+ public:
+ TaskSignal(nsIGlobalObject* aGlobal, TaskPriority aPriority)
+ : AbortSignal(aGlobal, false, JS::UndefinedHandleValue),
+ mPriority(aPriority),
+ mPriorityChanging(false) {}
+
+ IMPL_EVENT_HANDLER(prioritychange);
+
+ TaskPriority Priority() const { return mPriority; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override {
+ return TaskSignal_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ void SetPriority(TaskPriority aPriority) { mPriority = aPriority; }
+
+ bool IsTaskSignal() const override { return true; }
+
+ bool PriorityChanging() const { return mPriorityChanging; }
+
+ void SetPriorityChanging(bool aPriorityChanging) {
+ mPriorityChanging = aPriorityChanging;
+ }
+
+ void SetWebTaskScheduler(WebTaskScheduler* aScheduler) {
+ mSchedulers.AppendElement(aScheduler);
+ }
+
+ void RunPriorityChangeAlgorithms() {
+ for (const WeakPtr<WebTaskScheduler>& scheduler : mSchedulers) {
+ scheduler->RunTaskSignalPriorityChange(this);
+ }
+ }
+
+ private:
+ TaskPriority mPriority;
+
+ bool mPriorityChanging;
+
+ nsTArray<WeakPtr<WebTaskScheduler>> mSchedulers;
+
+ ~TaskSignal() = default;
+};
+} // namespace mozilla::dom
+#endif
diff --git a/dom/webscheduling/WebTaskController.cpp b/dom/webscheduling/WebTaskController.cpp
new file mode 100644
index 0000000000..f9d27aaa0d
--- /dev/null
+++ b/dom/webscheduling/WebTaskController.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "WebTaskController.h"
+#include "TaskSignal.h"
+
+#include "mozilla/dom/TaskPriorityChangeEvent.h"
+
+namespace mozilla::dom {
+
+WebTaskController::WebTaskController(nsIGlobalObject* aGlobal,
+ TaskPriority aPriority)
+ : AbortController(aGlobal) {
+ MOZ_ASSERT(!mSignal);
+ mSignal = new TaskSignal(aGlobal, aPriority);
+}
+
+void WebTaskController::SetPriority(TaskPriority aPriority, ErrorResult& aRv) {
+ // https://wicg.github.io/scheduling-apis/#tasksignal-signal-priority-change
+ RefPtr<TaskSignal> taskSignal = static_cast<TaskSignal*>(mSignal.get());
+ MOZ_ASSERT(taskSignal);
+ if (taskSignal->PriorityChanging()) {
+ aRv.ThrowNotAllowedError("Signal's priority changing is true");
+ return;
+ }
+
+ if (taskSignal->Priority() == aPriority) {
+ return;
+ }
+
+ taskSignal->SetPriorityChanging(true);
+
+ TaskPriority previousPriority = taskSignal->Priority();
+ taskSignal->SetPriority(aPriority);
+
+ taskSignal->RunPriorityChangeAlgorithms();
+
+ TaskPriorityChangeEventInit init;
+ init.mPreviousPriority = previousPriority;
+ RefPtr<TaskPriorityChangeEvent> event = TaskPriorityChangeEvent::Constructor(
+ taskSignal, u"prioritychange"_ns, init);
+
+ event->SetTrusted(true);
+
+ taskSignal->DispatchEvent(*event);
+ taskSignal->SetPriorityChanging(false);
+}
+
+already_AddRefed<WebTaskController> WebTaskController::Constructor(
+ const GlobalObject& aGlobal, const TaskControllerInit& aInit,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ RefPtr<WebTaskController> webTaskController =
+ new WebTaskController(global, aInit.mPriority);
+ return webTaskController.forget();
+}
+
+JSObject* WebTaskController::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TaskController_Binding::Wrap(aCx, this, aGivenProto);
+}
+} // namespace mozilla::dom
diff --git a/dom/webscheduling/WebTaskController.h b/dom/webscheduling/WebTaskController.h
new file mode 100644
index 0000000000..ade3526531
--- /dev/null
+++ b/dom/webscheduling/WebTaskController.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef mozilla_dom_WebTaskController_h
+#define mozilla_dom_WebTaskController_h
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/WebTaskSchedulingBinding.h"
+#include "mozilla/dom/AbortController.h"
+
+namespace mozilla::dom {
+class WebTaskController : public AbortController {
+ public:
+ explicit WebTaskController(nsIGlobalObject* aGlobal, TaskPriority aPriority);
+
+ static already_AddRefed<WebTaskController> Constructor(
+ const GlobalObject& aGlobal, const TaskControllerInit& aInit,
+ ErrorResult& aRv);
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetPriority(TaskPriority aPriority, ErrorResult& aRv);
+
+ private:
+ ~WebTaskController() = default;
+};
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/webscheduling/WebTaskScheduler.cpp b/dom/webscheduling/WebTaskScheduler.cpp
new file mode 100644
index 0000000000..b6fa749095
--- /dev/null
+++ b/dom/webscheduling/WebTaskScheduler.cpp
@@ -0,0 +1,383 @@
+/* -*- 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 "TaskSignal.h"
+#include "nsGlobalWindowInner.h"
+
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/TimeoutManager.h"
+
+namespace mozilla::dom {
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue,
+ const char* aName, uint32_t aFlags = 0) {
+ ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags);
+}
+
+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_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_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)
+
+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();
+ }
+
+ 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(mOwnerQueue);
+ remove();
+
+ mOwnerQueue->RemoveEntryFromTaskQueueMapIfNeeded();
+ mOwnerQueue = nullptr;
+ // At this point mOwnerQueue is destructed and this is fine.
+ // The caller of WebTask::Run keeps it alive.
+
+ ErrorResult error;
+
+ nsIGlobalObject* global = mPromise->GetGlobalObject();
+ if (!global || global->IsDying()) {
+ return false;
+ }
+
+ 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);
+
+ 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;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent,
+ mStaticPriorityTaskQueues,
+ mDynamicPriorityTaskQueues)
+
+/* static */
+already_AddRefed<WebTaskSchedulerMainThread>
+WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
+ RefPtr<WebTaskSchedulerMainThread> scheduler =
+ new WebTaskSchedulerMainThread(aWindow->AsGlobal());
+ return scheduler.forget();
+}
+
+already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
+ WorkerPrivate* aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ RefPtr<WebTaskSchedulerWorker> scheduler =
+ new WebTaskSchedulerWorker(aWorkerPrivate);
+ return scheduler.forget();
+}
+
+WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
+ : mParent(aParent), mNextEnqueueOrder(1) {
+ MOZ_ASSERT(aParent);
+}
+
+JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return Scheduler_Binding::Wrap(cx, this, aGivenProto);
+}
+
+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();
+ }
+
+ if (taskSignal.WasPassed()) {
+ AbortSignal& signalValue = taskSignal.Value();
+
+ if (signalValue.Aborted()) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(global)) {
+ promise->MaybeReject(NS_ERROR_UNEXPECTED);
+ return promise.forget();
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> reason(cx);
+ signalValue.GetReason(cx, &reason);
+ promise->MaybeReject(reason);
+ return promise.forget();
+ }
+ }
+
+ // Let queue be the result of selecting the scheduler task queue for scheduler
+ // given signal and priority.
+ WebTaskQueue& taskQueue = SelectTaskQueue(taskSignal, taskPriority);
+
+ uint64_t delay = aOptions.mDelay;
+
+ RefPtr<WebTask> task = CreateTask(taskQueue, taskSignal, aCallback, promise);
+ if (delay > 0) {
+ nsresult rv = SetTimeoutForDelayedTask(task, delay);
+ if (NS_FAILED(rv)) {
+ promise->MaybeRejectWithUnknownError(
+ "Failed to setup timeout for delayed task");
+ }
+ return promise.forget();
+ }
+
+ if (!QueueTask(task)) {
+ MOZ_ASSERT(task->isInList());
+ task->remove();
+
+ promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
+ return promise.forget();
+ }
+
+ return promise.forget();
+}
+
+already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
+ WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
+ SchedulerPostTaskCallback& aCallback, Promise* aPromise) {
+ uint32_t nextEnqueueOrder = mNextEnqueueOrder;
+ ++mNextEnqueueOrder;
+
+ RefPtr<WebTask> task = new WebTask(nextEnqueueOrder, aCallback, aPromise);
+
+ aQueue.AddTask(task);
+
+ if (aSignal.WasPassed()) {
+ AbortSignal& signalValue = aSignal.Value();
+ task->Follow(&signalValue);
+ }
+
+ return task.forget();
+}
+
+bool WebTaskScheduler::QueueTask(WebTask* aTask) {
+ if (!DispatchEventLoopRunnable()) {
+ return false;
+ }
+ MOZ_ASSERT(!aTask->HasScheduled());
+ aTask->SetHasScheduled(true);
+ return true;
+}
+
+WebTask* WebTaskScheduler::GetNextTask() const {
+ // We first combine queues from both mStaticPriorityTaskQueues and
+ // mDynamicPriorityTaskQueues into a single hash map which the
+ // keys are the priorities and the values are all the queues that
+ // belong to this priority.
+ //
+ // Then From the queues which
+ // 1. Have scheduled tasks
+ // 2. Its priority is not less than any other queues' priority
+ // We pick the task which has the smallest enqueue order.
+ nsTHashMap<nsUint32HashKey, nsTArray<WebTaskQueue*>> allQueues;
+
+ for (auto iter = mStaticPriorityTaskQueues.ConstIter(); !iter.Done();
+ iter.Next()) {
+ const auto& queue = iter.Data();
+ if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
+ nsTArray<WebTaskQueue*>& queuesForThisPriority =
+ allQueues.LookupOrInsert(static_cast<uint32_t>(iter.Key()));
+ queuesForThisPriority.AppendElement(queue.get());
+ }
+ }
+
+ for (auto iter = mDynamicPriorityTaskQueues.ConstIter(); !iter.Done();
+ iter.Next()) {
+ const auto& queue = iter.Data();
+ if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
+ nsTArray<WebTaskQueue*>& queuesForThisPriority = allQueues.LookupOrInsert(
+ static_cast<uint32_t>(iter.Key()->Priority()));
+ queuesForThisPriority.AppendElement(queue.get());
+ }
+ }
+
+ if (allQueues.IsEmpty()) {
+ return nullptr;
+ }
+
+ for (uint32_t priority = static_cast<uint32_t>(TaskPriority::User_blocking);
+ priority < static_cast<uint32_t>(TaskPriority::EndGuard_); ++priority) {
+ if (auto queues = allQueues.Lookup(priority)) {
+ WebTaskQueue* oldestQueue = nullptr;
+ MOZ_ASSERT(!queues.Data().IsEmpty());
+ for (auto& webTaskQueue : queues.Data()) {
+ MOZ_ASSERT(webTaskQueue->GetFirstScheduledTask());
+ 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() {
+ mStaticPriorityTaskQueues.Clear();
+ mDynamicPriorityTaskQueues.Clear();
+}
+
+void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
+ if (WebTaskQueue* const taskQueue =
+ mDynamicPriorityTaskQueues.Get(aTaskSignal)) {
+ taskQueue->SetPriority(aTaskSignal->Priority());
+ }
+}
+
+WebTaskQueue& WebTaskScheduler::SelectTaskQueue(
+ const Optional<OwningNonNull<AbortSignal>>& aSignal,
+ const Optional<TaskPriority>& aPriority) {
+ bool useSignal = !aPriority.WasPassed() && aSignal.WasPassed() &&
+ aSignal.Value().IsTaskSignal();
+
+ if (useSignal) {
+ TaskSignal* taskSignal = static_cast<TaskSignal*>(&(aSignal.Value()));
+ WebTaskQueue* const taskQueue =
+ mDynamicPriorityTaskQueues.GetOrInsertNew(taskSignal, taskSignal, this);
+ taskQueue->SetPriority(taskSignal->Priority());
+ taskSignal->SetWebTaskScheduler(this);
+ MOZ_ASSERT(mDynamicPriorityTaskQueues.Contains(taskSignal));
+
+ return *taskQueue;
+ }
+
+ TaskPriority taskPriority =
+ aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
+
+ uint32_t staticTaskQueueMapKey = static_cast<uint32_t>(taskPriority);
+ WebTaskQueue* const taskQueue = mStaticPriorityTaskQueues.GetOrInsertNew(
+ staticTaskQueueMapKey, staticTaskQueueMapKey, this);
+ taskQueue->SetPriority(taskPriority);
+ MOZ_ASSERT(
+ mStaticPriorityTaskQueues.Contains(static_cast<uint32_t>(taskPriority)));
+ return *taskQueue;
+}
+
+void WebTaskScheduler::DeleteEntryFromStaticQueueMap(uint32_t aKey) {
+ DebugOnly<bool> result = mStaticPriorityTaskQueues.Remove(aKey);
+ MOZ_ASSERT(result);
+}
+
+void WebTaskScheduler::DeleteEntryFromDynamicQueueMap(TaskSignal* aKey) {
+ DebugOnly<bool> result = mDynamicPriorityTaskQueues.Remove(aKey);
+ MOZ_ASSERT(result);
+}
+
+void WebTaskQueue::RemoveEntryFromTaskQueueMapIfNeeded() {
+ MOZ_ASSERT(mScheduler);
+ if (mTasks.isEmpty()) {
+ if (mOwnerKey.is<uint32_t>()) {
+ mScheduler->DeleteEntryFromStaticQueueMap(mOwnerKey.as<uint32_t>());
+ } else {
+ MOZ_ASSERT(mOwnerKey.is<TaskSignal*>());
+ mScheduler->DeleteEntryFromDynamicQueueMap(mOwnerKey.as<TaskSignal*>());
+ }
+ }
+}
+} // namespace mozilla::dom
diff --git a/dom/webscheduling/WebTaskScheduler.h b/dom/webscheduling/WebTaskScheduler.h
new file mode 100644
index 0000000000..18767be8a9
--- /dev/null
+++ b/dom/webscheduling/WebTaskScheduler.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef mozilla_dom_WebTaskScheduler_h
+#define mozilla_dom_WebTaskScheduler_h
+
+#include "nsThreadUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+#include "nsClassHashtable.h"
+
+#include "mozilla/Variant.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/AbortFollower.h"
+#include "mozilla/dom/TimeoutHandler.h"
+#include "mozilla/dom/WebTaskSchedulingBinding.h"
+
+namespace mozilla::dom {
+class WebTaskQueue;
+class WebTask : public LinkedListElement<RefPtr<WebTask>>,
+ public AbortFollower,
+ public SupportsWeakPtr {
+ friend class WebTaskScheduler;
+
+ public:
+ MOZ_CAN_RUN_SCRIPT bool Run();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ NS_DECL_CYCLE_COLLECTION_CLASS(WebTask)
+ WebTask(uint32_t aEnqueueOrder, SchedulerPostTaskCallback& aCallback,
+ Promise* aPromise)
+ : mEnqueueOrder(aEnqueueOrder),
+ mCallback(&aCallback),
+ mPromise(aPromise),
+ mHasScheduled(false),
+ mOwnerQueue(nullptr) {}
+
+ void RunAbortAlgorithm() override;
+
+ bool HasScheduled() const { return mHasScheduled; }
+
+ uint32_t EnqueueOrder() const { return mEnqueueOrder; }
+
+ void SetWebTaskQueue(WebTaskQueue* aWebTaskQueue) {
+ mOwnerQueue = aWebTaskQueue;
+ }
+
+ private:
+ void SetHasScheduled(bool aHasScheduled) { mHasScheduled = aHasScheduled; }
+
+ uint32_t mEnqueueOrder;
+
+ RefPtr<SchedulerPostTaskCallback> mCallback;
+ RefPtr<Promise> mPromise;
+
+ bool mHasScheduled;
+
+ // WebTaskQueue owns WebTask, so it's okay to use a raw pointer
+ WebTaskQueue* mOwnerQueue;
+
+ ~WebTask() = default;
+};
+
+class WebTaskQueue {
+ public:
+ WebTaskQueue(uint32_t aKey, WebTaskScheduler* aScheduler)
+ : mOwnerKey(AsVariant(aKey)), mScheduler(aScheduler) {}
+ WebTaskQueue(TaskSignal* aKey, WebTaskScheduler* aScheduler)
+ : mOwnerKey(AsVariant(aKey)), mScheduler(aScheduler) {}
+
+ TaskPriority Priority() const { return mPriority; }
+ void SetPriority(TaskPriority aNewPriority) { mPriority = aNewPriority; }
+
+ LinkedList<RefPtr<WebTask>>& Tasks() { return mTasks; }
+
+ void AddTask(WebTask* aTask) {
+ mTasks.insertBack(aTask);
+ aTask->SetWebTaskQueue(this);
+ }
+
+ void RemoveEntryFromTaskQueueMapIfNeeded();
+ // TODO: To optimize it, we could have the scheduled and unscheduled
+ // tasks stored separately.
+ WebTask* GetFirstScheduledTask() {
+ for (const auto& task : mTasks) {
+ if (task->HasScheduled()) {
+ return task;
+ }
+ }
+ return nullptr;
+ }
+
+ ~WebTaskQueue() {
+ mOwnerKey = AsVariant(Nothing());
+ for (const auto& task : mTasks) {
+ task->SetWebTaskQueue(nullptr);
+ }
+ mTasks.clear();
+ }
+
+ private:
+ TaskPriority mPriority = TaskPriority::User_visible;
+ LinkedList<RefPtr<WebTask>> mTasks;
+
+ // When mOwnerKey is TaskSignal*, it means as long as
+ // WebTaskQueue is alive, the corresponding TaskSignal
+ // is alive, so using a raw pointer is ok.
+ Variant<Nothing, uint32_t, TaskSignal*> mOwnerKey;
+
+ // WebTaskScheduler owns WebTaskQueue as a hashtable value, so using a raw
+ // pointer points to WebTaskScheduler is ok.
+ WebTaskScheduler* mScheduler;
+};
+
+class WebTaskSchedulerMainThread;
+class WebTaskSchedulerWorker;
+
+class WebTaskScheduler : public nsWrapperCache, public SupportsWeakPtr {
+ friend class DelayedWebTaskHandler;
+
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebTaskScheduler)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(WebTaskScheduler)
+
+ static already_AddRefed<WebTaskSchedulerMainThread> CreateForMainThread(
+ nsGlobalWindowInner* aWindow);
+
+ static already_AddRefed<WebTaskSchedulerWorker> CreateForWorker(
+ WorkerPrivate* aWorkerPrivate);
+
+ explicit WebTaskScheduler(nsIGlobalObject* aParent);
+
+ already_AddRefed<Promise> PostTask(SchedulerPostTaskCallback& aCallback,
+ const SchedulerPostTaskOptions& aOptions);
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ WebTask* GetNextTask() const;
+
+ virtual void Disconnect();
+
+ void RunTaskSignalPriorityChange(TaskSignal* aTaskSignal);
+
+ void DeleteEntryFromStaticQueueMap(uint32_t aKey);
+ void DeleteEntryFromDynamicQueueMap(TaskSignal* aKey);
+
+ protected:
+ virtual ~WebTaskScheduler() = default;
+ nsCOMPtr<nsIGlobalObject> mParent;
+
+ uint32_t mNextEnqueueOrder;
+
+ private:
+ already_AddRefed<WebTask> CreateTask(
+ WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
+ SchedulerPostTaskCallback& aCallback, Promise* aPromise);
+
+ bool QueueTask(WebTask* aTask);
+
+ WebTaskQueue& SelectTaskQueue(
+ const Optional<OwningNonNull<AbortSignal>>& aSignal,
+ const Optional<TaskPriority>& aPriority);
+
+ virtual nsresult SetTimeoutForDelayedTask(WebTask* aTask,
+ uint64_t aDelay) = 0;
+ virtual bool DispatchEventLoopRunnable() = 0;
+
+ nsClassHashtable<nsUint32HashKey, WebTaskQueue> mStaticPriorityTaskQueues;
+ nsClassHashtable<nsRefPtrHashKey<TaskSignal>, WebTaskQueue>
+ mDynamicPriorityTaskQueues;
+};
+
+class DelayedWebTaskHandler final : public TimeoutHandler {
+ public:
+ DelayedWebTaskHandler(JSContext* aCx, WebTaskScheduler* aScheduler,
+ WebTask* aTask)
+ : TimeoutHandler(aCx), mScheduler(aScheduler), mWebTask(aTask) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(DelayedWebTaskHandler)
+
+ MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
+ if (mScheduler && mWebTask) {
+ MOZ_ASSERT(!mWebTask->HasScheduled());
+ if (!mScheduler->QueueTask(mWebTask)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private:
+ ~DelayedWebTaskHandler() override = default;
+ WeakPtr<WebTaskScheduler> mScheduler;
+ // WebTask gets added to WebTaskQueue, and WebTaskQueue keeps its alive.
+ WeakPtr<WebTask> mWebTask;
+};
+} // namespace mozilla::dom
+#endif
diff --git a/dom/webscheduling/WebTaskSchedulerMainThread.cpp b/dom/webscheduling/WebTaskSchedulerMainThread.cpp
new file mode 100644
index 0000000000..7a5675bc46
--- /dev/null
+++ b/dom/webscheduling/WebTaskSchedulerMainThread.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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 "mozilla/TaskController.h"
+#include "mozilla/dom/TimeoutManager.h"
+
+#include "nsContentUtils.h"
+#include "WebTaskSchedulerMainThread.h"
+
+namespace mozilla::dom {
+
+NS_IMETHODIMP WebTaskMainThreadRunnable::Run() {
+ if (mScheduler) {
+ RefPtr<WebTask> task = mScheduler->GetNextTask();
+ if (task) {
+ task->Run();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult WebTaskSchedulerMainThread::SetTimeoutForDelayedTask(WebTask* aTask,
+ uint64_t aDelay) {
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsIGlobalObject* global = GetParentObject();
+ MOZ_ASSERT(global);
+
+ RefPtr<DelayedWebTaskHandler> handler =
+ new DelayedWebTaskHandler(cx, this, aTask);
+
+ int32_t delay = aDelay > INT32_MAX ? INT32_MAX : (int32_t)aDelay;
+
+ int32_t handle;
+ return global->GetAsInnerWindow()->TimeoutManager().SetTimeout(
+ handler, delay, /* aIsInterval */ false,
+ Timeout::Reason::eDelayedWebTaskTimeout, &handle);
+}
+
+bool WebTaskSchedulerMainThread::DispatchEventLoopRunnable() {
+ RefPtr<WebTaskMainThreadRunnable> runnable =
+ new WebTaskMainThreadRunnable(this);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThreadQueue(runnable.forget(),
+ EventQueuePriority::Normal));
+ return true;
+}
+} // namespace mozilla::dom
diff --git a/dom/webscheduling/WebTaskSchedulerMainThread.h b/dom/webscheduling/WebTaskSchedulerMainThread.h
new file mode 100644
index 0000000000..a5d57f6228
--- /dev/null
+++ b/dom/webscheduling/WebTaskSchedulerMainThread.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef mozilla_dom_WebSchedulerMainThread_h
+#define mozilla_dom_WebSchedulerMainThread_h
+
+#include "WebTaskScheduler.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/AbortFollower.h"
+
+namespace mozilla::dom {
+class WebTaskMainThreadRunnable final : public Runnable {
+ public:
+ explicit WebTaskMainThreadRunnable(WebTaskSchedulerMainThread* aScheduler)
+ : Runnable("WebTaskMainThreadRunnable"), mScheduler(aScheduler) {}
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
+
+ private:
+ ~WebTaskMainThreadRunnable() = default;
+ WeakPtr<WebTaskSchedulerMainThread> mScheduler;
+};
+
+class WebTaskSchedulerMainThread final : public WebTaskScheduler {
+ public:
+ explicit WebTaskSchedulerMainThread(nsIGlobalObject* aParent)
+ : WebTaskScheduler(aParent) {}
+
+ private:
+ nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay) override;
+ bool DispatchEventLoopRunnable() override;
+
+ ~WebTaskSchedulerMainThread() = default;
+};
+} // namespace mozilla::dom
+#endif
diff --git a/dom/webscheduling/WebTaskSchedulerWorker.cpp b/dom/webscheduling/WebTaskSchedulerWorker.cpp
new file mode 100644
index 0000000000..fea65c65db
--- /dev/null
+++ b/dom/webscheduling/WebTaskSchedulerWorker.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "WebTaskSchedulerWorker.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/TimeoutManager.h"
+
+namespace mozilla::dom {
+
+WebTaskWorkerRunnable::WebTaskWorkerRunnable(
+ WorkerPrivate* aWorkerPrivate, WebTaskSchedulerWorker* aSchedulerWorker)
+ : WorkerSameThreadRunnable(aWorkerPrivate, "WebTaskWorkerRunnable"),
+ mSchedulerWorker(aSchedulerWorker) {
+ MOZ_ASSERT(mSchedulerWorker);
+}
+
+WebTaskSchedulerWorker::WebTaskSchedulerWorker(WorkerPrivate* aWorkerPrivate)
+ : WebTaskScheduler(aWorkerPrivate->GlobalScope()),
+ mWorkerPrivate(aWorkerPrivate) {}
+
+bool WebTaskWorkerRunnable::WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (mSchedulerWorker) {
+ RefPtr<WebTask> task = mSchedulerWorker->GetNextTask();
+ if (task) {
+ task->Run();
+ }
+ }
+ return true;
+}
+
+nsresult WebTaskSchedulerWorker::SetTimeoutForDelayedTask(WebTask* aTask,
+ uint64_t aDelay) {
+ if (!mWorkerPrivate) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (!cx) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ RefPtr<DelayedWebTaskHandler> handler =
+ new DelayedWebTaskHandler(cx, this, aTask);
+ ErrorResult rv;
+
+ int32_t delay = aDelay > INT32_MAX ? INT32_MAX : (int32_t)aDelay;
+ mWorkerPrivate->SetTimeout(cx, handler, delay,
+ /* aIsInterval */ false,
+ Timeout::Reason::eDelayedWebTaskTimeout, rv);
+ return rv.StealNSResult();
+}
+
+bool WebTaskSchedulerWorker::DispatchEventLoopRunnable() {
+ if (!mWorkerPrivate) {
+ return false;
+ }
+ RefPtr<WebTaskWorkerRunnable> runnable =
+ new WebTaskWorkerRunnable(mWorkerPrivate, this);
+ return runnable->Dispatch();
+}
+
+void WebTaskSchedulerWorker::Disconnect() {
+ if (mWorkerPrivate) {
+ mWorkerPrivate = nullptr;
+ }
+ WebTaskScheduler::Disconnect();
+}
+} // namespace mozilla::dom
diff --git a/dom/webscheduling/WebTaskSchedulerWorker.h b/dom/webscheduling/WebTaskSchedulerWorker.h
new file mode 100644
index 0000000000..173f95aa01
--- /dev/null
+++ b/dom/webscheduling/WebTaskSchedulerWorker.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* 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/. */
+
+#ifndef mozilla_dom_WebTaskSchedulerWorker_h
+#define mozilla_dom_WebTaskSchedulerWorker_h
+
+#include "WebTaskScheduler.h"
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WebTaskSchedulingBinding.h"
+
+namespace mozilla::dom {
+
+class WebTaskWorkerRunnable final : public WorkerSameThreadRunnable {
+ public:
+ WebTaskWorkerRunnable(WorkerPrivate* aWorkerPrivate,
+ WebTaskSchedulerWorker* aSchedulerWorker);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
+
+ private:
+ ~WebTaskWorkerRunnable() = default;
+ WeakPtr<WebTaskSchedulerWorker> mSchedulerWorker;
+};
+
+class WebTaskSchedulerWorker final : public WebTaskScheduler {
+ public:
+ explicit WebTaskSchedulerWorker(WorkerPrivate* aWorkerPrivate);
+
+ void Disconnect() override;
+
+ private:
+ ~WebTaskSchedulerWorker() = default;
+
+ nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay) override;
+ bool DispatchEventLoopRunnable() override;
+
+ CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate;
+};
+} // namespace mozilla::dom
+#endif
diff --git a/dom/webscheduling/moz.build b/dom/webscheduling/moz.build
new file mode 100644
index 0000000000..b30b87972c
--- /dev/null
+++ b/dom/webscheduling/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Performance")
+
+EXPORTS.mozilla.dom += [
+ "TaskSignal.h",
+ "WebTaskController.h",
+ "WebTaskScheduler.h",
+ "WebTaskSchedulerMainThread.h",
+ "WebTaskSchedulerWorker.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTaskController.cpp",
+ "WebTaskScheduler.cpp",
+ "WebTaskSchedulerMainThread.cpp",
+ "WebTaskSchedulerWorker.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"