зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1734997 - Prototype the Prioritized Task Scheduling API r=smaug
Spec: https://wicg.github.io/scheduling-apis/ Differential Revision: https://phabricator.services.mozilla.com/D133494
This commit is contained in:
Родитель
15daab2123
Коммит
5042a856cf
|
@ -21,7 +21,7 @@ namespace dom {
|
|||
|
||||
class AbortSignal;
|
||||
|
||||
class AbortController final : public nsISupports, public nsWrapperCache {
|
||||
class AbortController : public nsISupports, public nsWrapperCache {
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortController)
|
||||
|
@ -40,8 +40,8 @@ class AbortController final : public nsISupports, public nsWrapperCache {
|
|||
|
||||
void Abort(JSContext* aCx, JS::Handle<JS::Value> aReason);
|
||||
|
||||
private:
|
||||
~AbortController();
|
||||
protected:
|
||||
virtual ~AbortController();
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
RefPtr<AbortSignal> mSignal;
|
||||
|
|
|
@ -25,9 +25,9 @@ namespace dom {
|
|||
// it appears only to be used internally in the Fetch API. It might be a good
|
||||
// idea to split AbortSignal into an implementation that can follow, and an
|
||||
// implementation that can't, to provide this complexity only when it's needed.
|
||||
class AbortSignal final : public DOMEventTargetHelper,
|
||||
public AbortSignalImpl,
|
||||
public AbortFollower {
|
||||
class AbortSignal : public DOMEventTargetHelper,
|
||||
public AbortSignalImpl,
|
||||
public AbortFollower {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(AbortSignal,
|
||||
|
@ -57,7 +57,9 @@ class AbortSignal final : public DOMEventTargetHelper,
|
|||
// AbortFollower
|
||||
void RunAbortAlgorithm() override;
|
||||
|
||||
private:
|
||||
virtual bool IsTaskSignal() const { return false; }
|
||||
|
||||
protected:
|
||||
~AbortSignal();
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ class Timeout final : protected LinkedListElement<RefPtr<Timeout>> {
|
|||
eTimeoutOrInterval,
|
||||
eIdleCallbackTimeout,
|
||||
eAbortSignalTimeout,
|
||||
eDelayedWebTaskTimeout,
|
||||
};
|
||||
|
||||
struct TimeoutIdAndReason {
|
||||
|
|
|
@ -451,6 +451,7 @@ uint32_t TimeoutManager::GetTimeoutId(Timeout::Reason aReason) {
|
|||
return ++mIdleCallbackTimeoutCounter;
|
||||
case Timeout::Reason::eTimeoutOrInterval:
|
||||
return ++mTimeoutIdCounter;
|
||||
case Timeout::Reason::eDelayedWebTaskTimeout:
|
||||
default:
|
||||
return std::numeric_limits<uint32_t>::max(); // no cancellation support
|
||||
}
|
||||
|
|
|
@ -153,6 +153,7 @@
|
|||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/ProxyHandlerUtils.h"
|
||||
#include "mozilla/dom/RootedDictionary.h"
|
||||
#include "mozilla/dom/WebTaskSchedulerMainThread.h"
|
||||
#include "mozilla/dom/ScriptLoader.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/ServiceWorker.h"
|
||||
|
@ -1291,6 +1292,11 @@ void nsGlobalWindowInner::FreeInnerObjects() {
|
|||
|
||||
mContentMediaController = nullptr;
|
||||
|
||||
if (mWebTaskScheduler) {
|
||||
mWebTaskScheduler->Disconnect();
|
||||
mWebTaskScheduler = nullptr;
|
||||
}
|
||||
|
||||
mSharedWorkers.Clear();
|
||||
|
||||
#ifdef MOZ_WEBSPEECH
|
||||
|
@ -1386,6 +1392,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler)
|
||||
|
||||
#ifdef MOZ_WEBSPEECH
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
|
||||
#endif
|
||||
|
@ -1484,6 +1492,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
|
|||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
|
||||
|
||||
if (tmp->mWebTaskScheduler) {
|
||||
tmp->mWebTaskScheduler->Disconnect();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler)
|
||||
}
|
||||
|
||||
#ifdef MOZ_WEBSPEECH
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
|
||||
#endif
|
||||
|
@ -1752,6 +1765,10 @@ void nsGlobalWindowInner::InitDocumentDependentState(JSContext* aCx) {
|
|||
mLocalStorage = nullptr;
|
||||
mSessionStorage = nullptr;
|
||||
mPerformance = nullptr;
|
||||
if (mWebTaskScheduler) {
|
||||
mWebTaskScheduler->Disconnect();
|
||||
mWebTaskScheduler = nullptr;
|
||||
}
|
||||
|
||||
// This must be called after nullifying the internal objects because here we
|
||||
// could recreate them, calling the getter methods, and store them into the JS
|
||||
|
@ -4226,6 +4243,14 @@ Selection* nsGlobalWindowInner::GetSelection(ErrorResult& aError) {
|
|||
FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr);
|
||||
}
|
||||
|
||||
WebTaskScheduler* nsGlobalWindowInner::Scheduler() {
|
||||
if (!mWebTaskScheduler) {
|
||||
mWebTaskScheduler = WebTaskScheduler::CreateForMainThread(this);
|
||||
}
|
||||
MOZ_ASSERT(mWebTaskScheduler);
|
||||
return mWebTaskScheduler;
|
||||
}
|
||||
|
||||
bool nsGlobalWindowInner::Find(const nsAString& aString, bool aCaseSensitive,
|
||||
bool aBackwards, bool aWrapAround,
|
||||
bool aWholeWord, bool aSearchInFrames,
|
||||
|
@ -6332,6 +6357,11 @@ static const char* GetTimeoutReasonString(Timeout* aTimeout) {
|
|||
return "setIdleCallback handler (timed out)";
|
||||
case Timeout::Reason::eAbortSignalTimeout:
|
||||
return "AbortSignal timeout";
|
||||
case Timeout::Reason::eDelayedWebTaskTimeout:
|
||||
return "delayedWebTaskCallback handler (timed out)";
|
||||
default:
|
||||
MOZ_CRASH("Unexpected enum value");
|
||||
return "";
|
||||
}
|
||||
MOZ_CRASH("Unexpected enum value");
|
||||
return "";
|
||||
|
|
|
@ -130,6 +130,8 @@ struct RequestInit;
|
|||
class RequestOrUSVString;
|
||||
class SharedWorker;
|
||||
class Selection;
|
||||
class WebTaskScheduler;
|
||||
class WebTaskSchedulerMainThread;
|
||||
class SpeechSynthesis;
|
||||
class Timeout;
|
||||
class U2F;
|
||||
|
@ -988,6 +990,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
|||
// https://whatpr.org/html/4734/structured-data.html#cross-origin-isolated
|
||||
bool CrossOriginIsolated() const override;
|
||||
|
||||
mozilla::dom::WebTaskScheduler* Scheduler();
|
||||
|
||||
protected:
|
||||
// Web IDL helpers
|
||||
|
||||
|
@ -1346,6 +1350,8 @@ class nsGlobalWindowInner final : public mozilla::dom::EventTarget,
|
|||
private:
|
||||
RefPtr<mozilla::dom::ContentMediaController> mContentMediaController;
|
||||
|
||||
RefPtr<mozilla::dom::WebTaskSchedulerMainThread> mWebTaskScheduler;
|
||||
|
||||
protected:
|
||||
// Whether we need to care about orientation changes.
|
||||
bool mHasOrientationChangeListeners : 1;
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
DOMInterfaces = {
|
||||
|
||||
'AbortSignal': {
|
||||
'implicitJSContext': [ 'throwIfAborted' ]
|
||||
'implicitJSContext': [ 'throwIfAborted' ],
|
||||
'concrete': True,
|
||||
},
|
||||
|
||||
'AnonymousContent': {
|
||||
|
@ -641,6 +642,11 @@ DOMInterfaces = {
|
|||
'concrete': True,
|
||||
},
|
||||
|
||||
'TaskController' : {
|
||||
'nativeType' : 'mozilla::dom::WebTaskController',
|
||||
'headerFile' : 'mozilla/dom/WebTaskController.h'
|
||||
},
|
||||
|
||||
'TransceiverImpl': {
|
||||
'nativeType': 'mozilla::TransceiverImpl',
|
||||
'headerFile': 'TransceiverImpl.h'
|
||||
|
@ -747,6 +753,11 @@ DOMInterfaces = {
|
|||
'headerFile': 'RTCStatsReport.h'
|
||||
},
|
||||
|
||||
'Scheduler': {
|
||||
'nativeType': 'mozilla::dom::WebTaskScheduler',
|
||||
'headerFile': 'mozilla/dom/WebTaskScheduler.h',
|
||||
},
|
||||
|
||||
'Screen': {
|
||||
'nativeType': 'nsScreen',
|
||||
},
|
||||
|
|
|
@ -111,6 +111,7 @@ DIRS += [
|
|||
"prio",
|
||||
"l10n",
|
||||
"origin-trials",
|
||||
"webscheduling",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
[Exposed=(Window, Worker), Pref="dom.enable_web_task_scheduling"]
|
||||
interface TaskPriorityChangeEvent : Event {
|
||||
constructor (DOMString type , TaskPriorityChangeEventInit priorityChangeEventInitDict);
|
||||
readonly attribute TaskPriority previousPriority;
|
||||
};
|
||||
|
||||
dictionary TaskPriorityChangeEventInit : EventInit {
|
||||
required TaskPriority previousPriority;
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
enum TaskPriority {
|
||||
"user-blocking",
|
||||
"user-visible",
|
||||
"background"
|
||||
};
|
||||
|
||||
[Exposed=(Window, Worker), Pref="dom.enable_web_task_scheduling"]
|
||||
interface TaskSignal : AbortSignal {
|
||||
readonly attribute TaskPriority priority;
|
||||
|
||||
attribute EventHandler onprioritychange;
|
||||
};
|
||||
|
||||
|
||||
dictionary SchedulerPostTaskOptions {
|
||||
AbortSignal signal;
|
||||
TaskPriority priority;
|
||||
[EnforceRange] unsigned long long delay = 0;
|
||||
};
|
||||
|
||||
callback SchedulerPostTaskCallback = any ();
|
||||
|
||||
[Exposed=(Window, Worker), Pref="dom.enable_web_task_scheduling"]
|
||||
interface Scheduler {
|
||||
Promise<any> postTask(
|
||||
SchedulerPostTaskCallback callback,
|
||||
optional SchedulerPostTaskOptions options = {}
|
||||
);
|
||||
};
|
||||
|
||||
dictionary TaskControllerInit {
|
||||
TaskPriority priority = "user-visible";
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker), Pref="dom.enable_web_task_scheduling"]
|
||||
interface TaskController : AbortController {
|
||||
[Throws]
|
||||
constructor(optional TaskControllerInit init = {});
|
||||
|
||||
[Throws]
|
||||
void setPriority(TaskPriority priority);
|
||||
};
|
|
@ -79,3 +79,9 @@ partial interface mixin WindowOrWorkerGlobalScope {
|
|||
[Throws, Pref="dom.caches.enabled", SameObject]
|
||||
readonly attribute CacheStorage caches;
|
||||
};
|
||||
|
||||
// https://wicg.github.io/scheduling-apis/#ref-for-windoworworkerglobalscope-scheduler
|
||||
partial interface mixin WindowOrWorkerGlobalScope {
|
||||
[Pref="dom.enable_web_task_scheduling", SameObject]
|
||||
readonly attribute Scheduler scheduler;
|
||||
};
|
||||
|
|
|
@ -985,6 +985,7 @@ WEBIDL_FILES = [
|
|||
"WebGLRenderingContext.webidl",
|
||||
"WebGPU.webidl",
|
||||
"WebSocket.webidl",
|
||||
"WebTaskScheduling.webidl",
|
||||
"WebXR.webidl",
|
||||
"WheelEvent.webidl",
|
||||
"WidevineCDMManifest.webidl",
|
||||
|
@ -1158,6 +1159,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
|
|||
"SecurityPolicyViolationEvent.webidl",
|
||||
"StyleSheetApplicableStateChangeEvent.webidl",
|
||||
"SubmitEvent.webidl",
|
||||
"TaskPriorityChangeEvent.webidl",
|
||||
"TCPServerSocketEvent.webidl",
|
||||
"TCPSocketErrorEvent.webidl",
|
||||
"TCPSocketEvent.webidl",
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- 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_TaskSignal_h
|
||||
#define mozilla_dom_TaskSignal_h
|
||||
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/dom/AbortSignal.h"
|
||||
#include "mozilla/dom/WebTaskSchedulingBinding.h"
|
||||
#include "WebTaskScheduler.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
class TaskSignal : public AbortSignal {
|
||||
public:
|
||||
TaskSignal(nsIGlobalObject* aGlobal, TaskPriority aPriority)
|
||||
: AbortSignal(aGlobal, false, JS::UndefinedHandleValue),
|
||||
mPriority(aPriority),
|
||||
mPriorityChanging(false) {}
|
||||
|
||||
IMPL_EVENT_HANDLER(prioritychange);
|
||||
|
||||
TaskPriority Priority() const { return mPriority; }
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override {
|
||||
return TaskSignal_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void SetPriority(TaskPriority aPriority) { mPriority = aPriority; }
|
||||
|
||||
bool IsTaskSignal() const override { return true; }
|
||||
|
||||
bool PriorityChanging() const { return mPriorityChanging; }
|
||||
|
||||
void SetPriorityChanging(bool aPriorityChanging) {
|
||||
mPriorityChanging = aPriorityChanging;
|
||||
}
|
||||
|
||||
void SetWebTaskScheduler(WebTaskScheduler* aScheduler) {
|
||||
mSchedulers.AppendElement(aScheduler);
|
||||
}
|
||||
|
||||
void RunPriorityChangeAlgorithms() {
|
||||
for (const WeakPtr<WebTaskScheduler>& scheduler : mSchedulers) {
|
||||
scheduler->RunTaskSignalPriorityChange(this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
TaskPriority mPriority;
|
||||
|
||||
bool mPriorityChanging;
|
||||
|
||||
nsTArray<WeakPtr<WebTaskScheduler>> mSchedulers;
|
||||
|
||||
~TaskSignal() = default;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
#endif
|
|
@ -0,0 +1,70 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#include "WebTaskController.h"
|
||||
#include "TaskSignal.h"
|
||||
|
||||
#include "mozilla/dom/TaskPriorityChangeEvent.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
WebTaskController::WebTaskController(nsIGlobalObject* aGlobal,
|
||||
TaskPriority aPriority)
|
||||
: AbortController(aGlobal) {
|
||||
MOZ_ASSERT(!mSignal);
|
||||
mSignal = new TaskSignal(aGlobal, aPriority);
|
||||
}
|
||||
|
||||
void WebTaskController::SetPriority(TaskPriority aPriority, ErrorResult& aRv) {
|
||||
// https://wicg.github.io/scheduling-apis/#tasksignal-signal-priority-change
|
||||
RefPtr<TaskSignal> taskSignal = static_cast<TaskSignal*>(mSignal.get());
|
||||
MOZ_ASSERT(taskSignal);
|
||||
if (taskSignal->PriorityChanging()) {
|
||||
aRv.ThrowNotAllowedError("Signal's priority changing is true");
|
||||
return;
|
||||
}
|
||||
|
||||
if (taskSignal->Priority() == aPriority) {
|
||||
return;
|
||||
}
|
||||
|
||||
taskSignal->SetPriorityChanging(true);
|
||||
|
||||
TaskPriority previousPriority = taskSignal->Priority();
|
||||
taskSignal->SetPriority(aPriority);
|
||||
|
||||
taskSignal->RunPriorityChangeAlgorithms();
|
||||
|
||||
TaskPriorityChangeEventInit init;
|
||||
init.mPreviousPriority = previousPriority;
|
||||
RefPtr<TaskPriorityChangeEvent> event = TaskPriorityChangeEvent::Constructor(
|
||||
taskSignal, u"prioritychange"_ns, init);
|
||||
|
||||
event->SetTrusted(true);
|
||||
|
||||
taskSignal->DispatchEvent(*event);
|
||||
taskSignal->SetPriorityChanging(false);
|
||||
}
|
||||
|
||||
already_AddRefed<WebTaskController> WebTaskController::Constructor(
|
||||
const GlobalObject& aGlobal, const TaskControllerInit& aInit,
|
||||
ErrorResult& aRv) {
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
RefPtr<WebTaskController> webTaskController =
|
||||
new WebTaskController(global, aInit.mPriority);
|
||||
return webTaskController.forget();
|
||||
}
|
||||
|
||||
JSObject* WebTaskController::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return TaskController_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,35 @@
|
|||
/* -*- 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_WebTaskController_h
|
||||
#define mozilla_dom_WebTaskController_h
|
||||
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
#include "mozilla/dom/WebTaskSchedulingBinding.h"
|
||||
#include "mozilla/dom/AbortController.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
class WebTaskController : public AbortController {
|
||||
public:
|
||||
explicit WebTaskController(nsIGlobalObject* aGlobal, TaskPriority aPriority);
|
||||
|
||||
static already_AddRefed<WebTaskController> Constructor(
|
||||
const GlobalObject& aGlobal, const TaskControllerInit& aInit,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
void SetPriority(TaskPriority aPriority, ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
~WebTaskController() = default;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif
|
|
@ -0,0 +1,350 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsTHashMap.h"
|
||||
#include "WebTaskScheduler.h"
|
||||
#include "WebTaskSchedulerWorker.h"
|
||||
#include "WebTaskSchedulerMainThread.h"
|
||||
#include "TaskSignal.h"
|
||||
#include "nsGlobalWindowInner.h"
|
||||
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/dom/TimeoutManager.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
inline void ImplCycleCollectionTraverse(
|
||||
nsCycleCollectionTraversalCallback& aCallback, WebTaskQueue& aQueue,
|
||||
const char* aName, uint32_t aFlags = 0) {
|
||||
ImplCycleCollectionTraverse(aCallback, aQueue.Tasks(), aName, aFlags);
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(WebTask)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTask)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTask)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTask)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTask)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTask)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(DelayedWebTaskHandler)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayedWebTaskHandler)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(DelayedWebTaskHandler)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(DelayedWebTaskHandler)
|
||||
|
||||
void WebTask::RunAbortAlgorithm() {
|
||||
// no-op if WebTask::Run has been called already
|
||||
if (mPromise->State() == Promise::PromiseState::Pending) {
|
||||
// There are two things that can keep a WebTask alive, either the abort
|
||||
// signal or WebTaskQueue.
|
||||
// It's possible that this task get cleared out from the WebTaskQueue first,
|
||||
// and then the abort signal get aborted. For example, the callback function
|
||||
// was async and there's a signal.abort() call in the callback.
|
||||
if (isInList()) {
|
||||
remove();
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(mPromise->GetGlobalObject())) {
|
||||
mPromise->MaybeReject(NS_ERROR_UNEXPECTED);
|
||||
} else {
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::RootedValue reason(cx);
|
||||
Signal()->GetReason(cx, &reason);
|
||||
mPromise->MaybeReject(reason);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!isInList());
|
||||
}
|
||||
|
||||
bool WebTask::Run() {
|
||||
MOZ_ASSERT(HasScheduled());
|
||||
remove();
|
||||
|
||||
ErrorResult error;
|
||||
|
||||
nsIGlobalObject* global = mPromise->GetGlobalObject();
|
||||
if (!global || global->IsDying()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(global)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> returnVal(jsapi.cx());
|
||||
|
||||
MOZ_ASSERT(mPromise->State() == Promise::PromiseState::Pending);
|
||||
|
||||
MOZ_KnownLive(mCallback)->Call(&returnVal, error, "WebTask",
|
||||
CallbackFunction::eRethrowExceptions);
|
||||
|
||||
error.WouldReportJSException();
|
||||
|
||||
Promise::PromiseState promiseState = mPromise->State();
|
||||
|
||||
// If the state is Rejected, it means the above Call triggers the
|
||||
// RunAbortAlgorithm method and rejected the promise
|
||||
MOZ_ASSERT_IF(promiseState != Promise::PromiseState::Pending,
|
||||
promiseState == Promise::PromiseState::Rejected);
|
||||
|
||||
if (promiseState == Promise::PromiseState::Pending) {
|
||||
if (error.Failed()) {
|
||||
mPromise->MaybeReject(std::move(error));
|
||||
} else {
|
||||
mPromise->MaybeResolve(returnVal);
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!isInList());
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTaskScheduler, mParent,
|
||||
mStaticPriorityTaskQueues,
|
||||
mDynamicPriorityTaskQueues)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebTaskScheduler, AddRef)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebTaskScheduler, Release)
|
||||
|
||||
/* static */
|
||||
already_AddRefed<WebTaskSchedulerMainThread>
|
||||
WebTaskScheduler::CreateForMainThread(nsGlobalWindowInner* aWindow) {
|
||||
RefPtr<WebTaskSchedulerMainThread> scheduler =
|
||||
new WebTaskSchedulerMainThread(aWindow->AsGlobal());
|
||||
return scheduler.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<WebTaskSchedulerWorker> WebTaskScheduler::CreateForWorker(
|
||||
WorkerPrivate* aWorkerPrivate) {
|
||||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||||
RefPtr<WebTaskSchedulerWorker> scheduler =
|
||||
new WebTaskSchedulerWorker(aWorkerPrivate);
|
||||
return scheduler.forget();
|
||||
}
|
||||
|
||||
WebTaskScheduler::WebTaskScheduler(nsIGlobalObject* aParent)
|
||||
: mParent(aParent), mNextEnqueueOrder(1) {
|
||||
MOZ_ASSERT(aParent);
|
||||
}
|
||||
|
||||
JSObject* WebTaskScheduler::WrapObject(JSContext* cx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return Scheduler_Binding::Wrap(cx, this, aGivenProto);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> WebTaskScheduler::PostTask(
|
||||
SchedulerPostTaskCallback& aCallback,
|
||||
const SchedulerPostTaskOptions& aOptions) {
|
||||
const Optional<OwningNonNull<AbortSignal>>& taskSignal = aOptions.mSignal;
|
||||
const Optional<TaskPriority>& taskPriority = aOptions.mPriority;
|
||||
|
||||
ErrorResult rv;
|
||||
// Instead of making WebTaskScheduler::PostTask throws, we always
|
||||
// create the promise and return it. This is because we need to
|
||||
// create the promise explicitly to be able to reject it with
|
||||
// signal's reason.
|
||||
RefPtr<Promise> promise = Promise::Create(mParent, rv);
|
||||
if (rv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIGlobalObject* global = GetParentObject();
|
||||
if (!global || global->IsDying()) {
|
||||
promise->MaybeRejectWithNotSupportedError("Current window is detached");
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
if (taskSignal.WasPassed()) {
|
||||
AbortSignal& signalValue = taskSignal.Value();
|
||||
|
||||
if (signalValue.Aborted()) {
|
||||
AutoJSAPI jsapi;
|
||||
if (!jsapi.Init(global)) {
|
||||
promise->MaybeReject(NS_ERROR_UNEXPECTED);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::RootedValue reason(cx);
|
||||
signalValue.GetReason(cx, &reason);
|
||||
promise->MaybeReject(reason);
|
||||
return promise.forget();
|
||||
}
|
||||
}
|
||||
|
||||
// Let queue be the result of selecting the scheduler task queue for scheduler
|
||||
// given signal and priority.
|
||||
WebTaskQueue& taskQueue = SelectTaskQueue(taskSignal, taskPriority);
|
||||
|
||||
uint64_t delay = aOptions.mDelay;
|
||||
|
||||
RefPtr<WebTask> task = CreateTask(taskQueue, taskSignal, aCallback, promise);
|
||||
if (delay > 0) {
|
||||
nsresult rv = SetTimeoutForDelayedTask(task, delay);
|
||||
if (NS_FAILED(rv)) {
|
||||
promise->MaybeRejectWithUnknownError(
|
||||
"Failed to setup timeout for delayed task");
|
||||
}
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
if (!QueueTask(task)) {
|
||||
promise->MaybeRejectWithNotSupportedError("Unable to queue the task");
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<WebTask> WebTaskScheduler::CreateTask(
|
||||
WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
|
||||
SchedulerPostTaskCallback& aCallback, Promise* aPromise) {
|
||||
uint32_t nextEnqueueOrder = mNextEnqueueOrder;
|
||||
++mNextEnqueueOrder;
|
||||
|
||||
RefPtr<WebTask> task = new WebTask(nextEnqueueOrder, aCallback, aPromise);
|
||||
|
||||
aQueue.AddTask(task);
|
||||
|
||||
if (aSignal.WasPassed()) {
|
||||
AbortSignal& signalValue = aSignal.Value();
|
||||
task->Follow(&signalValue);
|
||||
}
|
||||
|
||||
return task.forget();
|
||||
}
|
||||
|
||||
bool WebTaskScheduler::QueueTask(WebTask* aTask) {
|
||||
if (!DispatchEventLoopRunnable()) {
|
||||
return false;
|
||||
}
|
||||
MOZ_ASSERT(!aTask->HasScheduled());
|
||||
aTask->SetHasScheduled(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
WebTask* WebTaskScheduler::GetNextTask() const {
|
||||
// We first combine queues from both mStaticPriorityTaskQueues and
|
||||
// mDynamicPriorityTaskQueues into a single hash map which the
|
||||
// keys are the priorities and the values are all the queues that
|
||||
// belong to this priority.
|
||||
//
|
||||
// Then From the queues which
|
||||
// 1. Have scheduled tasks
|
||||
// 2. Its priority is not less than any other queues' priority
|
||||
// We pick the task which has the smallest enqueue order.
|
||||
nsTHashMap<nsUint32HashKey, nsTArray<WebTaskQueue*>> allQueues;
|
||||
|
||||
for (auto iter = mStaticPriorityTaskQueues.ConstIter(); !iter.Done();
|
||||
iter.Next()) {
|
||||
const auto& queue = iter.Data();
|
||||
if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
|
||||
nsTArray<WebTaskQueue*>& queuesForThisPriority =
|
||||
allQueues.LookupOrInsert(static_cast<uint32_t>(iter.Key()));
|
||||
queuesForThisPriority.AppendElement(queue.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = mDynamicPriorityTaskQueues.ConstIter(); !iter.Done();
|
||||
iter.Next()) {
|
||||
const auto& queue = iter.Data();
|
||||
if (!queue->Tasks().isEmpty() && queue->GetFirstScheduledTask()) {
|
||||
nsTArray<WebTaskQueue*>& queuesForThisPriority = allQueues.LookupOrInsert(
|
||||
static_cast<uint32_t>(iter.Key()->Priority()));
|
||||
queuesForThisPriority.AppendElement(queue.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (allQueues.IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (uint32_t priority = static_cast<uint32_t>(TaskPriority::User_blocking);
|
||||
priority < static_cast<uint32_t>(TaskPriority::EndGuard_); ++priority) {
|
||||
if (auto queues = allQueues.Lookup(priority)) {
|
||||
WebTaskQueue* oldestQueue = nullptr;
|
||||
MOZ_ASSERT(!queues.Data().IsEmpty());
|
||||
for (auto& webTaskQueue : queues.Data()) {
|
||||
MOZ_ASSERT(webTaskQueue->GetFirstScheduledTask());
|
||||
if (!oldestQueue) {
|
||||
oldestQueue = webTaskQueue;
|
||||
} else {
|
||||
WebTask* firstScheduledRunnableForCurrentQueue =
|
||||
webTaskQueue->GetFirstScheduledTask();
|
||||
WebTask* firstScheduledRunnableForOldQueue =
|
||||
oldestQueue->GetFirstScheduledTask();
|
||||
if (firstScheduledRunnableForOldQueue->EnqueueOrder() >
|
||||
firstScheduledRunnableForCurrentQueue->EnqueueOrder()) {
|
||||
oldestQueue = webTaskQueue;
|
||||
}
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(oldestQueue);
|
||||
return oldestQueue->GetFirstScheduledTask();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WebTaskScheduler::Disconnect() {
|
||||
mStaticPriorityTaskQueues.Clear();
|
||||
mDynamicPriorityTaskQueues.Clear();
|
||||
}
|
||||
|
||||
void WebTaskScheduler::RunTaskSignalPriorityChange(TaskSignal* aTaskSignal) {
|
||||
WebTaskQueue* const taskQueue = mDynamicPriorityTaskQueues.Get(aTaskSignal);
|
||||
MOZ_ASSERT(taskQueue);
|
||||
taskQueue->SetPriority(aTaskSignal->Priority());
|
||||
}
|
||||
|
||||
WebTaskQueue& WebTaskScheduler::SelectTaskQueue(
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal,
|
||||
const Optional<TaskPriority>& aPriority) {
|
||||
bool useSignal = !aPriority.WasPassed() && aSignal.WasPassed() &&
|
||||
aSignal.Value().IsTaskSignal();
|
||||
|
||||
if (useSignal) {
|
||||
TaskSignal* taskSignal = static_cast<TaskSignal*>(&(aSignal.Value()));
|
||||
WebTaskQueue* const taskQueue =
|
||||
mDynamicPriorityTaskQueues.GetOrInsertNew(taskSignal);
|
||||
taskQueue->SetPriority(taskSignal->Priority());
|
||||
taskSignal->SetWebTaskScheduler(this);
|
||||
MOZ_ASSERT(mDynamicPriorityTaskQueues.Contains(taskSignal));
|
||||
|
||||
return *taskQueue;
|
||||
}
|
||||
|
||||
TaskPriority taskPriority =
|
||||
aPriority.WasPassed() ? aPriority.Value() : TaskPriority::User_visible;
|
||||
|
||||
WebTaskQueue* const taskQueue = mStaticPriorityTaskQueues.GetOrInsertNew(
|
||||
static_cast<uint32_t>(taskPriority));
|
||||
taskQueue->SetPriority(taskPriority);
|
||||
MOZ_ASSERT(
|
||||
mStaticPriorityTaskQueues.Contains(static_cast<uint32_t>(taskPriority)));
|
||||
return *taskQueue;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,172 @@
|
|||
/* -*- 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/dom/Promise.h"
|
||||
#include "mozilla/dom/AbortFollower.h"
|
||||
#include "mozilla/dom/TimeoutHandler.h"
|
||||
#include "mozilla/dom/WebTaskSchedulingBinding.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
class WebTask : public LinkedListElement<RefPtr<WebTask>>,
|
||||
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) {}
|
||||
|
||||
void RunAbortAlgorithm() override;
|
||||
|
||||
bool HasScheduled() const { return mHasScheduled; }
|
||||
|
||||
uint32_t EnqueueOrder() const { return mEnqueueOrder; }
|
||||
|
||||
private:
|
||||
void SetHasScheduled(bool aHasScheduled) { mHasScheduled = aHasScheduled; }
|
||||
|
||||
uint32_t mEnqueueOrder;
|
||||
|
||||
RefPtr<SchedulerPostTaskCallback> mCallback;
|
||||
RefPtr<Promise> mPromise;
|
||||
|
||||
bool mHasScheduled;
|
||||
|
||||
~WebTask() = default;
|
||||
};
|
||||
|
||||
class WebTaskQueue {
|
||||
public:
|
||||
WebTaskQueue() = default;
|
||||
|
||||
TaskPriority Priority() const { return mPriority; }
|
||||
void SetPriority(TaskPriority aNewPriority) { mPriority = aNewPriority; }
|
||||
|
||||
LinkedList<RefPtr<WebTask>>& Tasks() { return mTasks; }
|
||||
|
||||
void AddTask(WebTask* aTask) { mTasks.insertBack(aTask); }
|
||||
|
||||
// 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() { mTasks.clear(); }
|
||||
|
||||
private:
|
||||
TaskPriority mPriority = TaskPriority::User_visible;
|
||||
LinkedList<RefPtr<WebTask>> mTasks;
|
||||
};
|
||||
|
||||
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_SCRIPT_HOLDER_NATIVE_CLASS(WebTaskScheduler)
|
||||
|
||||
static already_AddRefed<WebTaskSchedulerMainThread> CreateForMainThread(
|
||||
nsGlobalWindowInner* aWindow);
|
||||
|
||||
static already_AddRefed<WebTaskSchedulerWorker> CreateForWorker(
|
||||
WorkerPrivate* aWorkerPrivate);
|
||||
|
||||
explicit WebTaskScheduler(nsIGlobalObject* aParent);
|
||||
|
||||
already_AddRefed<Promise> PostTask(SchedulerPostTaskCallback& aCallback,
|
||||
const SchedulerPostTaskOptions& aOptions);
|
||||
|
||||
nsIGlobalObject* GetParentObject() const { return mParent; }
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* cx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
WebTask* GetNextTask() const;
|
||||
|
||||
void Disconnect();
|
||||
|
||||
void RunTaskSignalPriorityChange(TaskSignal* aTaskSignal);
|
||||
|
||||
protected:
|
||||
virtual ~WebTaskScheduler() = default;
|
||||
nsCOMPtr<nsIGlobalObject> mParent;
|
||||
|
||||
uint32_t mNextEnqueueOrder;
|
||||
|
||||
private:
|
||||
already_AddRefed<WebTask> CreateTask(
|
||||
WebTaskQueue& aQueue, const Optional<OwningNonNull<AbortSignal>>& aSignal,
|
||||
SchedulerPostTaskCallback& aCallback, Promise* aPromise);
|
||||
|
||||
bool QueueTask(WebTask* aTask);
|
||||
|
||||
WebTaskQueue& SelectTaskQueue(
|
||||
const Optional<OwningNonNull<AbortSignal>>& aSignal,
|
||||
const Optional<TaskPriority>& aPriority);
|
||||
|
||||
virtual nsresult SetTimeoutForDelayedTask(WebTask* aTask,
|
||||
uint64_t aDelay) = 0;
|
||||
virtual bool DispatchEventLoopRunnable() = 0;
|
||||
|
||||
nsClassHashtable<nsUint32HashKey, WebTaskQueue> mStaticPriorityTaskQueues;
|
||||
nsClassHashtable<nsPtrHashKey<TaskSignal>, 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<WebTaskScheduler> mScheduler;
|
||||
// WebTask gets added to WebTaskQueue, and WebTaskQueue keeps its alive.
|
||||
WeakPtr<WebTask> mWebTask;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
#endif
|
|
@ -0,0 +1,54 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#include "mozilla/TaskController.h"
|
||||
#include "mozilla/dom/TimeoutManager.h"
|
||||
|
||||
#include "nsContentUtils.h"
|
||||
#include "WebTaskSchedulerMainThread.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
NS_IMETHODIMP WebTaskMainThreadRunnable::Run() {
|
||||
if (mScheduler) {
|
||||
RefPtr<WebTask> task = mScheduler->GetNextTask();
|
||||
if (task) {
|
||||
task->Run();
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult WebTaskSchedulerMainThread::SetTimeoutForDelayedTask(WebTask* aTask,
|
||||
uint64_t aDelay) {
|
||||
JSContext* cx = nsContentUtils::GetCurrentJSContext();
|
||||
if (!cx) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
nsIGlobalObject* global = GetParentObject();
|
||||
MOZ_ASSERT(global);
|
||||
|
||||
RefPtr<DelayedWebTaskHandler> handler =
|
||||
new DelayedWebTaskHandler(cx, this, aTask);
|
||||
|
||||
int32_t delay = aDelay > INT32_MAX ? INT32_MAX : (int32_t)aDelay;
|
||||
|
||||
int32_t handle;
|
||||
return global->AsInnerWindow()->TimeoutManager().SetTimeout(
|
||||
handler, delay, /* aIsInterval */ false,
|
||||
Timeout::Reason::eDelayedWebTaskTimeout, &handle);
|
||||
}
|
||||
|
||||
bool WebTaskSchedulerMainThread::DispatchEventLoopRunnable() {
|
||||
RefPtr<WebTaskMainThreadRunnable> runnable =
|
||||
new WebTaskMainThreadRunnable(this);
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThreadQueue(runnable.forget(),
|
||||
EventQueuePriority::Normal));
|
||||
return true;
|
||||
}
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,42 @@
|
|||
/* -*- 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_WebSchedulerMainThread_h
|
||||
#define mozilla_dom_WebSchedulerMainThread_h
|
||||
|
||||
#include "WebTaskScheduler.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#include "mozilla/dom/AbortFollower.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
class WebTaskMainThreadRunnable final : public Runnable {
|
||||
public:
|
||||
explicit WebTaskMainThreadRunnable(WebTaskSchedulerMainThread* aScheduler)
|
||||
: Runnable("WebTaskMainThreadRunnable"), mScheduler(aScheduler) {}
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
|
||||
|
||||
private:
|
||||
~WebTaskMainThreadRunnable() = default;
|
||||
WeakPtr<WebTaskSchedulerMainThread> mScheduler;
|
||||
};
|
||||
|
||||
class WebTaskSchedulerMainThread final : public WebTaskScheduler {
|
||||
public:
|
||||
explicit WebTaskSchedulerMainThread(nsIGlobalObject* aParent)
|
||||
: WebTaskScheduler(aParent) {}
|
||||
|
||||
private:
|
||||
nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay) override;
|
||||
bool DispatchEventLoopRunnable() override;
|
||||
|
||||
~WebTaskSchedulerMainThread() = default;
|
||||
};
|
||||
} // namespace mozilla::dom
|
||||
#endif
|
|
@ -0,0 +1,59 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "WebTaskSchedulerWorker.h"
|
||||
#include "mozilla/dom/WorkerScope.h"
|
||||
#include "mozilla/dom/TimeoutManager.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
WebTaskWorkerRunnable::WebTaskWorkerRunnable(
|
||||
WorkerPrivate* aWorkerPrivate, WebTaskSchedulerWorker* aSchedulerWorker)
|
||||
: WorkerSameThreadRunnable(aWorkerPrivate),
|
||||
mSchedulerWorker(aSchedulerWorker) {
|
||||
MOZ_ASSERT(mSchedulerWorker);
|
||||
}
|
||||
|
||||
WebTaskSchedulerWorker::WebTaskSchedulerWorker(WorkerPrivate* aWorkerPrivate)
|
||||
: WebTaskScheduler(aWorkerPrivate->GlobalScope()),
|
||||
mWorkerPrivate(aWorkerPrivate) {}
|
||||
|
||||
bool WebTaskWorkerRunnable::WorkerRun(JSContext* aCx,
|
||||
WorkerPrivate* aWorkerPrivate) {
|
||||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
if (mSchedulerWorker) {
|
||||
RefPtr<WebTask> task = mSchedulerWorker->GetNextTask();
|
||||
if (task) {
|
||||
task->Run();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult WebTaskSchedulerWorker::SetTimeoutForDelayedTask(WebTask* aTask,
|
||||
uint64_t aDelay) {
|
||||
JSContext* cx = nsContentUtils::GetCurrentJSContext();
|
||||
if (!cx) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
RefPtr<DelayedWebTaskHandler> handler =
|
||||
new DelayedWebTaskHandler(cx, this, aTask);
|
||||
ErrorResult rv;
|
||||
|
||||
int32_t delay = aDelay > INT32_MAX ? INT32_MAX : (int32_t)aDelay;
|
||||
mWorkerPrivate->SetTimeout(cx, handler, delay,
|
||||
/* aIsInterval */ false,
|
||||
Timeout::Reason::eDelayedWebTaskTimeout, rv);
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
|
||||
bool WebTaskSchedulerWorker::DispatchEventLoopRunnable() {
|
||||
RefPtr<WebTaskWorkerRunnable> runnable =
|
||||
new WebTaskWorkerRunnable(mWorkerPrivate, this);
|
||||
return runnable->Dispatch();
|
||||
}
|
||||
} // namespace mozilla::dom
|
|
@ -0,0 +1,48 @@
|
|||
/* -*- 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_WebTaskSchedulerWorker_h
|
||||
#define mozilla_dom_WebTaskSchedulerWorker_h
|
||||
|
||||
#include "WebTaskScheduler.h"
|
||||
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/dom/WorkerRunnable.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/dom/WebTaskSchedulingBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class WebTaskWorkerRunnable : public WorkerSameThreadRunnable {
|
||||
public:
|
||||
WebTaskWorkerRunnable(WorkerPrivate* aWorkerPrivate,
|
||||
WebTaskSchedulerWorker* aSchedulerWorker);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
|
||||
|
||||
private:
|
||||
~WebTaskWorkerRunnable() = default;
|
||||
WeakPtr<WebTaskSchedulerWorker> mSchedulerWorker;
|
||||
};
|
||||
|
||||
class WebTaskSchedulerWorker final : public WebTaskScheduler {
|
||||
public:
|
||||
explicit WebTaskSchedulerWorker(WorkerPrivate* aWorkerPrivate);
|
||||
|
||||
private:
|
||||
~WebTaskSchedulerWorker() = default;
|
||||
|
||||
nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay) override;
|
||||
bool DispatchEventLoopRunnable() override;
|
||||
|
||||
CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate;
|
||||
};
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
#endif
|
|
@ -0,0 +1,27 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
with Files("**"):
|
||||
BUG_COMPONENT = ("Core", "DOM: Performance")
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
"TaskSignal.h",
|
||||
"WebTaskController.h",
|
||||
"WebTaskScheduler.h",
|
||||
"WebTaskSchedulerMainThread.h",
|
||||
"WebTaskSchedulerWorker.h",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"WebTaskController.cpp",
|
||||
"WebTaskScheduler.cpp",
|
||||
"WebTaskSchedulerMainThread.cpp",
|
||||
"WebTaskSchedulerWorker.cpp",
|
||||
]
|
||||
|
||||
include("/ipc/chromium/chromium-config.mozbuild")
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
|
@ -52,6 +52,7 @@
|
|||
#include "mozilla/dom/TimeoutHandler.h"
|
||||
#include "mozilla/dom/WorkerBinding.h"
|
||||
#include "mozilla/dom/WorkerScope.h"
|
||||
#include "mozilla/dom/WebTaskScheduler.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/WindowContext.h"
|
||||
#include "mozilla/extensions/ExtensionBrowser.h" // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
|
||||
|
@ -4591,10 +4592,15 @@ bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
|
|||
|
||||
// If the worker script never ran, or failed to compile, we don't need to do
|
||||
// anything else.
|
||||
if (!GlobalScope()) {
|
||||
WorkerGlobalScope* global = GlobalScope();
|
||||
if (!global) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WebTaskScheduler* scheduler = global->GetExistingScheduler()) {
|
||||
scheduler->Disconnect();
|
||||
}
|
||||
|
||||
// Don't abort the script now, but we dispatch a runnable to do it when the
|
||||
// current JS frame is executed.
|
||||
if (aStatus == Closing) {
|
||||
|
@ -4872,14 +4878,25 @@ bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
|
|||
// break out of the loop.
|
||||
|
||||
RefPtr<TimeoutHandler> handler(info->mHandler);
|
||||
if (info->mReason == Timeout::Reason::eTimeoutOrInterval) {
|
||||
const char* reason;
|
||||
if (info->mIsInterval) {
|
||||
reason = "setInterval handler";
|
||||
} else {
|
||||
reason = "setTimeout handler";
|
||||
}
|
||||
|
||||
const char* reason;
|
||||
switch (info->mReason) {
|
||||
case Timeout::Reason::eTimeoutOrInterval:
|
||||
if (info->mIsInterval) {
|
||||
reason = "setInterval handler";
|
||||
} else {
|
||||
reason = "setTimeout handler";
|
||||
}
|
||||
break;
|
||||
case Timeout::Reason::eDelayedWebTaskTimeout:
|
||||
reason = "delayedWebTask handler";
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
|
||||
reason = "AbortSignal Timeout";
|
||||
}
|
||||
if (info->mReason == Timeout::Reason::eTimeoutOrInterval ||
|
||||
info->mReason == Timeout::Reason::eDelayedWebTaskTimeout) {
|
||||
RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
|
||||
CallbackDebuggerNotificationGuard guard(
|
||||
scope, info->mIsInterval
|
||||
|
@ -4892,7 +4909,7 @@ bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
|
|||
}
|
||||
} else {
|
||||
MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
|
||||
MOZ_ALWAYS_TRUE(handler->Call("AbortSignal timeout"));
|
||||
MOZ_ALWAYS_TRUE(handler->Call(reason));
|
||||
}
|
||||
|
||||
NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
#include "mozilla/dom/Performance.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseWorkerProxy.h"
|
||||
#include "mozilla/dom/WebTaskSchedulerWorker.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/SerializedStackHolder.h"
|
||||
#include "mozilla/dom/ServiceWorkerDescriptor.h"
|
||||
|
@ -380,6 +381,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WorkerGlobalScope,
|
|||
WorkerGlobalScopeBase)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebTaskScheduler)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
|
||||
|
@ -391,6 +393,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WorkerGlobalScope,
|
|||
WorkerGlobalScopeBase)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
|
||||
if (tmp->mWebTaskScheduler) {
|
||||
tmp->mWebTaskScheduler->Disconnect();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebTaskScheduler)
|
||||
}
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
|
||||
|
@ -696,6 +702,21 @@ already_AddRefed<IDBFactory> WorkerGlobalScope::GetIndexedDB(
|
|||
return indexedDB.forget();
|
||||
}
|
||||
|
||||
WebTaskScheduler* WorkerGlobalScope::Scheduler() {
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
if (!mWebTaskScheduler) {
|
||||
mWebTaskScheduler = WebTaskScheduler::CreateForWorker(mWorkerPrivate);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mWebTaskScheduler);
|
||||
return mWebTaskScheduler;
|
||||
}
|
||||
|
||||
WebTaskScheduler* WorkerGlobalScope::GetExistingScheduler() const {
|
||||
return mWebTaskScheduler;
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> WorkerGlobalScope::CreateImageBitmap(
|
||||
const ImageBitmapSource& aImage, const ImageBitmapOptions& aOptions,
|
||||
ErrorResult& aRv) {
|
||||
|
|
|
@ -76,6 +76,8 @@ class WorkerLocation;
|
|||
class WorkerNavigator;
|
||||
class WorkerPrivate;
|
||||
class VsyncWorkerChild;
|
||||
class WebTaskScheduler;
|
||||
class WebTaskSchedulerWorker;
|
||||
struct RequestInit;
|
||||
|
||||
namespace cache {
|
||||
|
@ -311,6 +313,9 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase {
|
|||
|
||||
already_AddRefed<cache::CacheStorage> GetCaches(ErrorResult& aRv);
|
||||
|
||||
WebTaskScheduler* Scheduler();
|
||||
WebTaskScheduler* GetExistingScheduler() const;
|
||||
|
||||
bool WindowInteractionAllowed() const;
|
||||
|
||||
void AllowWindowInteraction();
|
||||
|
@ -346,6 +351,7 @@ class WorkerGlobalScope : public WorkerGlobalScopeBase {
|
|||
RefPtr<IDBFactory> mIndexedDB;
|
||||
RefPtr<cache::CacheStorage> mCacheStorage;
|
||||
RefPtr<DebuggerNotificationManager> mDebuggerNotificationManager;
|
||||
RefPtr<WebTaskSchedulerWorker> mWebTaskScheduler;
|
||||
uint32_t mWindowInteractionsAllowed = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -2146,6 +2146,12 @@
|
|||
value: true
|
||||
mirror: always
|
||||
|
||||
# Whether the scheduler interface will be exposed
|
||||
- name: dom.enable_web_task_scheduling
|
||||
type: RelaxedAtomicBool
|
||||
value: @IS_NIGHTLY_BUILD@
|
||||
mirror: always
|
||||
|
||||
# If this is true, it's allowed to fire "cut", "copy" and "paste" events.
|
||||
# Additionally, "input" events may expose clipboard content when inputType
|
||||
# is "insertFromPaste" or something.
|
||||
|
|
|
@ -880,6 +880,7 @@ STATIC_ATOMS = [
|
|||
Atom("onpopupshowing", "onpopupshowing"),
|
||||
Atom("onpopupshown", "onpopupshown"),
|
||||
Atom("onprocessorerror", "onprocessorerror"),
|
||||
Atom("onprioritychange", "onprioritychange"),
|
||||
Atom("onpush", "onpush"),
|
||||
Atom("onpushsubscriptionchange", "onpushsubscriptionchange"),
|
||||
Atom("onRadioStateChange", "onRadioStateChange"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче