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());
82 MOZ_ASSERT(mOwnerQueue
);
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.
92 nsIGlobalObject
* global
= mPromise
->GetGlobalObject();
93 if (!global
|| global
->IsDying()) {
98 if (!jsapi
.Init(global
)) {
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();
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
);
120 if (error
.Failed()) {
121 if (!error
.IsUncatchableException()) {
122 mPromise
->MaybeReject(std::move(error
));
124 error
.SuppressException();
127 mPromise
->MaybeResolve(returnVal
);
130 MOZ_ASSERT(!isInList());
134 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler
, mParent
,
135 mStaticPriorityTaskQueues
,
136 mDynamicPriorityTaskQueues
)
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) {
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
;
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
175 RefPtr
<Promise
> promise
= Promise::Create(mParent
, rv
);
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()) {
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
);
212 nsresult rv
= SetTimeoutForDelayedTask(task
, delay
);
214 promise
->MaybeRejectWithUnknownError(
215 "Failed to setup timeout for delayed task");
217 return promise
.forget();
220 if (!QueueTask(task
)) {
221 MOZ_ASSERT(task
->isInList());
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
;
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()) {
253 MOZ_ASSERT(!aTask
->HasScheduled());
254 aTask
->SetHasScheduled(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();
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();
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()) {
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());
302 oldestQueue
= webTaskQueue
;
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();
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();
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
));
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
);
357 mStaticPriorityTaskQueues
.Contains(static_cast<uint32_t>(taskPriority
)));
361 void WebTaskScheduler::DeleteEntryFromStaticQueueMap(uint32_t aKey
) {
362 DebugOnly
<bool> result
= mStaticPriorityTaskQueues
.Remove(aKey
);
366 void WebTaskScheduler::DeleteEntryFromDynamicQueueMap(TaskSignal
* aKey
) {
367 DebugOnly
<bool> result
= mDynamicPriorityTaskQueues
.Remove(aKey
);
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>());
377 MOZ_ASSERT(mOwnerKey
.is
<TaskSignal
*>());
378 mScheduler
->DeleteEntryFromDynamicQueueMap(mOwnerKey
.as
<TaskSignal
*>());
382 } // namespace mozilla::dom