зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1193394 - Part 1: Microtasks and promises scheduling. r=bevis
This commit is contained in:
Родитель
277d9cedf4
Коммит
b7726493fb
|
@ -1565,6 +1565,30 @@ Animation::GetRenderedDocument() const
|
|||
return mEffect->AsKeyframeEffect()->GetRenderedDocument();
|
||||
}
|
||||
|
||||
class AsyncFinishNotification : public MicroTaskRunnable
|
||||
{
|
||||
public:
|
||||
explicit AsyncFinishNotification(Animation* aAnimation)
|
||||
: MicroTaskRunnable()
|
||||
, mAnimation(aAnimation)
|
||||
{}
|
||||
|
||||
virtual void Run(AutoSlowOperation& aAso) override
|
||||
{
|
||||
mAnimation->DoFinishNotificationImmediately(this);
|
||||
mAnimation = nullptr;
|
||||
}
|
||||
|
||||
virtual bool Suppressed() override
|
||||
{
|
||||
nsIGlobalObject* global = mAnimation->GetOwnerGlobal();
|
||||
return global && global->IsInSyncOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Animation> mAnimation;
|
||||
};
|
||||
|
||||
void
|
||||
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
||||
{
|
||||
|
@ -1572,11 +1596,8 @@ Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
|||
|
||||
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
|
||||
DoFinishNotificationImmediately();
|
||||
} else if (!mFinishNotificationTask.IsPending()) {
|
||||
RefPtr<nsRunnableMethod<Animation>> runnable =
|
||||
NewRunnableMethod("dom::Animation::DoFinishNotificationImmediately",
|
||||
this,
|
||||
&Animation::DoFinishNotificationImmediately);
|
||||
} else if (!mFinishNotificationTask) {
|
||||
RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
|
||||
context->DispatchToMicroTask(do_AddRef(runnable));
|
||||
mFinishNotificationTask = runnable.forget();
|
||||
}
|
||||
|
@ -1599,9 +1620,13 @@ Animation::MaybeResolveFinishedPromise()
|
|||
}
|
||||
|
||||
void
|
||||
Animation::DoFinishNotificationImmediately()
|
||||
Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync)
|
||||
{
|
||||
mFinishNotificationTask.Revoke();
|
||||
if (aAsync && aAsync != mFinishNotificationTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFinishNotificationTask = nullptr;
|
||||
|
||||
if (PlayState() != AnimationPlayState::Finished) {
|
||||
return;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/AnimationPerformanceWarning.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
|
||||
#include "mozilla/LinkedList.h"
|
||||
|
@ -44,6 +45,7 @@ struct AnimationRule;
|
|||
|
||||
namespace dom {
|
||||
|
||||
class AsyncFinishNotification;
|
||||
class CSSAnimation;
|
||||
class CSSTransition;
|
||||
|
||||
|
@ -449,7 +451,8 @@ protected:
|
|||
void ResetFinishedPromise();
|
||||
void MaybeResolveFinishedPromise();
|
||||
void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
|
||||
void DoFinishNotificationImmediately();
|
||||
friend class AsyncFinishNotification;
|
||||
void DoFinishNotificationImmediately(MicroTaskRunnable* aAsync = nullptr);
|
||||
void DispatchPlaybackEvent(const nsAString& aName);
|
||||
|
||||
/**
|
||||
|
@ -542,7 +545,7 @@ protected:
|
|||
// getAnimations() list.
|
||||
bool mIsRelevant;
|
||||
|
||||
nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
|
||||
RefPtr<MicroTaskRunnable> mFinishNotificationTask;
|
||||
// True if mFinished is resolved or would be resolved if mFinished has
|
||||
// yet to be created. This is not set when mFinished is rejected since
|
||||
// in that case mFinished is immediately reset to represent a new current
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "DocumentTimeline.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/dom/DocumentTimelineBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "AnimationUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsDOMMutationObserver.h"
|
||||
|
@ -160,14 +159,11 @@ DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
|
|||
|
||||
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
|
||||
// step2.
|
||||
// FIXME: This needs to be replaced with nsAutoMicroTask.
|
||||
// Note that this should be done before nsAutoAnimationMutationBatch. If
|
||||
// PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
|
||||
// is destroyed, some mutation records might not be delivered in this
|
||||
// checkpoint.
|
||||
auto autoPerformMicrotaskCheckpoint = MakeScopeExit([] {
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
});
|
||||
nsAutoMicroTask mt;
|
||||
nsAutoAnimationMutationBatch mb(mDocument);
|
||||
|
||||
for (Animation* animation = mAnimationOrder.getFirst(); animation;
|
||||
|
|
|
@ -1067,7 +1067,7 @@ CustomElementReactionsStack::Enqueue(Element* aElement,
|
|||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
|
||||
context->DispatchMicroTaskRunnable(bqmt.forget());
|
||||
context->DispatchToMicroTask(bqmt.forget());
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -5871,10 +5871,10 @@ nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
|
|||
|
||||
/* static */
|
||||
void
|
||||
nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable)
|
||||
nsContentUtils::AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction)
|
||||
{
|
||||
MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
|
||||
CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable));
|
||||
CycleCollectedJSContext::Get()->AddPendingIDBTransaction(Move(aTransaction));
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
@ -6933,9 +6933,16 @@ nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
|
|||
contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
|
||||
|
||||
// If there are 2 viewers for the current docshell, that
|
||||
// means the current document is a zombie document.
|
||||
// Only navigate into the subdocument if it's not a zombie.
|
||||
return !zombieViewer;
|
||||
// means the current document may be a zombie document.
|
||||
// While load and pageshow events are dispatched, zombie viewer is the old,
|
||||
// to be hidden document.
|
||||
if (zombieViewer) {
|
||||
bool inOnLoad = false;
|
||||
docShell->GetIsExecutingOnLoadHandler(&inOnLoad);
|
||||
return inOnLoad;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -2031,17 +2031,12 @@ public:
|
|||
*/
|
||||
static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
|
||||
|
||||
/* Add a "synchronous section", in the form of an nsIRunnable run once the
|
||||
* event loop has reached a "metastable state". |aRunnable| must not cause any
|
||||
* queued events to be processed (i.e. must not spin the event loop).
|
||||
* We've reached a metastable state when the currently executing task or
|
||||
* microtask has finished. This is not specced at this time.
|
||||
* In practice this runs aRunnable once the currently executing task or
|
||||
* microtask finishes. If called multiple times per microtask, all the
|
||||
* runnables will be executed, in the order in which RunInMetastableState()
|
||||
* was called
|
||||
/* Add a pending IDBTransaction to be cleaned up at the end of performing a
|
||||
* microtask checkpoint.
|
||||
* See the step of "Cleanup Indexed Database Transactions" in
|
||||
* https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
||||
*/
|
||||
static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable);
|
||||
static void AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction);
|
||||
|
||||
/**
|
||||
* Returns true if we are doing StableState/MetastableState.
|
||||
|
|
|
@ -620,7 +620,7 @@ nsDOMMutationObserver::QueueMutationObserverMicroTask()
|
|||
|
||||
RefPtr<MutationObserverMicroTask> momt =
|
||||
new MutationObserverMicroTask();
|
||||
ccjs->DispatchMicroTaskRunnable(momt.forget());
|
||||
ccjs->DispatchToMicroTask(momt.forget());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -643,7 +643,7 @@ nsDOMMutationObserver::RescheduleForRun()
|
|||
|
||||
RefPtr<MutationObserverMicroTask> momt =
|
||||
new MutationObserverMicroTask();
|
||||
ccjs->DispatchMicroTaskRunnable(momt.forget());
|
||||
ccjs->DispatchToMicroTask(momt.forget());
|
||||
sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
|
||||
}
|
||||
|
||||
|
|
|
@ -6714,12 +6714,6 @@ nsGlobalWindowInner::RunTimeoutHandler(Timeout* aTimeout,
|
|||
// point anyway, and the script context should have already reported
|
||||
// the script error in the usual way - so we just drop it.
|
||||
|
||||
// Since we might be processing more timeouts, go ahead and flush the promise
|
||||
// queue now before we do that. We need to do that while we're still in our
|
||||
// "running JS is safe" state (e.g. mRunningTimeout is set, timeout->mRunning
|
||||
// is false).
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
|
||||
if (trackNestingLevel) {
|
||||
TimeoutManager::SetNestingLevel(nestingLevel);
|
||||
}
|
||||
|
|
|
@ -140,11 +140,9 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
|
|||
, mExceptionHandling(aExceptionHandling)
|
||||
, mIsMainThread(NS_IsMainThread())
|
||||
{
|
||||
if (mIsMainThread) {
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->EnterMicroTask();
|
||||
}
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->EnterMicroTask();
|
||||
}
|
||||
|
||||
// Compute the caller's subject principal (if necessary) early, before we
|
||||
|
@ -351,11 +349,9 @@ CallbackObject::CallSetup::~CallSetup()
|
|||
|
||||
// It is important that this is the last thing we do, after leaving the
|
||||
// compartment and undoing all our entry/incumbent script changes
|
||||
if (mIsMainThread) {
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->LeaveMicroTask();
|
||||
}
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->LeaveMicroTask();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1095,12 +1095,8 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
|
|||
}
|
||||
|
||||
if (NS_SUCCEEDED(result)) {
|
||||
if (mIsMainThreadELM) {
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->EnterMicroTask();
|
||||
}
|
||||
}
|
||||
nsAutoMicroTask mt;
|
||||
|
||||
// nsIDOMEvent::currentTarget is set in EventDispatcher.
|
||||
if (listenerHolder.HasWebIDLCallback()) {
|
||||
ErrorResult rv;
|
||||
|
@ -1110,12 +1106,6 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
|
|||
} else {
|
||||
result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent);
|
||||
}
|
||||
if (mIsMainThreadELM) {
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->LeaveMicroTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -849,22 +849,26 @@ DispatchSuccessEvent(ResultHelper* aResultHelper,
|
|||
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(transaction,
|
||||
transaction->IsOpen() && !transaction->IsAborted());
|
||||
|
||||
bool dummy;
|
||||
nsresult rv = request->DispatchEvent(aEvent, &dummy);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT_IF(transaction,
|
||||
transaction->IsOpen() || transaction->IsAborted());
|
||||
|
||||
WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
|
||||
MOZ_ASSERT(internalEvent);
|
||||
|
||||
if (transaction &&
|
||||
transaction->IsOpen() &&
|
||||
internalEvent->mFlags.mExceptionWasRaised) {
|
||||
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
|
||||
transaction->IsOpen()) {
|
||||
if (internalEvent->mFlags.mExceptionWasRaised) {
|
||||
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
|
||||
} else {
|
||||
// To handle upgrade transaction.
|
||||
transaction->Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ IDBFileHandle::Create(IDBMutableFile* aMutableFile,
|
|||
MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
nsContentUtils::AddPendingIDBTransaction(runnable.forget());
|
||||
|
||||
fileHandle->mCreating = true;
|
||||
|
||||
|
|
|
@ -192,15 +192,11 @@ IDBTransaction::CreateVersionChange(
|
|||
|
||||
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
|
||||
transaction->NoteActiveTransaction();
|
||||
|
||||
transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
|
||||
transaction->mNextObjectStoreId = aNextObjectStoreId;
|
||||
transaction->mNextIndexId = aNextIndexId;
|
||||
transaction->mCreating = true;
|
||||
|
||||
aDatabase->RegisterTransaction(transaction);
|
||||
transaction->mRegistered = true;
|
||||
|
@ -250,7 +246,7 @@ IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase,
|
|||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
nsContentUtils::AddPendingIDBTransaction(runnable.forget());
|
||||
|
||||
transaction->mCreating = true;
|
||||
|
||||
|
|
|
@ -510,126 +510,6 @@ Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Promise::PerformMicroTaskCheckpoint()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
|
||||
// On the main thread, we always use the main promise micro task queue.
|
||||
std::queue<nsCOMPtr<nsIRunnable>>& microtaskQueue =
|
||||
context->GetPromiseMicroTaskQueue();
|
||||
|
||||
if (microtaskQueue.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoSlowOperation aso;
|
||||
|
||||
do {
|
||||
nsCOMPtr<nsIRunnable> runnable = microtaskQueue.front().forget();
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue.pop();
|
||||
nsresult rv = runnable->Run();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return false;
|
||||
}
|
||||
aso.CheckForInterrupt();
|
||||
context->AfterProcessMicrotask();
|
||||
} while (!microtaskQueue.empty());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Promise::IsWorkerDebuggerMicroTaskEmpty()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (!context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
|
||||
&context->GetDebuggerPromiseMicroTaskQueue();
|
||||
|
||||
return microtaskQueue->empty();
|
||||
}
|
||||
|
||||
void
|
||||
Promise::PerformWorkerMicroTaskCheckpoint()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// For a normal microtask checkpoint, we try to use the debugger microtask
|
||||
// queue first. If the debugger queue is empty, we use the normal microtask
|
||||
// queue instead.
|
||||
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
|
||||
&context->GetDebuggerPromiseMicroTaskQueue();
|
||||
|
||||
if (microtaskQueue->empty()) {
|
||||
microtaskQueue = &context->GetPromiseMicroTaskQueue();
|
||||
if (microtaskQueue->empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue->pop();
|
||||
nsresult rv = runnable->Run();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
context->AfterProcessMicrotask();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Promise::PerformWorkerDebuggerMicroTaskCheckpoint()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// For a debugger microtask checkpoint, we always use the debugger microtask
|
||||
// queue.
|
||||
std::queue<nsCOMPtr<nsIRunnable>>* microtaskQueue =
|
||||
&context->GetDebuggerPromiseMicroTaskQueue();
|
||||
|
||||
if (microtaskQueue->empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = microtaskQueue->front().forget();
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue->pop();
|
||||
nsresult rv = runnable->Run();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return;
|
||||
}
|
||||
context->AfterProcessMicrotask();
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Promise::GlobalJSObject() const
|
||||
{
|
||||
|
|
|
@ -104,15 +104,6 @@ public:
|
|||
// specializations in the .cpp for
|
||||
// the T values we support.
|
||||
|
||||
// Called by DOM to let us execute our callbacks. May be called recursively.
|
||||
// Returns true if at least one microtask was processed.
|
||||
static bool PerformMicroTaskCheckpoint();
|
||||
|
||||
static void PerformWorkerMicroTaskCheckpoint();
|
||||
|
||||
static void PerformWorkerDebuggerMicroTaskCheckpoint();
|
||||
static bool IsWorkerDebuggerMicroTaskEmpty();
|
||||
|
||||
// WebIDL
|
||||
|
||||
nsIGlobalObject* GetParentObject() const
|
||||
|
|
|
@ -830,8 +830,6 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMP
|
|||
AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
|
||||
: AutoJSAPI()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
|
||||
Init();
|
||||
|
@ -840,9 +838,12 @@ AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMP
|
|||
void
|
||||
AutoSlowOperation::CheckForInterrupt()
|
||||
{
|
||||
// JS_CheckForInterrupt expects us to be in a compartment.
|
||||
JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
|
||||
JS_CheckForInterrupt(cx());
|
||||
// For now we support only main thread!
|
||||
if (mIsMainThread) {
|
||||
// JS_CheckForInterrupt expects us to be in a compartment.
|
||||
JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
|
||||
JS_CheckForInterrupt(cx());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -293,7 +293,6 @@ protected:
|
|||
// AutoJSAPI, so Init must NOT be called on subclasses that use this.
|
||||
AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
|
||||
|
||||
private:
|
||||
mozilla::Maybe<JSAutoRequest> mAutoRequest;
|
||||
mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
|
||||
JSContext *mCx;
|
||||
|
@ -302,6 +301,7 @@ private:
|
|||
bool mIsMainThread;
|
||||
Maybe<JS::WarningReporter> mOldWarningReporter;
|
||||
|
||||
private:
|
||||
void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
|
||||
JSContext* aCx, bool aIsMainThread);
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ public:
|
|||
MOZ_ASSERT(mWorkerPrivate);
|
||||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
if (mPendingPromisesCount) {
|
||||
if (mPendingPromisesCount || !mKeepAliveToken) {
|
||||
return;
|
||||
}
|
||||
if (mCallback) {
|
||||
|
@ -365,6 +365,18 @@ private:
|
|||
mSelfRef = nullptr;
|
||||
}
|
||||
|
||||
class MaybeDoneRunner : public MicroTaskRunnable
|
||||
{
|
||||
public:
|
||||
explicit MaybeDoneRunner(KeepAliveHandler* aHandler) : mHandler(aHandler) {}
|
||||
virtual void Run(AutoSlowOperation& aAso) override
|
||||
{
|
||||
mHandler->MaybeDone();
|
||||
}
|
||||
|
||||
RefPtr<KeepAliveHandler> mHandler;
|
||||
};
|
||||
|
||||
void
|
||||
RemovePromise(ExtendableEventResult aResult)
|
||||
{
|
||||
|
@ -388,10 +400,7 @@ private:
|
|||
CycleCollectedJSContext* cx = CycleCollectedJSContext::Get();
|
||||
MOZ_ASSERT(cx);
|
||||
|
||||
RefPtr<nsIRunnable> r =
|
||||
NewRunnableMethod("dom::KeepAliveHandler::MaybeDone",
|
||||
this,
|
||||
&KeepAliveHandler::MaybeDone);
|
||||
RefPtr<MaybeDoneRunner> r = new MaybeDoneRunner(this);
|
||||
cx->DispatchToMicroTask(r.forget());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1052,6 +1052,10 @@ public:
|
|||
{
|
||||
MOZ_COUNT_CTOR_INHERITED(WorkerJSContext, CycleCollectedJSContext);
|
||||
MOZ_ASSERT(aWorkerPrivate);
|
||||
// Magical number 2. Workers have the base recursion depth 1, and normal
|
||||
// runnables run at level 2, and we don't want to process microtasks
|
||||
// at any other level.
|
||||
SetTargetedMicroTaskRecursionDepth(2);
|
||||
}
|
||||
|
||||
~WorkerJSContext()
|
||||
|
@ -1105,26 +1109,14 @@ public:
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
virtual void AfterProcessTask(uint32_t aRecursionDepth) override
|
||||
virtual void DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable) override
|
||||
{
|
||||
// Only perform the Promise microtask checkpoint on the outermost event
|
||||
// loop. Don't run it, for example, during sync XHR or importScripts.
|
||||
if (aRecursionDepth == 2) {
|
||||
CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
|
||||
} else if (aRecursionDepth > 2) {
|
||||
AutoDisableMicroTaskCheckpoint disableMicroTaskCheckpoint;
|
||||
CycleCollectedJSContext::AfterProcessTask(aRecursionDepth);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable) override
|
||||
{
|
||||
RefPtr<nsIRunnable> runnable(aRunnable);
|
||||
RefPtr<MicroTaskRunnable> runnable(aRunnable);
|
||||
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr;
|
||||
std::queue<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
|
||||
|
||||
JSContext* cx = GetCurrentWorkerThreadJSContext();
|
||||
NS_ASSERTION(cx, "This should never be null!");
|
||||
|
@ -1133,16 +1125,16 @@ public:
|
|||
NS_ASSERTION(global, "This should never be null!");
|
||||
|
||||
// On worker threads, if the current global is the worker global, we use the
|
||||
// main promise micro task queue. Otherwise, the current global must be
|
||||
// main micro task queue. Otherwise, the current global must be
|
||||
// either the debugger global or a debugger sandbox, and we use the debugger
|
||||
// promise micro task queue instead.
|
||||
// micro task queue instead.
|
||||
if (IsWorkerGlobal(global)) {
|
||||
microTaskQueue = &mPromiseMicroTaskQueue;
|
||||
microTaskQueue = &GetMicroTaskQueue();
|
||||
} else {
|
||||
MOZ_ASSERT(IsWorkerDebuggerGlobal(global) ||
|
||||
IsWorkerDebuggerSandbox(global));
|
||||
|
||||
microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
|
||||
microTaskQueue = &GetDebuggerMicroTaskQueue();
|
||||
}
|
||||
|
||||
microTaskQueue->push(runnable.forget());
|
||||
|
|
|
@ -3259,8 +3259,10 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
|
|||
static_cast<nsIRunnable*>(runnable)->Run();
|
||||
runnable->Release();
|
||||
|
||||
// Flush the promise queue.
|
||||
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->PerformDebuggerMicroTaskCheckpoint();
|
||||
}
|
||||
|
||||
if (debuggerRunnablesPending) {
|
||||
WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
|
||||
|
@ -4344,9 +4346,12 @@ WorkerPrivate::EnterDebuggerEventLoop()
|
|||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
|
||||
context->GetDebuggerMicroTaskQueue();
|
||||
while (mControlQueue.IsEmpty() &&
|
||||
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
|
||||
Promise::IsWorkerDebuggerMicroTaskEmpty()) {
|
||||
debuggerMtQueue.empty()) {
|
||||
WaitForWorkerEvents();
|
||||
}
|
||||
|
||||
|
@ -4354,8 +4359,9 @@ WorkerPrivate::EnterDebuggerEventLoop()
|
|||
|
||||
// XXXkhuey should we abort JS on the stack here if we got Abort above?
|
||||
}
|
||||
if (!Promise::IsWorkerDebuggerMicroTaskEmpty()) {
|
||||
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (context) {
|
||||
context->PerformDebuggerMicroTaskCheckpoint();
|
||||
}
|
||||
if (debuggerRunnablesPending) {
|
||||
// Start the periodic GC timer if it is not already running.
|
||||
|
@ -4373,8 +4379,10 @@ WorkerPrivate::EnterDebuggerEventLoop()
|
|||
static_cast<nsIRunnable*>(runnable)->Run();
|
||||
runnable->Release();
|
||||
|
||||
// Flush the promise queue.
|
||||
Promise::PerformWorkerDebuggerMicroTaskCheckpoint();
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->PerformDebuggerMicroTaskCheckpoint();
|
||||
}
|
||||
|
||||
// Now *might* be a good time to GC. Let the JS engine make the decision.
|
||||
if (JS::CurrentGlobalOrNull(cx)) {
|
||||
|
@ -4739,8 +4747,8 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
|
|||
|
||||
RefPtr<Function> callback = info->mHandler->GetCallback();
|
||||
if (!callback) {
|
||||
// scope for the AutoEntryScript, so it comes off the stack before we do
|
||||
// Promise::PerformMicroTaskCheckpoint.
|
||||
nsAutoMicroTask mt;
|
||||
|
||||
AutoEntryScript aes(global, reason, false);
|
||||
|
||||
// Evaluate the timeout expression.
|
||||
|
@ -4775,10 +4783,6 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
|
|||
rv.SuppressException();
|
||||
}
|
||||
|
||||
// Since we might be processing more timeouts, go ahead and flush
|
||||
// the promise queue now before we do that.
|
||||
Promise::PerformWorkerMicroTaskCheckpoint();
|
||||
|
||||
NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
|
||||
}
|
||||
|
||||
|
|
|
@ -1219,21 +1219,6 @@ XPCJSContext::BeforeProcessTask(bool aMightBlock)
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// If ProcessNextEvent was called during a Promise "then" callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMightBlock) {
|
||||
if (Promise::PerformMicroTaskCheckpoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
|
||||
NS_DispatchToMainThread(new Runnable("Empty_microtask_runnable"));
|
||||
}
|
||||
}
|
||||
|
||||
// Start the slow script timer.
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
|
||||
mSlowScriptSecondHalf = false;
|
||||
|
|
|
@ -52,7 +52,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
|
|||
, mRuntime(nullptr)
|
||||
, mJSContext(nullptr)
|
||||
, mDoingStableStates(false)
|
||||
, mDisableMicroTaskCheckpoint(false)
|
||||
, mTargetedMicroTaskRecursionDepth(0)
|
||||
, mMicroTaskLevel(0)
|
||||
, mMicroTaskRecursionDepth(0)
|
||||
{
|
||||
|
@ -77,8 +77,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
|
|||
}
|
||||
|
||||
// Last chance to process any events.
|
||||
ProcessMetastableStateQueue(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
|
||||
CleanupIDBTransactions(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
|
||||
|
||||
ProcessStableStateQueue();
|
||||
MOZ_ASSERT(mStableStateEvents.IsEmpty());
|
||||
|
@ -86,8 +86,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
|
|||
// Clear mPendingException first, since it might be cycle collected.
|
||||
mPendingException = nullptr;
|
||||
|
||||
MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mPromiseMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
|
||||
|
||||
mUncaughtRejections.reset();
|
||||
mConsumedRejections.reset();
|
||||
|
@ -181,15 +181,14 @@ CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|||
return 0;
|
||||
}
|
||||
|
||||
class PromiseJobRunnable final : public Runnable
|
||||
class PromiseJobRunnable final : public MicroTaskRunnable
|
||||
{
|
||||
public:
|
||||
PromiseJobRunnable(JS::HandleObject aCallback,
|
||||
JS::HandleObject aAllocationSite,
|
||||
nsIGlobalObject* aIncumbentGlobal)
|
||||
: Runnable("PromiseJobRunnable")
|
||||
, mCallback(
|
||||
new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
|
||||
:mCallback(
|
||||
new PromiseJobCallback(aCallback, aAllocationSite, aIncumbentGlobal))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -198,15 +197,21 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
virtual void Run(AutoSlowOperation& aAso) override
|
||||
{
|
||||
JSObject* callback = mCallback->CallbackPreserveColor();
|
||||
nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
|
||||
if (global && !global->IsDying()) {
|
||||
mCallback->Call("promise callback");
|
||||
aAso.CheckForInterrupt();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
virtual bool Suppressed() override
|
||||
{
|
||||
nsIGlobalObject* global =
|
||||
xpc::NativeGlobal(mCallback->CallbackPreserveColor());
|
||||
return global && global->IsInSyncOperation();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -240,7 +245,7 @@ CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
|
|||
if (aIncumbentGlobal) {
|
||||
global = xpc::NativeGlobal(aIncumbentGlobal);
|
||||
}
|
||||
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
|
||||
RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, aAllocationSite, global);
|
||||
self->DispatchToMicroTask(runnable.forget());
|
||||
return true;
|
||||
}
|
||||
|
@ -281,18 +286,18 @@ CycleCollectedJSContext::SetPendingException(Exception* aException)
|
|||
mPendingException = aException;
|
||||
}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>&
|
||||
CycleCollectedJSContext::GetPromiseMicroTaskQueue()
|
||||
std::queue<RefPtr<MicroTaskRunnable>>&
|
||||
CycleCollectedJSContext::GetMicroTaskQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
return mPromiseMicroTaskQueue;
|
||||
return mPendingMicroTaskRunnables;
|
||||
}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>&
|
||||
CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue()
|
||||
std::queue<RefPtr<MicroTaskRunnable>>&
|
||||
CycleCollectedJSContext::GetDebuggerMicroTaskQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
return mDebuggerPromiseMicroTaskQueue;
|
||||
return mDebuggerMicroTaskQueue;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -312,24 +317,24 @@ CycleCollectedJSContext::ProcessStableStateQueue()
|
|||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
|
||||
CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
MOZ_RELEASE_ASSERT(!mDoingStableStates);
|
||||
mDoingStableStates = true;
|
||||
|
||||
nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents);
|
||||
nsTArray<PendingIDBTransactionData> localQueue = Move(mPendingIDBTransactions);
|
||||
|
||||
for (uint32_t i = 0; i < localQueue.Length(); ++i)
|
||||
{
|
||||
RunInMetastableStateData& data = localQueue[i];
|
||||
PendingIDBTransactionData& data = localQueue[i];
|
||||
if (data.mRecursionDepth != aRecursionDepth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget();
|
||||
runnable->Run();
|
||||
nsCOMPtr<nsIRunnable> transaction = data.mTransaction.forget();
|
||||
transaction->Run();
|
||||
}
|
||||
|
||||
localQueue.RemoveElementAt(i--);
|
||||
|
@ -337,11 +342,27 @@ CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
|
|||
|
||||
// If the queue has events in it now, they were added from something we called,
|
||||
// so they belong at the end of the queue.
|
||||
localQueue.AppendElements(mMetastableStateEvents);
|
||||
localQueue.SwapElements(mMetastableStateEvents);
|
||||
localQueue.AppendElements(mPendingIDBTransactions);
|
||||
localQueue.SwapElements(mPendingIDBTransactions);
|
||||
mDoingStableStates = false;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock)
|
||||
{
|
||||
// If ProcessNextEvent was called during a microtask callback, we
|
||||
// must process any pending microtasks before blocking in the event loop,
|
||||
// otherwise we may deadlock until an event enters the queue later.
|
||||
if (aMightBlock && PerformMicroTaskCheckPoint()) {
|
||||
// If any microtask was processed, we post a dummy event in order to
|
||||
// force the ProcessNextEvent call not to block. This is required
|
||||
// to support nested event loops implemented using a pattern like
|
||||
// "while (condition) thread.processNextEvent(true)", in case the
|
||||
// condition is triggered here by a Promise "then" callback.
|
||||
NS_DispatchToMainThread(new Runnable("BeforeProcessTask"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
{
|
||||
|
@ -349,19 +370,8 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
|||
|
||||
// See HTML 6.1.4.2 Processing model
|
||||
|
||||
// Execute any events that were waiting for a microtask to complete.
|
||||
// This is not (yet) in the spec.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
|
||||
// Step 4.1: Execute microtasks.
|
||||
if (!mDisableMicroTaskCheckpoint) {
|
||||
PerformMicroTaskCheckPoint();
|
||||
if (NS_IsMainThread()) {
|
||||
Promise::PerformMicroTaskCheckpoint();
|
||||
} else {
|
||||
Promise::PerformWorkerMicroTaskCheckpoint();
|
||||
}
|
||||
}
|
||||
PerformMicroTaskCheckPoint();
|
||||
|
||||
// Step 4.2 Execute any events that were waiting for a stable state.
|
||||
ProcessStableStateQueue();
|
||||
|
@ -371,10 +381,12 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
|||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask()
|
||||
CycleCollectedJSContext::AfterProcessMicrotasks()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
AfterProcessMicrotask(RecursionDepth());
|
||||
// Cleanup Indexed Database transactions:
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
||||
CleanupIDBTransactions(RecursionDepth());
|
||||
}
|
||||
|
||||
void CycleCollectedJSContext::IsIdleGCTaskNeeded()
|
||||
|
@ -407,16 +419,6 @@ void CycleCollectedJSContext::IsIdleGCTaskNeeded()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
|
||||
// Between microtasks, execute any events that were waiting for a microtask
|
||||
// to complete.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
CycleCollectedJSContext::RecursionDepth()
|
||||
{
|
||||
|
@ -431,12 +433,12 @@ CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunna
|
|||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable)
|
||||
CycleCollectedJSContext::AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
|
||||
RunInMetastableStateData data;
|
||||
data.mRunnable = aRunnable;
|
||||
PendingIDBTransactionData data;
|
||||
data.mTransaction = aTransaction;
|
||||
|
||||
MOZ_ASSERT(mOwningThread);
|
||||
data.mRecursionDepth = RecursionDepth();
|
||||
|
@ -453,18 +455,19 @@ CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aR
|
|||
}
|
||||
#endif
|
||||
|
||||
mMetastableStateEvents.AppendElement(Move(data));
|
||||
mPendingIDBTransactions.AppendElement(Move(data));
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable)
|
||||
CycleCollectedJSContext::DispatchToMicroTask(
|
||||
already_AddRefed<MicroTaskRunnable> aRunnable)
|
||||
{
|
||||
RefPtr<nsIRunnable> runnable(aRunnable);
|
||||
RefPtr<MicroTaskRunnable> runnable(aRunnable);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
mPromiseMicroTaskQueue.push(runnable.forget());
|
||||
mPendingMicroTaskRunnables.push(runnable.forget());
|
||||
}
|
||||
|
||||
class AsyncMutationHandler final : public mozilla::Runnable
|
||||
|
@ -482,41 +485,61 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
void
|
||||
bool
|
||||
CycleCollectedJSContext::PerformMicroTaskCheckPoint()
|
||||
{
|
||||
if (mPendingMicroTaskRunnables.empty()) {
|
||||
if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
|
||||
AfterProcessMicrotasks();
|
||||
// Nothing to do, return early.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t currentDepth = RecursionDepth();
|
||||
if (mMicroTaskRecursionDepth >= currentDepth) {
|
||||
// We are already executing microtasks for the current recursion depth.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mTargetedMicroTaskRecursionDepth != 0 &&
|
||||
mTargetedMicroTaskRecursionDepth != currentDepth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
|
||||
// Special case for main thread where DOM mutations may happen when
|
||||
// it is not safe to run scripts.
|
||||
nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
|
||||
MOZ_ASSERT(currentDepth > 0);
|
||||
mMicroTaskRecursionDepth = currentDepth;
|
||||
|
||||
bool didProcess = false;
|
||||
AutoSlowOperation aso;
|
||||
|
||||
std::queue<RefPtr<MicroTaskRunnable>> suppressed;
|
||||
while (!mPendingMicroTaskRunnables.empty()) {
|
||||
RefPtr<MicroTaskRunnable> runnable =
|
||||
mPendingMicroTaskRunnables.front().forget();
|
||||
mPendingMicroTaskRunnables.pop();
|
||||
for (;;) {
|
||||
RefPtr<MicroTaskRunnable> runnable;
|
||||
if (!mDebuggerMicroTaskQueue.empty()) {
|
||||
runnable = mDebuggerMicroTaskQueue.front().forget();
|
||||
mDebuggerMicroTaskQueue.pop();
|
||||
} else if (!mPendingMicroTaskRunnables.empty()) {
|
||||
runnable = mPendingMicroTaskRunnables.front().forget();
|
||||
mPendingMicroTaskRunnables.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (runnable->Suppressed()) {
|
||||
// Microtasks in worker shall never be suppressed.
|
||||
// Otherwise, mPendingMicroTaskRunnables will be replaced later with
|
||||
// all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
suppressed.push(runnable);
|
||||
} else {
|
||||
didProcess = true;
|
||||
runnable->Run(aso);
|
||||
}
|
||||
}
|
||||
|
@ -526,13 +549,37 @@ CycleCollectedJSContext::PerformMicroTaskCheckPoint()
|
|||
// for some time, but no longer than spinning the event loop nestedly
|
||||
// (sync XHR, alert, etc.)
|
||||
mPendingMicroTaskRunnables.swap(suppressed);
|
||||
|
||||
AfterProcessMicrotasks();
|
||||
|
||||
return didProcess;
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::DispatchMicroTaskRunnable(
|
||||
already_AddRefed<MicroTaskRunnable> aRunnable)
|
||||
{
|
||||
mPendingMicroTaskRunnables.push(aRunnable);
|
||||
}
|
||||
CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
|
||||
{
|
||||
// Don't do normal microtask handling checks here, since whoever is calling
|
||||
// this method is supposed to know what they are doing.
|
||||
|
||||
AutoSlowOperation aso;
|
||||
for (;;) {
|
||||
// For a debugger microtask checkpoint, we always use the debugger microtask
|
||||
// queue.
|
||||
std::queue<RefPtr<MicroTaskRunnable>>* microtaskQueue =
|
||||
&GetDebuggerMicroTaskQueue();
|
||||
|
||||
if (microtaskQueue->empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
RefPtr<MicroTaskRunnable> runnable = microtaskQueue->front().forget();
|
||||
MOZ_ASSERT(runnable);
|
||||
|
||||
// This function can re-enter, so we remove the element before calling.
|
||||
microtaskQueue->pop();
|
||||
runnable->Run(aso);
|
||||
}
|
||||
|
||||
AfterProcessMicrotasks();
|
||||
}
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -103,9 +103,6 @@ protected:
|
|||
|
||||
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
|
||||
|
||||
private:
|
||||
MOZ_IS_CLASS_INIT
|
||||
void InitializeCommon();
|
||||
|
@ -121,11 +118,11 @@ private:
|
|||
JS::PromiseRejectionHandlingState state,
|
||||
void* aData);
|
||||
|
||||
void AfterProcessMicrotask(uint32_t aRecursionDepth);
|
||||
void AfterProcessMicrotasks();
|
||||
public:
|
||||
void ProcessStableStateQueue();
|
||||
private:
|
||||
void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
|
||||
void CleanupIDBTransactions(uint32_t aRecursionDepth);
|
||||
|
||||
public:
|
||||
enum DeferredFinalizeType {
|
||||
|
@ -142,8 +139,8 @@ public:
|
|||
already_AddRefed<dom::Exception> GetPendingException() const;
|
||||
void SetPendingException(dom::Exception* aException);
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
|
||||
std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
|
||||
|
||||
JSContext* Context() const
|
||||
{
|
||||
|
@ -157,46 +154,19 @@ public:
|
|||
return JS::RootingContext::get(mJSContext);
|
||||
}
|
||||
|
||||
bool MicroTaskCheckpointDisabled() const
|
||||
void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth)
|
||||
{
|
||||
return mDisableMicroTaskCheckpoint;
|
||||
mTargetedMicroTaskRecursionDepth = aDepth;
|
||||
}
|
||||
|
||||
void DisableMicroTaskCheckpoint(bool aDisable)
|
||||
{
|
||||
mDisableMicroTaskCheckpoint = aDisable;
|
||||
}
|
||||
|
||||
class MOZ_RAII AutoDisableMicroTaskCheckpoint
|
||||
{
|
||||
public:
|
||||
AutoDisableMicroTaskCheckpoint()
|
||||
: mCCJSCX(CycleCollectedJSContext::Get())
|
||||
{
|
||||
mOldValue = mCCJSCX->MicroTaskCheckpointDisabled();
|
||||
mCCJSCX->DisableMicroTaskCheckpoint(true);
|
||||
}
|
||||
|
||||
~AutoDisableMicroTaskCheckpoint()
|
||||
{
|
||||
mCCJSCX->DisableMicroTaskCheckpoint(mOldValue);
|
||||
}
|
||||
|
||||
CycleCollectedJSContext* mCCJSCX;
|
||||
bool mOldValue;
|
||||
};
|
||||
|
||||
protected:
|
||||
JSContext* MaybeContext() const { return mJSContext; }
|
||||
|
||||
public:
|
||||
// nsThread entrypoints
|
||||
virtual void BeforeProcessTask(bool aMightBlock) { };
|
||||
virtual void BeforeProcessTask(bool aMightBlock);
|
||||
virtual void AfterProcessTask(uint32_t aRecursionDepth);
|
||||
|
||||
// microtask processor entry point
|
||||
void AfterProcessMicrotask();
|
||||
|
||||
// Check whether we need an idle GC task.
|
||||
void IsIdleGCTaskNeeded();
|
||||
|
||||
|
@ -204,16 +174,15 @@ public:
|
|||
|
||||
// Run in stable state (call through nsContentUtils)
|
||||
void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
// This isn't in the spec at all yet, but this gets the behavior we want for IDB.
|
||||
// Runs after the current microtask completes.
|
||||
void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
|
||||
|
||||
void AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
|
||||
|
||||
// Get the current thread's CycleCollectedJSContext. Returns null if there
|
||||
// isn't one.
|
||||
static CycleCollectedJSContext* Get();
|
||||
|
||||
// Queue an async microtask to the current main or worker thread.
|
||||
virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable);
|
||||
virtual void DispatchToMicroTask(already_AddRefed<MicroTaskRunnable> aRunnable);
|
||||
|
||||
// Call EnterMicroTask when you're entering JS execution.
|
||||
// Usually the best way to do this is to use nsAutoMicroTask.
|
||||
|
@ -244,9 +213,9 @@ public:
|
|||
mMicroTaskLevel = aLevel;
|
||||
}
|
||||
|
||||
void PerformMicroTaskCheckPoint();
|
||||
bool PerformMicroTaskCheckPoint();
|
||||
|
||||
void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable);
|
||||
void PerformDebuggerMicroTaskCheckpoint();
|
||||
|
||||
bool IsInStableOrMetaStableState()
|
||||
{
|
||||
|
@ -281,21 +250,25 @@ private:
|
|||
nsCOMPtr<dom::Exception> mPendingException;
|
||||
nsThread* mOwningThread; // Manual refcounting to avoid include hell.
|
||||
|
||||
struct RunInMetastableStateData
|
||||
struct PendingIDBTransactionData
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> mRunnable;
|
||||
nsCOMPtr<nsIRunnable> mTransaction;
|
||||
uint32_t mRecursionDepth;
|
||||
};
|
||||
|
||||
nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
|
||||
nsTArray<RunInMetastableStateData> mMetastableStateEvents;
|
||||
nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
|
||||
uint32_t mBaseRecursionDepth;
|
||||
bool mDoingStableStates;
|
||||
|
||||
bool mDisableMicroTaskCheckpoint;
|
||||
// If set to none 0, microtasks will be processed only when recursion depth
|
||||
// is the set value.
|
||||
uint32_t mTargetedMicroTaskRecursionDepth;
|
||||
|
||||
uint32_t mMicroTaskLevel;
|
||||
|
||||
std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
|
||||
std::queue<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
|
||||
|
||||
uint32_t mMicroTaskRecursionDepth;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче