Bug 1856777 [wpt PR 42330] - Update wpt metadata, a=testonly
[gecko.git] / dom / webscheduling / WebTaskScheduler.cpp
blobdc49fa2d8a5817e0116a2f62129908fcecca2305
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/. */
7 #include "nsTHashMap.h"
8 #include "WebTaskScheduler.h"
9 #include "WebTaskSchedulerWorker.h"
10 #include "WebTaskSchedulerMainThread.h"
11 #include "TaskSignal.h"
12 #include "nsGlobalWindowInner.h"
14 #include "mozilla/dom/WorkerPrivate.h"
15 #include "mozilla/dom/TimeoutManager.h"
17 namespace mozilla::dom {
19 inline void ImplCycleCollectionTraverse(
20 nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue,
21 const char* aName, uint32_t aFlags = 0) {
22 ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags);
25 NS_IMPL_CYCLE_COLLECTION_CLASS(WebTask)
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTask)
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
32 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTask)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
34 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
35 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
38 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTask)
39 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTask)
41 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTask)
42 NS_INTERFACE_MAP_ENTRY(nsISupports)
43 NS_INTERFACE_MAP_END
45 NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler)
47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler)
48 NS_INTERFACE_MAP_ENTRY(nsISupports)
49 NS_INTERFACE_MAP_END
51 NS_IMPL_CYCLE_COLLECTING_ADDREF(DelayedWebTaskHandler)
52 NS_IMPL_CYCLE_COLLECTING_RELEASE(DelayedWebTaskHandler)
54 void WebTask::RunAbortAlgorithm() {
55 // no-op if WebTask::Run has been called already
56 if (mPromise->State() == Promise::PromiseState::Pending) {
57 // There are two things that can keep a WebTask alive, either the abort
58 // signal or WebTaskQueue.
59 // It's possible that this task get cleared out from the WebTaskQueue first,
60 // and then the abort signal get aborted. For example, the callback function
61 // was async and there's a signal.abort() call in the callback.
62 if (isInList()) {
63 remove();
66 AutoJSAPI jsapi;
67 if (!jsapi.Init(mPromise->GetGlobalObject())) {
68 mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
69 } else {
70 JSContext* cx = jsapi.cx();
71 JS::Rooted<JS::Value> reason(cx);
72 Signal()->GetReason(cx, &reason);
73 mPromise->MaybeReject(reason);
77 MOZ_ASSERT(!isInList());
80 bool WebTask::Run() {
81 MOZ_ASSERT(HasScheduled());
82 MOZ_ASSERT(mOwnerQueue);
83 remove();
85 mOwnerQueue->RemoveEntryFromTaskQueueMapIfNeeded();
86 mOwnerQueue = nullptr;
87 // At this point mOwnerQueue is destructed and this is fine.
88 // The caller of WebTask::Run keeps it alive.
90 ErrorResult error;
92 nsIGlobalObject* global = mPromise->GetGlobalObject();
93 if (!global || global->IsDying()) {
94 return false;
97 AutoJSAPI jsapi;
98 if (!jsapi.Init(global)) {
99 return false;
102 JS::Rooted<JS::Value> returnVal(jsapi.cx());
104 MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
106 MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask",
107 CallbackFunction::eRethrowExceptions);
109 error.WouldReportJSException();
111 #ifdef DEBUG
112 Promise::PromiseState promiseState = mPromise->State();
114 // If the state is Rejected, it means the above Call triggers the
115 // RunAbortAlgorithm method and rejected the promise
116 MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending,
117 promiseState == Promise::PromiseState::Rejected);
118 #endif
120 if (error.Failed()) {
121 if (!error.IsUncatchableException()) {
122 mPromise->MaybeReject(std::move(error));
123 } else {
124 error.SuppressException();
126 } else {
127 mPromise->MaybeResolve(returnVal);
130 MOZ_ASSERT(!isInList());
131 return true;
134 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent,
135 mStaticPriorityTaskQueues,
136 mDynamicPriorityTaskQueues)
138 /* static */
139 already_AddRefed<WebTaskSchedulerMainThread>
140 WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
141 RefPtr<WebTaskSchedulerMainThread> scheduler =
142 new WebTaskSchedulerMainThread(aWindow->AsGlobal());
143 return scheduler.forget();
146 already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
147 WorkerPrivate* aWorkerPrivate) {
148 aWorkerPrivate->AssertIsOnWorkerThread();
149 RefPtr<WebTaskSchedulerWorker> scheduler =
150 new WebTaskSchedulerWorker(aWorkerPrivate);
151 return scheduler.forget();
154 WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
155 : mParent(aParent), mNextEnqueueOrder(1) {
156 MOZ_ASSERT(aParent);
159 JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
160 JS::Handle<JSObject*> aGivenProto) {
161 return Scheduler_Binding::Wrap(cx, this, aGivenProto);
164 already_AddRefed<Promise> WebTaskScheduler::PostTask(
165 SchedulerPostTaskCallback& aCallback,
166 const SchedulerPostTaskOptions& aOptions) {
167 const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal;
168 const Optional<TaskPriority>& taskPriority = aOptions.mPriority;
170 ErrorResult rv;
171 // Instead of making WebTaskScheduler::PostTask throws, we always
172 // create the promise and return it. This is because we need to
173 // create the promise explicitly to be able to reject it with
174 // signal's reason.
175 RefPtr<Promise> promise = Promise::Create(mParent, rv);
176 if (rv.Failed()) {
177 return nullptr;
180 nsIGlobalObject* global = GetParentObject();
181 if (!global || global->IsDying()) {
182 promise->MaybeRejectWithNotSupportedError("Current window is detached");
183 return promise.forget();
186 if (taskSignal.WasPassed()) {
187 AbortSignal& signalValue = taskSignal.Value();
189 if (signalValue.Aborted()) {
190 AutoJSAPI jsapi;
191 if (!jsapi.Init(global)) {
192 promise->MaybeReject(NS_ERROR_UNEXPECTED);
193 return promise.forget();
196 JSContext* cx = jsapi.cx();
197 JS::Rooted<JS::Value> reason(cx);
198 signalValue.GetReason(cx, &reason);
199 promise->MaybeReject(reason);
200 return promise.forget();
204 // Let queue be the result of selecting the scheduler task queue for scheduler
205 // given signal and priority.
206 WebTaskQueue& taskQueue = SelectTaskQueue(taskSignal, taskPriority);
208 uint64_t delay = aOptions.mDelay;
210 RefPtr<WebTask> task = CreateTask(taskQueue, taskSignal, aCallback, promise);
211 if (delay > 0) {
212 nsresult rv = SetTimeoutForDelayedTask(task, delay);
213 if (NS_FAILED(rv)) {
214 promise->MaybeRejectWithUnknownError(
215 "Failed to setup timeout for delayed task");
217 return promise.forget();
220 if (!QueueTask(task)) {
221 MOZ_ASSERT(task->isInList());
222 task->remove();
224 promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
225 return promise.forget();
228 return promise.forget();
231 already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
232 WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
233 SchedulerPostTaskCallback& aCallback, Promise* aPromise) {
234 uint32_t nextEnqueueOrder = mNextEnqueueOrder;
235 ++mNextEnqueueOrder;
237 RefPtr<WebTask> task = new WebTask(nextEnqueueOrder, aCallback, aPromise);
239 aQueue.AddTask(task);
241 if (aSignal.WasPassed()) {
242 AbortSignal& signalValue = aSignal.Value();
243 task->Follow(&signalValue);
246 return task.forget();
249 bool WebTaskScheduler::QueueTask(WebTask* aTask) {
250 if (!DispatchEventLoopRunnable()) {
251 return false;
253 MOZ_ASSERT(!aTask->HasScheduled());
254 aTask->SetHasScheduled(true);
255 return true;
258 WebTask* WebTaskScheduler::GetNextTask() const {
259 // We first combine queues from both mStaticPriorityTaskQueues and
260 // mDynamicPriorityTaskQueues into a single hash map which the
261 // keys are the priorities and the values are all the queues that
262 // belong to this priority.
264 // Then From the queues which
265 // 1. Have scheduled tasks
266 // 2. Its priority is not less than any other queues' priority
267 // We pick the task which has the smallest enqueue order.
268 nsTHashMap<nsUint32HashKey, nsTArray<WebTaskQueue*>> allQueues;
270 for (auto iter = mStaticPriorityTaskQueues.ConstIter(); !iter.Done();
271 iter.Next()) {
272 const auto& queue = iter.Data();
273 if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
274 nsTArray<WebTaskQueue*>& queuesForThisPriority =
275 allQueues.LookupOrInsert(static_cast<uint32_t>(iter.Key()));
276 queuesForThisPriority.AppendElement(queue.get());
280 for (auto iter = mDynamicPriorityTaskQueues.ConstIter(); !iter.Done();
281 iter.Next()) {
282 const auto& queue = iter.Data();
283 if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
284 nsTArray<WebTaskQueue*>& queuesForThisPriority = allQueues.LookupOrInsert(
285 static_cast<uint32_t>(iter.Key()->Priority()));
286 queuesForThisPriority.AppendElement(queue.get());
290 if (allQueues.IsEmpty()) {
291 return nullptr;
294 for (uint32_t priority = static_cast<uint32_t>(TaskPriority::User_blocking);
295 priority < static_cast<uint32_t>(TaskPriority::EndGuard_); ++priority) {
296 if (auto queues = allQueues.Lookup(priority)) {
297 WebTaskQueue* oldestQueue = nullptr;
298 MOZ_ASSERT(!queues.Data().IsEmpty());
299 for (auto& webTaskQueue : queues.Data()) {
300 MOZ_ASSERT(webTaskQueue->GetFirstScheduledTask());
301 if (!oldestQueue) {
302 oldestQueue = webTaskQueue;
303 } else {
304 WebTask* firstScheduledRunnableForCurrentQueue =
305 webTaskQueue->GetFirstScheduledTask();
306 WebTask* firstScheduledRunnableForOldQueue =
307 oldestQueue->GetFirstScheduledTask();
308 if (firstScheduledRunnableForOldQueue->EnqueueOrder() >
309 firstScheduledRunnableForCurrentQueue->EnqueueOrder()) {
310 oldestQueue = webTaskQueue;
314 MOZ_ASSERT(oldestQueue);
315 return oldestQueue->GetFirstScheduledTask();
318 return nullptr;
321 void WebTaskScheduler::Disconnect() {
322 mStaticPriorityTaskQueues.Clear();
323 mDynamicPriorityTaskQueues.Clear();
326 void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
327 WebTaskQueue* const taskQueue = mDynamicPriorityTaskQueues.Get(aTaskSignal);
328 MOZ_ASSERT(taskQueue);
329 taskQueue->SetPriority(aTaskSignal->Priority());
332 WebTaskQueue& WebTaskScheduler::SelectTaskQueue(
333 const Optional<OwningNonNull<AbortSignal>>& aSignal,
334 const Optional<TaskPriority>& aPriority) {
335 bool useSignal = !aPriority.WasPassed() && aSignal.WasPassed() &&
336 aSignal.Value().IsTaskSignal();
338 if (useSignal) {
339 TaskSignal* taskSignal = static_cast<TaskSignal*>(&(aSignal.Value()));
340 WebTaskQueue* const taskQueue =
341 mDynamicPriorityTaskQueues.GetOrInsertNew(taskSignal, taskSignal, this);
342 taskQueue->SetPriority(taskSignal->Priority());
343 taskSignal->SetWebTaskScheduler(this);
344 MOZ_ASSERT(mDynamicPriorityTaskQueues.Contains(taskSignal));
346 return *taskQueue;
349 TaskPriority taskPriority =
350 aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
352 uint32_t staticTaskQueueMapKey = static_cast<uint32_t>(taskPriority);
353 WebTaskQueue* const taskQueue = mStaticPriorityTaskQueues.GetOrInsertNew(
354 staticTaskQueueMapKey, staticTaskQueueMapKey, this);
355 taskQueue->SetPriority(taskPriority);
356 MOZ_ASSERT(
357 mStaticPriorityTaskQueues.Contains(static_cast<uint32_t>(taskPriority)));
358 return *taskQueue;
361 void WebTaskScheduler::DeleteEntryFromStaticQueueMap(uint32_t aKey) {
362 DebugOnly<bool> result = mStaticPriorityTaskQueues.Remove(aKey);
363 MOZ_ASSERT(result);
366 void WebTaskScheduler::DeleteEntryFromDynamicQueueMap(TaskSignal* aKey) {
367 DebugOnly<bool> result = mDynamicPriorityTaskQueues.Remove(aKey);
368 MOZ_ASSERT(result);
371 void WebTaskQueue::RemoveEntryFromTaskQueueMapIfNeeded() {
372 MOZ_ASSERT(mScheduler);
373 if (mTasks.isEmpty()) {
374 if (mOwnerKey.is<uint32_t>()) {
375 mScheduler->DeleteEntryFromStaticQueueMap(mOwnerKey.as<uint32_t>());
376 } else {
377 MOZ_ASSERT(mOwnerKey.is<TaskSignal*>());
378 mScheduler->DeleteEntryFromDynamicQueueMap(mOwnerKey.as<TaskSignal*>());
382 } // namespace mozilla::dom