/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=2: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_WebTaskScheduler_h #define mozilla_dom_WebTaskScheduler_h #include "nsThreadUtils.h" #include "nsPIDOMWindow.h" #include "nsWrapperCache.h" #include "nsClassHashtable.h" #include "mozilla/Variant.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/AbortFollower.h" #include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/WebTaskSchedulingBinding.h" namespace mozilla::dom { class WebTaskQueue; class WebTask : public LinkedListElement>, public AbortFollower, public SupportsWeakPtr { friend class WebTaskScheduler; public: MOZ_CAN_RUN_SCRIPT bool Run(); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(WebTask) WebTask(uint32_t aEnqueueOrder, SchedulerPostTaskCallback& aCallback, Promise* aPromise) : mEnqueueOrder(aEnqueueOrder), mCallback(&aCallback), mPromise(aPromise), mHasScheduled(false), mOwnerQueue(nullptr) {} void RunAbortAlgorithm() override; bool HasScheduled() const { return mHasScheduled; } uint32_t EnqueueOrder() const { return mEnqueueOrder; } void SetWebTaskQueue(WebTaskQueue* aWebTaskQueue) { mOwnerQueue = aWebTaskQueue; } private: void SetHasScheduled(bool aHasScheduled) { mHasScheduled = aHasScheduled; } uint32_t mEnqueueOrder; RefPtr mCallback; RefPtr mPromise; bool mHasScheduled; // WebTaskQueue owns WebTask, so it's okay to use a raw pointer WebTaskQueue* mOwnerQueue; ~WebTask() = default; }; class WebTaskQueue { public: WebTaskQueue(uint32_t aKey, WebTaskScheduler* aScheduler) : mOwnerKey(AsVariant(aKey)), mScheduler(aScheduler) {} WebTaskQueue(TaskSignal* aKey, WebTaskScheduler* aScheduler) : mOwnerKey(AsVariant(aKey)), mScheduler(aScheduler) {} TaskPriority Priority() const { return mPriority; } void SetPriority(TaskPriority aNewPriority) { mPriority = aNewPriority; } LinkedList>& Tasks() { return mTasks; } void AddTask(WebTask* aTask) { mTasks.insertBack(aTask); aTask->SetWebTaskQueue(this); } void RemoveEntryFromTaskQueueMapIfNeeded(); // TODO: To optimize it, we could have the scheduled and unscheduled // tasks stored separately. WebTask* GetFirstScheduledTask() { for (const auto& task : mTasks) { if (task->HasScheduled()) { return task; } } return nullptr; } ~WebTaskQueue() { mOwnerKey = AsVariant(Nothing()); for (const auto& task : mTasks) { task->SetWebTaskQueue(nullptr); } mTasks.clear(); } private: TaskPriority mPriority = TaskPriority::User_visible; LinkedList> mTasks; // When mOwnerKey is TaskSignal*, it means as long as // WebTaskQueue is alive, the corresponding TaskSignal // is alive, so using a raw pointer is ok. Variant mOwnerKey; // WebTaskScheduler owns WebTaskQueue as a hashtable value, so using a raw // pointer points to WebTaskScheduler is ok. WebTaskScheduler* mScheduler; }; class WebTaskSchedulerMainThread; class WebTaskSchedulerWorker; class WebTaskScheduler : public nsWrapperCache, public SupportsWeakPtr { friend class DelayedWebTaskHandler; public: NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebTaskScheduler) NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(WebTaskScheduler) static already_AddRefed CreateForMainThread( nsGlobalWindowInner* aWindow); static already_AddRefed CreateForWorker( WorkerPrivate* aWorkerPrivate); explicit WebTaskScheduler(nsIGlobalObject* aParent); already_AddRefed PostTask(SchedulerPostTaskCallback& aCallback, const SchedulerPostTaskOptions& aOptions); nsIGlobalObject* GetParentObject() const { return mParent; } virtual JSObject* WrapObject(JSContext* cx, JS::Handle aGivenProto) override; WebTask* GetNextTask() const; virtual void Disconnect(); void RunTaskSignalPriorityChange(TaskSignal* aTaskSignal); void DeleteEntryFromStaticQueueMap(uint32_t aKey); void DeleteEntryFromDynamicQueueMap(TaskSignal* aKey); protected: virtual ~WebTaskScheduler() = default; nsCOMPtr mParent; uint32_t mNextEnqueueOrder; private: already_AddRefed CreateTask( WebTaskQueue& aQueue, const Optional>& aSignal, SchedulerPostTaskCallback& aCallback, Promise* aPromise); bool QueueTask(WebTask* aTask); WebTaskQueue& SelectTaskQueue( const Optional>& aSignal, const Optional& aPriority); virtual nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay) = 0; virtual bool DispatchEventLoopRunnable() = 0; nsClassHashtable mStaticPriorityTaskQueues; nsClassHashtable, WebTaskQueue> mDynamicPriorityTaskQueues; }; class DelayedWebTaskHandler final : public TimeoutHandler { public: DelayedWebTaskHandler(JSContext* aCx, WebTaskScheduler* aScheduler, WebTask* aTask) : TimeoutHandler(aCx), mScheduler(aScheduler), mWebTask(aTask) {} NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(DelayedWebTaskHandler) MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override { if (mScheduler && mWebTask) { MOZ_ASSERT(!mWebTask->HasScheduled()); if (!mScheduler->QueueTask(mWebTask)) { return false; } } return true; } private: ~DelayedWebTaskHandler() override = default; WeakPtr mScheduler; // WebTask gets added to WebTaskQueue, and WebTaskQueue keeps its alive. WeakPtr mWebTask; }; } // namespace mozilla::dom #endif