Bug 1854367 - Mark storage-access-permission.sub.https.window.html subtest as intermi...
[gecko.git] / dom / webscheduling / WebTaskScheduler.cpp
blob14cc51ba660c2145675656739f578fe6c32b08be
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 remove();
84 ErrorResult error;
86 nsIGlobalObject* global = mPromise->GetGlobalObject();
87 if (!global || global->IsDying()) {
88 return false;
91 AutoJSAPI jsapi;
92 if (!jsapi.Init(global)) {
93 return false;
96 JS::Rooted<JS::Value> returnVal(jsapi.cx());
98 MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
100 MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask",
101 CallbackFunction::eRethrowExceptions);
103 error.WouldReportJSException();
105 #ifdef DEBUG
106 Promise::PromiseState promiseState = mPromise->State();
108 // If the state is Rejected, it means the above Call triggers the
109 // RunAbortAlgorithm method and rejected the promise
110 MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending,
111 promiseState == Promise::PromiseState::Rejected);
112 #endif
114 if (error.Failed()) {
115 if (!error.IsUncatchableException()) {
116 mPromise->MaybeReject(std::move(error));
117 } else {
118 error.SuppressException();
120 } else {
121 mPromise->MaybeResolve(returnVal);
124 MOZ_ASSERT(!isInList());
125 return true;
128 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent,
129 mStaticPriorityTaskQueues,
130 mDynamicPriorityTaskQueues)
132 /* static */
133 already_AddRefed<WebTaskSchedulerMainThread>
134 WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
135 RefPtr<WebTaskSchedulerMainThread> scheduler =
136 new WebTaskSchedulerMainThread(aWindow->AsGlobal());
137 return scheduler.forget();
140 already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
141 WorkerPrivate* aWorkerPrivate) {
142 aWorkerPrivate->AssertIsOnWorkerThread();
143 RefPtr<WebTaskSchedulerWorker> scheduler =
144 new WebTaskSchedulerWorker(aWorkerPrivate);
145 return scheduler.forget();
148 WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
149 : mParent(aParent), mNextEnqueueOrder(1) {
150 MOZ_ASSERT(aParent);
153 JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
154 JS::Handle<JSObject*> aGivenProto) {
155 return Scheduler_Binding::Wrap(cx, this, aGivenProto);
158 already_AddRefed<Promise> WebTaskScheduler::PostTask(
159 SchedulerPostTaskCallback& aCallback,
160 const SchedulerPostTaskOptions& aOptions) {
161 const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal;
162 const Optional<TaskPriority>& taskPriority = aOptions.mPriority;
164 ErrorResult rv;
165 // Instead of making WebTaskScheduler::PostTask throws, we always
166 // create the promise and return it. This is because we need to
167 // create the promise explicitly to be able to reject it with
168 // signal's reason.
169 RefPtr<Promise> promise = Promise::Create(mParent, rv);
170 if (rv.Failed()) {
171 return nullptr;
174 nsIGlobalObject* global = GetParentObject();
175 if (!global || global->IsDying()) {
176 promise->MaybeRejectWithNotSupportedError("Current window is detached");
177 return promise.forget();
180 if (taskSignal.WasPassed()) {
181 AbortSignal& signalValue = taskSignal.Value();
183 if (signalValue.Aborted()) {
184 AutoJSAPI jsapi;
185 if (!jsapi.Init(global)) {
186 promise->MaybeReject(NS_ERROR_UNEXPECTED);
187 return promise.forget();
190 JSContext* cx = jsapi.cx();
191 JS::Rooted<JS::Value> reason(cx);
192 signalValue.GetReason(cx, &reason);
193 promise->MaybeReject(reason);
194 return promise.forget();
198 // Let queue be the result of selecting the scheduler task queue for scheduler
199 // given signal and priority.
200 WebTaskQueue& taskQueue = SelectTaskQueue(taskSignal, taskPriority);
202 uint64_t delay = aOptions.mDelay;
204 RefPtr<WebTask> task = CreateTask(taskQueue, taskSignal, aCallback, promise);
205 if (delay > 0) {
206 nsresult rv = SetTimeoutForDelayedTask(task, delay);
207 if (NS_FAILED(rv)) {
208 promise->MaybeRejectWithUnknownError(
209 "Failed to setup timeout for delayed task");
211 return promise.forget();
214 if (!QueueTask(task)) {
215 MOZ_ASSERT(task->isInList());
216 task->remove();
218 promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
219 return promise.forget();
222 return promise.forget();
225 already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
226 WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
227 SchedulerPostTaskCallback& aCallback, Promise* aPromise) {
228 uint32_t nextEnqueueOrder = mNextEnqueueOrder;
229 ++mNextEnqueueOrder;
231 RefPtr<WebTask> task = new WebTask(nextEnqueueOrder, aCallback, aPromise);
233 aQueue.AddTask(task);
235 if (aSignal.WasPassed()) {
236 AbortSignal& signalValue = aSignal.Value();
237 task->Follow(&signalValue);
240 return task.forget();
243 bool WebTaskScheduler::QueueTask(WebTask* aTask) {
244 if (!DispatchEventLoopRunnable()) {
245 return false;
247 MOZ_ASSERT(!aTask->HasScheduled());
248 aTask->SetHasScheduled(true);
249 return true;
252 WebTask* WebTaskScheduler::GetNextTask() const {
253 // We first combine queues from both mStaticPriorityTaskQueues and
254 // mDynamicPriorityTaskQueues into a single hash map which the
255 // keys are the priorities and the values are all the queues that
256 // belong to this priority.
258 // Then From the queues which
259 // 1. Have scheduled tasks
260 // 2. Its priority is not less than any other queues' priority
261 // We pick the task which has the smallest enqueue order.
262 nsTHashMap<nsUint32HashKey, nsTArray<WebTaskQueue*>> allQueues;
264 for (auto iter = mStaticPriorityTaskQueues.ConstIter(); !iter.Done();
265 iter.Next()) {
266 const auto& queue = iter.Data();
267 if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
268 nsTArray<WebTaskQueue*>& queuesForThisPriority =
269 allQueues.LookupOrInsert(static_cast<uint32_t>(iter.Key()));
270 queuesForThisPriority.AppendElement(queue.get());
274 for (auto iter = mDynamicPriorityTaskQueues.ConstIter(); !iter.Done();
275 iter.Next()) {
276 const auto& queue = iter.Data();
277 if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
278 nsTArray<WebTaskQueue*>& queuesForThisPriority = allQueues.LookupOrInsert(
279 static_cast<uint32_t>(iter.Key()->Priority()));
280 queuesForThisPriority.AppendElement(queue.get());
284 if (allQueues.IsEmpty()) {
285 return nullptr;
288 for (uint32_t priority = static_cast<uint32_t>(TaskPriority::User_blocking);
289 priority < static_cast<uint32_t>(TaskPriority::EndGuard_); ++priority) {
290 if (auto queues = allQueues.Lookup(priority)) {
291 WebTaskQueue* oldestQueue = nullptr;
292 MOZ_ASSERT(!queues.Data().IsEmpty());
293 for (auto& webTaskQueue : queues.Data()) {
294 MOZ_ASSERT(webTaskQueue->GetFirstScheduledTask());
295 if (!oldestQueue) {
296 oldestQueue = webTaskQueue;
297 } else {
298 WebTask* firstScheduledRunnableForCurrentQueue =
299 webTaskQueue->GetFirstScheduledTask();
300 WebTask* firstScheduledRunnableForOldQueue =
301 oldestQueue->GetFirstScheduledTask();
302 if (firstScheduledRunnableForOldQueue->EnqueueOrder() >
303 firstScheduledRunnableForCurrentQueue->EnqueueOrder()) {
304 oldestQueue = webTaskQueue;
308 MOZ_ASSERT(oldestQueue);
309 return oldestQueue->GetFirstScheduledTask();
312 return nullptr;
315 void WebTaskScheduler::Disconnect() {
316 mStaticPriorityTaskQueues.Clear();
317 mDynamicPriorityTaskQueues.Clear();
320 void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
321 WebTaskQueue* const taskQueue = mDynamicPriorityTaskQueues.Get(aTaskSignal);
322 MOZ_ASSERT(taskQueue);
323 taskQueue->SetPriority(aTaskSignal->Priority());
326 WebTaskQueue& WebTaskScheduler::SelectTaskQueue(
327 const Optional<OwningNonNull<AbortSignal>>& aSignal,
328 const Optional<TaskPriority>& aPriority) {
329 bool useSignal = !aPriority.WasPassed() && aSignal.WasPassed() &&
330 aSignal.Value().IsTaskSignal();
332 if (useSignal) {
333 TaskSignal* taskSignal = static_cast<TaskSignal*>(&(aSignal.Value()));
334 WebTaskQueue* const taskQueue =
335 mDynamicPriorityTaskQueues.GetOrInsertNew(taskSignal);
336 taskQueue->SetPriority(taskSignal->Priority());
337 taskSignal->SetWebTaskScheduler(this);
338 MOZ_ASSERT(mDynamicPriorityTaskQueues.Contains(taskSignal));
340 return *taskQueue;
343 TaskPriority taskPriority =
344 aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
346 WebTaskQueue* const taskQueue = mStaticPriorityTaskQueues.GetOrInsertNew(
347 static_cast<uint32_t>(taskPriority));
348 taskQueue->SetPriority(taskPriority);
349 MOZ_ASSERT(
350 mStaticPriorityTaskQueues.Contains(static_cast<uint32_t>(taskPriority)));
351 return *taskQueue;
354 } // namespace mozilla::dom