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
)
45 NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler
)
47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler
)
48 NS_INTERFACE_MAP_ENTRY(nsISupports
)
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.
67 if (!jsapi
.Init(mPromise
->GetGlobalObject())) {
68 mPromise
->MaybeReject(NS_ERROR_UNEXPECTED
);
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());
81 MOZ_ASSERT(HasScheduled());
86 nsIGlobalObject
* global
= mPromise
->GetGlobalObject();
87 if (!global
|| global
->IsDying()) {
92 if (!jsapi
.Init(global
)) {
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();
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
);
114 if (error
.Failed()) {
115 if (!error
.IsUncatchableException()) {
116 mPromise
->MaybeReject(std::move(error
));
118 error
.SuppressException();
121 mPromise
->MaybeResolve(returnVal
);
124 MOZ_ASSERT(!isInList());
128 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler
, mParent
,
129 mStaticPriorityTaskQueues
,
130 mDynamicPriorityTaskQueues
)
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) {
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
;
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
169 RefPtr
<Promise
> promise
= Promise::Create(mParent
, rv
);
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()) {
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
);
206 nsresult rv
= SetTimeoutForDelayedTask(task
, delay
);
208 promise
->MaybeRejectWithUnknownError(
209 "Failed to setup timeout for delayed task");
211 return promise
.forget();
214 if (!QueueTask(task
)) {
215 MOZ_ASSERT(task
->isInList());
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
;
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()) {
247 MOZ_ASSERT(!aTask
->HasScheduled());
248 aTask
->SetHasScheduled(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();
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();
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()) {
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());
296 oldestQueue
= webTaskQueue
;
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();
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();
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
));
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
);
350 mStaticPriorityTaskQueues
.Contains(static_cast<uint32_t>(taskPriority
)));
354 } // namespace mozilla::dom