WebTaskScheduler.cpp (23478B)
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/. */ 6 7 #include "WebTaskScheduler.h" 8 9 #include "WebTaskSchedulerMainThread.h" 10 #include "WebTaskSchedulerWorker.h" 11 #include "mozilla/dom/TimeoutManager.h" 12 #include "mozilla/dom/WorkerPrivate.h" 13 #include "nsGlobalWindowInner.h" 14 #include "nsTHashMap.h" 15 16 namespace mozilla::dom { 17 18 // Keeps track of all the existings schedulers that 19 // share the same event loop. 20 MOZ_RUNINIT static LinkedList<WebTaskScheduler> gWebTaskSchedulersMainThread; 21 22 static Atomic<uint64_t> gWebTaskEnqueueOrder(0); 23 24 // According to 25 // https://github.com/WICG/scheduling-apis/issues/113#issuecomment-2596102676, 26 // tasks with User_blocking or User_visible needs to run before timers. 27 static bool IsNormalOrHighPriority(TaskPriority aPriority) { 28 return aPriority == TaskPriority::User_blocking || 29 aPriority == TaskPriority::User_visible; 30 } 31 32 inline void ImplCycleCollectionTraverse( 33 nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue, 34 const char* aName, uint32_t aFlags = 0) { 35 ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags); 36 } 37 38 inline void ImplCycleCollectionTraverse( 39 nsCycleCollectionTraversalCallback& aCallback, 40 const WebTaskQueueHashKey& aField, const char* aName, uint32_t aFlags = 0) { 41 const WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey(); 42 if (typeKey.is<RefPtr<TaskSignal>>()) { 43 ImplCycleCollectionTraverse(aCallback, typeKey.as<RefPtr<TaskSignal>>(), 44 aName, aFlags); 45 } 46 } 47 48 inline void ImplCycleCollectionUnlink(WebTaskQueueHashKey& aField) { 49 WebTaskQueueHashKey::WebTaskQueueTypeKey& typeKey = aField.GetTypeKey(); 50 if (typeKey.is<RefPtr<TaskSignal>>()) { 51 ImplCycleCollectionUnlink(typeKey.as<RefPtr<TaskSignal>>()); 52 } 53 } 54 55 NS_IMPL_CYCLE_COLLECTION_CLASS(WebTaskSchedulingState) 56 57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTaskSchedulingState) 58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortSource, mPrioritySource); 59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 60 61 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTaskSchedulingState) 62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortSource, mPrioritySource); 63 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 64 65 NS_IMPL_CYCLE_COLLECTION_CLASS(WebTask) 66 67 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTask) 68 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) 69 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) 70 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskQueueHashKey) 71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSchedulingState) 72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 73 74 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTask) 75 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) 76 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) 77 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskQueueHashKey) 78 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSchedulingState) 79 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 80 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 81 82 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTask) 83 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTask) 84 85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTask) 86 NS_INTERFACE_MAP_ENTRY(nsISupports) 87 NS_INTERFACE_MAP_END 88 89 NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler) 90 91 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler) 92 NS_INTERFACE_MAP_ENTRY(nsISupports) 93 NS_INTERFACE_MAP_END 94 95 NS_IMPL_CYCLE_COLLECTING_ADDREF(DelayedWebTaskHandler) 96 NS_IMPL_CYCLE_COLLECTING_RELEASE(DelayedWebTaskHandler) 97 98 WebTask::WebTask(uint32_t aEnqueueOrder, 99 const Maybe<SchedulerPostTaskCallback&>& aCallback, 100 WebTaskSchedulingState* aSchedlingState, Promise* aPromise, 101 WebTaskScheduler* aWebTaskScheduler, 102 const WebTaskQueueHashKey& aHashKey) 103 : mEnqueueOrder(aEnqueueOrder), 104 mPromise(aPromise), 105 mHasScheduled(false), 106 mSchedulingState(aSchedlingState), 107 mScheduler(aWebTaskScheduler), 108 mWebTaskQueueHashKey(aHashKey) { 109 if (aCallback.isSome()) { 110 mCallback = &aCallback.ref(); 111 } 112 } 113 114 void WebTask::RunAbortAlgorithm() { 115 // no-op if WebTask::Run has been called already 116 if (mPromise->State() == Promise::PromiseState::Pending) { 117 // There are two things that can keep a WebTask alive, either the abort 118 // signal or WebTaskQueue. 119 // It's possible that this task get cleared out from the WebTaskQueue first, 120 // and then the abort signal get aborted. For example, the callback function 121 // was async and there's a signal.abort() call in the callback. 122 if (isInList()) { 123 remove(); 124 MOZ_ASSERT(mScheduler); 125 if (HasScheduled()) { 126 mScheduler->NotifyTaskWillBeRunOrAborted(this); 127 } 128 } 129 130 AutoJSAPI jsapi; 131 if (!jsapi.Init(mPromise->GetGlobalObject())) { 132 mPromise->MaybeReject(NS_ERROR_UNEXPECTED); 133 } else { 134 JSContext* cx = jsapi.cx(); 135 JS::Rooted<JS::Value> reason(cx); 136 Signal()->GetReason(cx, &reason); 137 mPromise->MaybeReject(reason); 138 } 139 } 140 141 MOZ_ASSERT(!isInList()); 142 } 143 144 bool WebTask::Run() { 145 MOZ_ASSERT(HasScheduled()); 146 MOZ_ASSERT(mScheduler); 147 remove(); 148 149 mScheduler->NotifyTaskWillBeRunOrAborted(this); 150 ClearWebTaskScheduler(); 151 152 if (!mCallback) { 153 // Scheduler.yield 154 mPromise->MaybeResolveWithUndefined(); 155 MOZ_ASSERT(!isInList()); 156 return true; 157 } 158 159 MOZ_ASSERT(mSchedulingState); 160 161 ErrorResult error; 162 163 nsIGlobalObject* global = mPromise->GetGlobalObject(); 164 if (!global || global->IsDying()) { 165 return false; 166 } 167 168 // 11.2.2 Set event loop’s current scheduling state to state. 169 global->SetWebTaskSchedulingState(mSchedulingState); 170 171 AutoJSAPI jsapi; 172 if (!jsapi.Init(global)) { 173 return false; 174 } 175 176 JS::Rooted<JS::Value> returnVal(jsapi.cx()); 177 178 MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending); 179 180 MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask", 181 CallbackFunction::eRethrowExceptions); 182 183 // 11.2.4 Set event loop’s current scheduling state to null. 184 global->SetWebTaskSchedulingState(nullptr); 185 186 error.WouldReportJSException(); 187 188 #ifdef DEBUG 189 Promise::PromiseState promiseState = mPromise->State(); 190 191 // If the state is Rejected, it means the above Call triggers the 192 // RunAbortAlgorithm method and rejected the promise 193 MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending, 194 promiseState == Promise::PromiseState::Rejected); 195 #endif 196 197 if (error.Failed()) { 198 if (!error.IsUncatchableException()) { 199 mPromise->MaybeReject(std::move(error)); 200 } else { 201 error.SuppressException(); 202 } 203 } else { 204 mPromise->MaybeResolve(returnVal); 205 } 206 207 MOZ_ASSERT(!isInList()); 208 return true; 209 } 210 211 inline void ImplCycleCollectionUnlink( 212 nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField) { 213 aField.Clear(); 214 } 215 216 inline void ImplCycleCollectionTraverse( 217 nsCycleCollectionTraversalCallback& aCallback, 218 nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& aField, const char* aName, 219 uint32_t aFlags = 0) { 220 for (auto& entry : aField) { 221 ImplCycleCollectionTraverse( 222 aCallback, entry.GetKey(), 223 "nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueueHashKey", 224 aFlags); 225 ImplCycleCollectionTraverse( 226 aCallback, *entry.GetModifiableData(), 227 "nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>::WebTaskQueue", aFlags); 228 } 229 } 230 231 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent, mWebTaskQueues) 232 233 /* static */ 234 already_AddRefed<WebTaskSchedulerMainThread> 235 WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) { 236 RefPtr<WebTaskSchedulerMainThread> scheduler = 237 new WebTaskSchedulerMainThread(aWindow->AsGlobal()); 238 gWebTaskSchedulersMainThread.insertBack(scheduler); 239 return scheduler.forget(); 240 } 241 242 already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker( 243 WorkerPrivate* aWorkerPrivate) { 244 aWorkerPrivate->AssertIsOnWorkerThread(); 245 RefPtr<WebTaskSchedulerWorker> scheduler = 246 WebTaskSchedulerWorker::Create(aWorkerPrivate); 247 return scheduler.forget(); 248 } 249 250 WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent) 251 : mParent(aParent) { 252 MOZ_ASSERT(aParent); 253 } 254 255 JSObject* WebTaskScheduler::WrapObject(JSContext* cx, 256 JS::Handle<JSObject*> aGivenProto) { 257 return Scheduler_Binding::Wrap(cx, this, aGivenProto); 258 } 259 260 static bool ShouldRejectPromiseWithReasonCausedByAbortSignal( 261 AbortSignal& aAbortSignal, nsIGlobalObject* aGlobal, Promise& aPromise) { 262 MOZ_ASSERT(aGlobal); 263 if (!aAbortSignal.Aborted()) { 264 return false; 265 } 266 267 AutoJSAPI jsapi; 268 if (!jsapi.Init(aGlobal)) { 269 aPromise.MaybeRejectWithNotSupportedError( 270 "Failed to initialize the JS context"); 271 return true; 272 } 273 274 JSContext* cx = jsapi.cx(); 275 JS::Rooted<JS::Value> reason(cx); 276 aAbortSignal.GetReason(cx, &reason); 277 aPromise.MaybeReject(reason); 278 return true; 279 } 280 281 // https://wicg.github.io/scheduling-apis/#sec-scheduler-alg-scheduling-tasks-and-continuations 282 already_AddRefed<Promise> WebTaskScheduler::PostTask( 283 SchedulerPostTaskCallback& aCallback, 284 const SchedulerPostTaskOptions& aOptions) { 285 const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal; 286 const Optional<TaskPriority>& taskPriority = aOptions.mPriority; 287 288 ErrorResult rv; 289 // Instead of making WebTaskScheduler::PostTask throws, we always 290 // create the promise and return it. This is because we need to 291 // create the promise explicitly to be able to reject it with 292 // signal's reason. 293 RefPtr<Promise> promise = Promise::Create(mParent, rv); 294 if (rv.Failed()) { 295 return nullptr; 296 } 297 298 nsIGlobalObject* global = GetParentObject(); 299 if (!global || global->IsDying()) { 300 promise->MaybeRejectWithNotSupportedError("Current window is detached"); 301 return promise.forget(); 302 } 303 304 // 4. Let state be a new scheduling state. 305 RefPtr<WebTaskSchedulingState> newState = new WebTaskSchedulingState(); 306 AbortSignal* signalValue = nullptr; 307 if (taskSignal.WasPassed()) { 308 signalValue = &taskSignal.Value(); 309 // 3. If signal is not null and it is aborted, then reject result with 310 // signal’s abort reason and return result. 311 if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*signalValue, global, 312 *promise)) { 313 return promise.forget(); 314 } 315 316 // 5. Set state’s abort source to signal. 317 newState->SetAbortSource(signalValue); 318 } 319 320 if (taskPriority.WasPassed()) { 321 // 6. If options["priority"] exists, then set state’s priority source to the 322 // result of creating a fixed priority unabortable task signal given 323 // options["priority"] 324 newState->SetPrioritySource( 325 TaskSignal::Create(GetParentObject(), taskPriority.Value())); 326 } else if (signalValue && signalValue->IsTaskSignal()) { 327 // 7. Otherwise if signal is not null and implements the TaskSignal 328 // interface, then set state’s priority source to signal. 329 newState->SetPrioritySource( 330 do_AddRef(static_cast<TaskSignal*>(signalValue))); 331 } 332 333 if (!newState->GetPrioritySource()) { 334 // 8. If state’s priority source is null, then set state’s priority 335 // source to the result of creating a fixed priority unabortable task 336 // signal given "user-visible". 337 newState->SetPrioritySource( 338 TaskSignal::Create(GetParentObject(), TaskPriority::User_visible)); 339 } 340 341 MOZ_ASSERT(newState->GetPrioritySource()); 342 343 // 9. Let handle be the result of creating a task handle given result and 344 // signal. 345 // 10. If signal is not null, then add handle’s abort steps to signal. 346 // 11. Let enqueueSteps be the following steps... 347 RefPtr<WebTask> task = CreateTask(signalValue, newState->GetPrioritySource(), 348 taskPriority, false /* aIsContinuation */, 349 SomeRef(aCallback), newState, promise); 350 351 const TaskSignal* finalPrioritySource = newState->GetPrioritySource(); 352 // 12. Let delay be options["delay"]. 353 const uint64_t delay = aOptions.mDelay; 354 355 // 13. If delay is greater than 0, then run steps after a timeout given 356 // scheduler’s relevant global object, "scheduler-postTask", delay, and the 357 // following steps... 358 if (delay > 0) { 359 nsresult rv = SetTimeoutForDelayedTask( 360 task, delay, 361 GetEventQueuePriority(finalPrioritySource->Priority(), 362 false /* aIsContinuation */)); 363 if (NS_FAILED(rv)) { 364 promise->MaybeRejectWithUnknownError( 365 "Failed to setup timeout for delayed task"); 366 } 367 return promise.forget(); 368 } 369 370 // 14. Otherwise, run enqueueSteps. 371 if (!DispatchTask(task, GetEventQueuePriority(finalPrioritySource->Priority(), 372 false /* aIsContinuation */))) { 373 MOZ_ASSERT(task->isInList()); 374 task->remove(); 375 376 promise->MaybeRejectWithNotSupportedError("Unable to queue the task"); 377 return promise.forget(); 378 } 379 380 return promise.forget(); 381 } 382 383 // https://wicg.github.io/scheduling-apis/#schedule-a-yield-continuation 384 already_AddRefed<Promise> WebTaskScheduler::YieldImpl() { 385 ErrorResult rv; 386 // 1. Let result be a new promise. 387 RefPtr<Promise> promise = Promise::Create(mParent, rv); 388 if (rv.Failed()) { 389 return nullptr; 390 } 391 392 nsIGlobalObject* global = GetParentObject(); 393 if (!global || global->IsDying()) { 394 promise->MaybeRejectWithNotSupportedError("Current window is detached"); 395 return promise.forget(); 396 } 397 398 RefPtr<AbortSignal> abortSource; 399 RefPtr<TaskSignal> prioritySource; 400 // 2. Let inheritedState be the scheduler’s relevant agent's event loop's 401 // current scheduling state. 402 if (auto* schedulingState = global->GetWebTaskSchedulingState()) { 403 // 3. Let abortSource be inheritedState’s abort source if inheritedState is 404 // not null, or otherwise null. 405 abortSource = schedulingState->GetAbortSource(); 406 // 5. Let prioritySource be inheritedState’s priority source if 407 // inheritedState is not null, or otherwise null. 408 prioritySource = schedulingState->GetPrioritySource(); 409 } 410 411 if (abortSource) { 412 // 4. If abortSource is not null and abortSource is aborted, then reject 413 // result with abortSource’s abort reason and return result. 414 if (ShouldRejectPromiseWithReasonCausedByAbortSignal(*abortSource, global, 415 *promise)) { 416 return promise.forget(); 417 } 418 } 419 420 if (!prioritySource) { 421 // 6. If prioritySource is null, then set prioritySource to the result of 422 // creating a fixed priority unabortable task signal given "user-visible". 423 prioritySource = 424 TaskSignal::Create(GetParentObject(), TaskPriority::User_visible); 425 } 426 427 // 7. Let handle be the result of creating a task handle given result and 428 // abortSource. 429 // 8. If abortSource is not null, then add handle’s abort steps to 430 // abortSource. 431 // 9. Set handle’s queue to the result of selecting the scheduler task queue 432 // for scheduler given prioritySource and true. 433 // 10. Schedule a task to invoke an algorithm for scheduler given handle and 434 // the following steps: 435 RefPtr<WebTask> task = 436 CreateTask(abortSource, prioritySource, {}, true /* aIsContinuation */, 437 Nothing(), nullptr, promise); 438 439 EventQueuePriority eventQueuePriority = GetEventQueuePriority( 440 prioritySource->Priority(), true /* aIsContinuation */); 441 if (!DispatchTask(task, eventQueuePriority)) { 442 MOZ_ASSERT(task->isInList()); 443 // CreateTask adds the task to WebTaskScheduler's queue, so we 444 // need to remove from it when we failed to dispatch the runnable. 445 task->remove(); 446 447 promise->MaybeRejectWithNotSupportedError("Unable to queue the task"); 448 return promise.forget(); 449 } 450 451 return promise.forget(); 452 } 453 454 already_AddRefed<WebTask> WebTaskScheduler::CreateTask( 455 AbortSignal* aAbortSignal, TaskSignal* aTaskSignal, 456 const Optional<TaskPriority>& aPriority, bool aIsContinuation, 457 const Maybe<SchedulerPostTaskCallback&>& aCallback, 458 WebTaskSchedulingState* aSchedulingState, Promise* aPromise) { 459 WebTaskScheduler::SelectedTaskQueueData selectedTaskQueueData = 460 SelectTaskQueue(aTaskSignal, aPriority, aIsContinuation); 461 462 gWebTaskEnqueueOrder += 1; 463 RefPtr<WebTask> task = 464 new WebTask(gWebTaskEnqueueOrder, aCallback, aSchedulingState, aPromise, 465 this, selectedTaskQueueData.mSelectedQueueHashKey); 466 467 selectedTaskQueueData.mSelectedTaskQueue.AddTask(task); 468 469 if (aAbortSignal) { 470 task->Follow(aAbortSignal); 471 } 472 473 return task.forget(); 474 } 475 476 bool WebTaskScheduler::DispatchTask(WebTask* aTask, 477 EventQueuePriority aPriority) { 478 if (!DispatchEventLoopRunnable(aPriority)) { 479 return false; 480 } 481 MOZ_ASSERT(!aTask->HasScheduled()); 482 483 auto taskQueue = mWebTaskQueues.Lookup(aTask->TaskQueueHashKey()); 484 MOZ_DIAGNOSTIC_ASSERT(taskQueue); 485 486 if (IsNormalOrHighPriority(aTask->Priority()) && 487 !taskQueue->HasScheduledTasks()) { 488 // This is the first task that is scheduled for this queue. 489 IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled(); 490 } 491 492 aTask->SetHasScheduled(); 493 return true; 494 } 495 496 // https://wicg.github.io/scheduling-apis/#select-the-next-scheduler-task-queue-from-all-schedulers 497 WebTask* WebTaskScheduler::GetNextTask(bool aIsMainThread) { 498 // 1. Let queues be an empty set. 499 AutoTArray<nsTArray<WebTaskQueue*>, WebTaskQueue::EffectivePriorityCount> 500 allQueues; 501 allQueues.SetLength(WebTaskQueue::EffectivePriorityCount); 502 503 auto processScheduler = [&](WebTaskScheduler& aScheduler) { 504 for (auto iter = aScheduler.GetWebTaskQueues().Iter(); !iter.Done(); 505 iter.Next()) { 506 auto& queue = iter.Data(); 507 if (queue.HasScheduledTasks()) { 508 const WebTaskQueueHashKey& key = iter.Key(); 509 nsTArray<WebTaskQueue*>& queuesForThisPriority = 510 allQueues[key.EffectivePriority()]; 511 queuesForThisPriority.AppendElement(&queue); 512 } 513 } 514 }; 515 // 3. For each scheduler in schedulers, extend queues with the result of 516 // getting the runnable task queues for scheduler. 517 if (aIsMainThread) { 518 // 2. Let schedulers be the set of all Scheduler objects whose relevant 519 // agent’s event loop is event loop and that have a runnable task. 520 for (const auto& scheduler : gWebTaskSchedulersMainThread) { 521 processScheduler(*scheduler); 522 } 523 } else { 524 // Workers don't share the same event loop. 525 processScheduler(*this); 526 } 527 528 if (allQueues.IsEmpty()) { 529 return nullptr; 530 } 531 532 // Reverse checking the queues, so it starts with the highest priority 533 for (auto& queues : Reversed(allQueues)) { 534 if (queues.IsEmpty()) { 535 continue; 536 } 537 WebTaskQueue* oldestQueue = nullptr; 538 for (auto& webTaskQueue : queues) { 539 MOZ_ASSERT(webTaskQueue->HasScheduledTasks()); 540 if (!oldestQueue) { 541 oldestQueue = webTaskQueue; 542 } else { 543 WebTask* firstScheduledRunnableForCurrentQueue = 544 webTaskQueue->GetFirstScheduledTask(); 545 WebTask* firstScheduledRunnableForOldQueue = 546 oldestQueue->GetFirstScheduledTask(); 547 if (firstScheduledRunnableForOldQueue->EnqueueOrder() > 548 firstScheduledRunnableForCurrentQueue->EnqueueOrder()) { 549 oldestQueue = webTaskQueue; 550 } 551 } 552 } 553 MOZ_ASSERT(oldestQueue); 554 return oldestQueue->GetFirstScheduledTask(); 555 } 556 return nullptr; 557 } 558 559 void WebTaskScheduler::Disconnect() { 560 if (isInList()) { 561 remove(); 562 } 563 mWebTaskQueues.Clear(); 564 } 565 566 void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) { 567 // aIsContinuation is always false because continued tasks, 568 // a.k.a yield(), can't change its priority. 569 WebTaskQueueHashKey key(aTaskSignal, false /* aIsContinuation */); 570 if (auto entry = mWebTaskQueues.Lookup(key)) { 571 if (IsNormalOrHighPriority(entry.Data().Priority()) != 572 IsNormalOrHighPriority(key.Priority())) { 573 // The counter needs to be adjusted if it has scheduled tasks 574 // because this queue changes its priority. 575 if (entry.Data().HasScheduledTasks()) { 576 if (IsNormalOrHighPriority(key.Priority())) { 577 // Promoted from lower priority to high priority. 578 IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled(); 579 } else { 580 // Demoted from high priority to low priority. 581 DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled(); 582 } 583 } 584 } 585 entry.Data().SetPriority(aTaskSignal->Priority()); 586 } 587 } 588 589 WebTaskScheduler::SelectedTaskQueueData WebTaskScheduler::SelectTaskQueue( 590 TaskSignal* aTaskSignal, const Optional<TaskPriority>& aPriority, 591 const bool aIsContinuation) { 592 bool useSignal = !aPriority.WasPassed() && aTaskSignal; 593 594 if (useSignal) { 595 WebTaskQueueHashKey signalHashKey(aTaskSignal, aIsContinuation); 596 WebTaskQueue& taskQueue = 597 mWebTaskQueues.LookupOrInsert(signalHashKey, this); 598 599 taskQueue.SetPriority(aTaskSignal->Priority()); 600 aTaskSignal->SetWebTaskScheduler(this); 601 602 return SelectedTaskQueueData{WebTaskQueueHashKey(signalHashKey), taskQueue}; 603 } 604 605 TaskPriority taskPriority = 606 aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible; 607 608 uint32_t staticTaskQueueMapKey = static_cast<uint32_t>(taskPriority); 609 WebTaskQueueHashKey staticHashKey(staticTaskQueueMapKey, aIsContinuation); 610 WebTaskQueue& taskQueue = mWebTaskQueues.LookupOrInsert(staticHashKey, this); 611 taskQueue.SetPriority(taskPriority); 612 613 return SelectedTaskQueueData{WebTaskQueueHashKey(staticHashKey), taskQueue}; 614 } 615 616 EventQueuePriority WebTaskScheduler::GetEventQueuePriority( 617 const TaskPriority& aPriority, bool aIsContinuation) const { 618 switch (aPriority) { 619 case TaskPriority::User_blocking: 620 return EventQueuePriority::MediumHigh; 621 case TaskPriority::User_visible: 622 return aIsContinuation ? EventQueuePriority::MediumHigh 623 : EventQueuePriority::Normal; 624 case TaskPriority::Background: 625 return EventQueuePriority::Low; 626 default: 627 MOZ_ASSERT_UNREACHABLE("Invalid TaskPriority"); 628 return EventQueuePriority::Normal; 629 } 630 } 631 632 void WebTaskScheduler::NotifyTaskWillBeRunOrAborted(const WebTask* aWebTask) { 633 const WebTaskQueueHashKey& hashKey = aWebTask->TaskQueueHashKey(); 634 MOZ_ASSERT(mWebTaskQueues.Contains(hashKey)); 635 if (auto entry = mWebTaskQueues.Lookup(hashKey)) { 636 const WebTaskQueue& taskQueue = *entry; 637 if (IsNormalOrHighPriority(taskQueue.Priority())) { 638 // If the taskQueue 639 // 1. is empty 640 // 2. or it's not empty but the existing tasks are 641 // not scheduled (delay tasks). 642 if (!taskQueue.HasScheduledTasks()) { 643 DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled(); 644 } 645 } 646 if (taskQueue.IsEmpty()) { 647 DeleteEntryFromWebTaskQueueMap(hashKey); 648 } 649 } 650 } 651 652 WebTaskQueue::~WebTaskQueue() { 653 MOZ_ASSERT(mScheduler); 654 655 bool hasScheduledTask = false; 656 for (const auto& task : mTasks) { 657 if (!hasScheduledTask && task->HasScheduled()) { 658 hasScheduledTask = true; 659 } 660 task->ClearWebTaskScheduler(); 661 } 662 mTasks.clear(); 663 664 if (hasScheduledTask && IsNormalOrHighPriority(Priority())) { 665 mScheduler->DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled(); 666 } 667 } 668 } // namespace mozilla::dom