diff options
Diffstat (limited to 'dom/webscheduling')
-rw-r--r-- | dom/webscheduling/TaskSignal.h | 63 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskController.cpp | 70 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskController.h | 35 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskScheduler.cpp | 383 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskScheduler.h | 207 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskSchedulerMainThread.cpp | 54 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskSchedulerMainThread.h | 42 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskSchedulerWorker.cpp | 72 | ||||
-rw-r--r-- | dom/webscheduling/WebTaskSchedulerWorker.h | 48 | ||||
-rw-r--r-- | dom/webscheduling/moz.build | 27 |
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" |