Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2024-01-09 12:01:14 +08:00
commit d4a80aa2c4
42 changed files with 538 additions and 364 deletions

View file

@ -1383,6 +1383,30 @@ Animation::GetRenderedDocument() const
return mEffect->AsKeyframeEffect()->GetRenderedDocument(); 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 void
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
{ {
@ -1390,9 +1414,8 @@ Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) { if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
DoFinishNotificationImmediately(); DoFinishNotificationImmediately();
} else if (!mFinishNotificationTask.IsPending()) { } else if (!mFinishNotificationTask) {
RefPtr<nsRunnableMethod<Animation>> runnable = RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
context->DispatchToMicroTask(do_AddRef(runnable)); context->DispatchToMicroTask(do_AddRef(runnable));
mFinishNotificationTask = runnable.forget(); mFinishNotificationTask = runnable.forget();
} }
@ -1415,9 +1438,13 @@ Animation::MaybeResolveFinishedPromise()
} }
void void
Animation::DoFinishNotificationImmediately() Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync)
{ {
mFinishNotificationTask.Revoke(); if (aAsync && aAsync != mFinishNotificationTask) {
return;
}
mFinishNotificationTask = nullptr;
if (PlayState() != AnimationPlayState::Finished) { if (PlayState() != AnimationPlayState::Finished) {
return; return;

View file

@ -9,6 +9,7 @@
#include "nsWrapperCache.h" #include "nsWrapperCache.h"
#include "nsCycleCollectionParticipant.h" #include "nsCycleCollectionParticipant.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DOMEventTargetHelper.h" #include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
#include "mozilla/LinkedList.h" #include "mozilla/LinkedList.h"
@ -42,6 +43,7 @@ class AnimValuesStyleRule;
namespace dom { namespace dom {
class AsyncFinishNotification;
class CSSAnimation; class CSSAnimation;
class CSSTransition; class CSSTransition;
@ -378,7 +380,8 @@ protected:
void ResetFinishedPromise(); void ResetFinishedPromise();
void MaybeResolveFinishedPromise(); void MaybeResolveFinishedPromise();
void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag); void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
void DoFinishNotificationImmediately(); friend class AsyncFinishNotification;
void DoFinishNotificationImmediately(MicroTaskRunnable* aAsync = nullptr);
void DispatchPlaybackEvent(const nsAString& aName); void DispatchPlaybackEvent(const nsAString& aName);
/** /**
@ -446,7 +449,7 @@ protected:
// getAnimations() list. // getAnimations() list.
bool mIsRelevant; bool mIsRelevant;
nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask; RefPtr<MicroTaskRunnable> mFinishNotificationTask;
// True if mFinished is resolved or would be resolved if mFinished has // 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 // 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 // in that case mFinished is immediately reset to represent a new current

View file

@ -155,6 +155,13 @@ DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
bool needsTicks = false; bool needsTicks = false;
nsTArray<Animation*> animationsToRemove(mAnimations.Count()); nsTArray<Animation*> animationsToRemove(mAnimations.Count());
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
// step2.
// 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.
nsAutoMicroTask mt;
nsAutoAnimationMutationBatch mb(mDocument); nsAutoAnimationMutationBatch mb(mDocument);
for (Animation* animation = mAnimationOrder.getFirst(); animation; for (Animation* animation = mAnimationOrder.getFirst(); animation;

View file

@ -5,7 +5,6 @@
#include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/dom/CustomElementRegistryBinding.h" #include "mozilla/dom/CustomElementRegistryBinding.h"
#include "mozilla/dom/HTMLElementBinding.h" #include "mozilla/dom/HTMLElementBinding.h"
#include "mozilla/dom/WebComponentsBinding.h" #include "mozilla/dom/WebComponentsBinding.h"
@ -1153,7 +1152,7 @@ CustomElementReactionsStack::Enqueue(Element* aElement,
CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this); RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
context->DispatchMicroTaskRunnable(bqmt.forget()); context->DispatchToMicroTask(bqmt.forget());
} }
void void

View file

@ -9,6 +9,7 @@
#include "js/GCHashTable.h" #include "js/GCHashTable.h"
#include "js/TypeDecls.h" #include "js/TypeDecls.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/FunctionBinding.h"

View file

@ -5418,10 +5418,10 @@ nsContentUtils::RunInStableState(already_AddRefed<nsIRunnable> aRunnable)
/* static */ /* static */
void void
nsContentUtils::RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable) nsContentUtils::AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction)
{ {
MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!"); MOZ_ASSERT(CycleCollectedJSContext::Get(), "Must be on a script thread!");
CycleCollectedJSContext::Get()->RunInMetastableState(Move(aRunnable)); CycleCollectedJSContext::Get()->AddPendingIDBTransaction(Move(aTransaction));
} }
/* /*
@ -6472,9 +6472,16 @@ nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer)); contentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer));
// If there are 2 viewers for the current docshell, that // If there are 2 viewers for the current docshell, that
// means the current document is a zombie document. // means the current document may be a zombie document.
// Only navigate into the subdocument if it's not a zombie. // While load and pageshow events are dispatched, zombie viewer is the old,
return !zombieViewer; // to be hidden document.
if (zombieViewer) {
bool inOnLoad = false;
docShell->GetIsExecutingOnLoadHandler(&inOnLoad);
return inOnLoad;
}
return true;
} }
bool bool

View file

@ -1766,17 +1766,12 @@ public:
*/ */
static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable); static void RunInStableState(already_AddRefed<nsIRunnable> aRunnable);
/* Add a "synchronous section", in the form of an nsIRunnable run once the /* Add a pending IDBTransaction to be cleaned up at the end of performing a
* event loop has reached a "metastable state". |aRunnable| must not cause any * microtask checkpoint.
* queued events to be processed (i.e. must not spin the event loop). * See the step of "Cleanup Indexed Database Transactions" in
* We've reached a metastable state when the currently executing task or * https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
* 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
*/ */
static void RunInMetastableState(already_AddRefed<nsIRunnable> aRunnable); static void AddPendingIDBTransaction(already_AddRefed<nsIRunnable> aTransaction);
/* Process viewport META data. This gives us information for the scale /* Process viewport META data. This gives us information for the scale
* and zoom of a page on mobile devices. We stick the information in * and zoom of a page on mobile devices. We stick the information in

View file

@ -623,7 +623,7 @@ nsDOMMutationObserver::QueueMutationObserverMicroTask()
RefPtr<MutationObserverMicroTask> momt = RefPtr<MutationObserverMicroTask> momt =
new MutationObserverMicroTask(); new MutationObserverMicroTask();
ccjs->DispatchMicroTaskRunnable(momt.forget()); ccjs->DispatchToMicroTask(momt.forget());
} }
void void
@ -644,9 +644,8 @@ nsDOMMutationObserver::RescheduleForRun()
return; return;
} }
RefPtr<MutationObserverMicroTask> momt = RefPtr<MutationObserverMicroTask> momt = new MutationObserverMicroTask();
new MutationObserverMicroTask(); ccjs->DispatchToMicroTask(momt.forget());
ccjs->DispatchMicroTaskRunnable(momt.forget());
sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>; sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
} }

View file

@ -908,6 +908,7 @@ GK_ATOM(onreadsuccess, "onreadsuccess")
GK_ATOM(onready, "onready") GK_ATOM(onready, "onready")
GK_ATOM(onreadystatechange, "onreadystatechange") GK_ATOM(onreadystatechange, "onreadystatechange")
GK_ATOM(onreceived, "onreceived") GK_ATOM(onreceived, "onreceived")
GK_ATOM(onrejectionhandled, "onrejectionhandled")
GK_ATOM(onremoteheld, "onremoteheld") GK_ATOM(onremoteheld, "onremoteheld")
GK_ATOM(onremoteresumed, "onremoteresumed") GK_ATOM(onremoteresumed, "onremoteresumed")
GK_ATOM(onrequestprogress, "onrequestprogress") GK_ATOM(onrequestprogress, "onrequestprogress")
@ -951,6 +952,7 @@ GK_ATOM(ontransitionend, "ontransitionend")
GK_ATOM(ontransitionrun, "ontransitionrun") GK_ATOM(ontransitionrun, "ontransitionrun")
GK_ATOM(ontransitionstart, "ontransitionstart") GK_ATOM(ontransitionstart, "ontransitionstart")
GK_ATOM(onunderflow, "onunderflow") GK_ATOM(onunderflow, "onunderflow")
GK_ATOM(onunhandledrejection, "onunhandledrejection")
GK_ATOM(onunload, "onunload") GK_ATOM(onunload, "onunload")
GK_ATOM(onupdatefound, "onupdatefound") GK_ATOM(onupdatefound, "onupdatefound")
GK_ATOM(onupdateready, "onupdateready") GK_ATOM(onupdateready, "onupdateready")

View file

@ -13011,12 +13011,6 @@ nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout,
// point anyway, and the script context should have already reported // point anyway, and the script context should have already reported
// the script error in the usual way - so we just drop it. // 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) { if (trackNestingLevel) {
sNestingLevel = nestingLevel; sNestingLevel = nestingLevel;
} }

View file

@ -145,6 +145,6 @@ void nsIGlobalObject::QueueMicrotask(VoidFunction& aCallback) {
CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
if (context) { if (context) {
RefPtr<MicroTaskRunnable> mt = new QueuedMicrotask(this, aCallback); RefPtr<MicroTaskRunnable> mt = new QueuedMicrotask(this, aCallback);
context->DispatchMicroTaskRunnable(mt.forget()); context->DispatchToMicroTask(mt.forget());
} }
} }

View file

@ -13,7 +13,6 @@
#include "mozilla/Alignment.h" #include "mozilla/Alignment.h"
#include "mozilla/Array.h" #include "mozilla/Array.h"
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DeferredFinalize.h" #include "mozilla/DeferredFinalize.h"
#include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/CallbackObject.h" #include "mozilla/dom/CallbackObject.h"

View file

@ -78,12 +78,10 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
, mExceptionHandling(aExceptionHandling) , mExceptionHandling(aExceptionHandling)
, mIsMainThread(NS_IsMainThread()) , mIsMainThread(NS_IsMainThread())
{ {
if (mIsMainThread) {
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
if (ccjs) { if (ccjs) {
ccjs->EnterMicroTask(); ccjs->EnterMicroTask();
} }
}
// Compute the caller's subject principal (if necessary) early, before we // Compute the caller's subject principal (if necessary) early, before we
// do anything that might perturb the relevant state. // do anything that might perturb the relevant state.
@ -290,13 +288,11 @@ CallbackObject::CallSetup::~CallSetup()
// It is important that this is the last thing we do, after leaving the // It is important that this is the last thing we do, after leaving the
// compartment and undoing all our entry/incumbent script changes // compartment and undoing all our entry/incumbent script changes
if (mIsMainThread) {
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
if (ccjs) { if (ccjs) {
ccjs->LeaveMicroTask(); ccjs->LeaveMicroTask();
} }
} }
}
already_AddRefed<nsISupports> already_AddRefed<nsISupports>
CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback, CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback,

View file

@ -80,7 +80,7 @@ def idlTypeNeedsCycleCollection(type):
type.isObject() or type.isObject() or
type.isSpiderMonkeyInterface()): type.isSpiderMonkeyInterface()):
return False return False
elif type.isCallback() or type.isGeckoInterface(): elif type.isCallback() or type.isPromise() or type.isGeckoInterface():
return True return True
elif type.isUnion(): elif type.isUnion():
return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes) return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes)

View file

@ -1065,12 +1065,8 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
} }
if (NS_SUCCEEDED(result)) { if (NS_SUCCEEDED(result)) {
if (mIsMainThreadELM) { nsAutoMicroTask mt;
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
if (ccjs) {
ccjs->EnterMicroTask();
}
}
// nsIDOMEvent::currentTarget is set in EventDispatcher. // nsIDOMEvent::currentTarget is set in EventDispatcher.
if (listenerHolder.HasWebIDLCallback()) { if (listenerHolder.HasWebIDLCallback()) {
ErrorResult rv; ErrorResult rv;
@ -1080,12 +1076,6 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
} else { } else {
result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent); result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent);
} }
if (mIsMainThreadELM) {
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
if (ccjs) {
ccjs->LeaveMicroTask();
}
}
} }
return result; return result;

View file

@ -578,14 +578,14 @@ WINDOW_EVENT(popstate,
ePopState, ePopState,
EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly, EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
eBasicEventClass) eBasicEventClass)
// Not supported yet WINDOW_EVENT(rejectionhandled, eRejectionHandled,
// WINDOW_EVENT(redo) EventNameType_HTMLBodyOrFramesetOnly, eBasicEventClass)
WINDOW_EVENT(storage, WINDOW_EVENT(storage,
eStorage, eStorage,
EventNameType_HTMLBodyOrFramesetOnly, EventNameType_HTMLBodyOrFramesetOnly,
eBasicEventClass) eBasicEventClass)
// Not supported yet WINDOW_EVENT(unhandledrejection, eUnhandledRejection,
// WINDOW_EVENT(undo) EventNameType_HTMLBodyOrFramesetOnly, eBasicEventClass)
WINDOW_EVENT(unload, WINDOW_EVENT(unload,
eUnload, eUnload,
(EventNameType_XUL | EventNameType_SVGSVG | (EventNameType_XUL | EventNameType_SVGSVG |

View file

@ -18,10 +18,11 @@
#include "nsIObserverService.h" #include "nsIObserverService.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h" #include "mozilla/Services.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"
#include "mozilla/Preferences.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PermissionMessageUtils.h"

View file

@ -18,6 +18,7 @@
#include "IndexedDatabase.h" #include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h" #include "IndexedDatabaseInlines.h"
#include "mozilla/BasicEvents.h" #include "mozilla/BasicEvents.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/TypeTraits.h" #include "mozilla/TypeTraits.h"
#include "mozilla/dom/Element.h" #include "mozilla/dom/Element.h"
@ -870,22 +871,26 @@ DispatchSuccessEvent(ResultHelper* aResultHelper,
IDB_LOG_STRINGIFY(aEvent, kSuccessEventType)); IDB_LOG_STRINGIFY(aEvent, kSuccessEventType));
} }
MOZ_ASSERT_IF(transaction,
transaction->IsOpen() && !transaction->IsAborted());
bool dummy; bool dummy;
nsresult rv = request->DispatchEvent(aEvent, &dummy); nsresult rv = request->DispatchEvent(aEvent, &dummy);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
return; return;
} }
MOZ_ASSERT_IF(transaction,
transaction->IsOpen() || transaction->IsAborted());
WidgetEvent* internalEvent = aEvent->WidgetEventPtr(); WidgetEvent* internalEvent = aEvent->WidgetEventPtr();
MOZ_ASSERT(internalEvent); MOZ_ASSERT(internalEvent);
if (transaction && if (transaction &&
transaction->IsOpen() && transaction->IsOpen()) {
internalEvent->mFlags.mExceptionWasRaised) { if (internalEvent->mFlags.mExceptionWasRaised) {
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR); transaction->Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
} else {
// To handle upgrade transaction.
transaction->Run();
}
} }
} }

View file

@ -23,6 +23,7 @@
#include "mozilla/AutoRestore.h" #include "mozilla/AutoRestore.h"
#include "mozilla/Casting.h" #include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h" #include "mozilla/CheckedInt.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/EndianUtils.h" #include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h" #include "mozilla/ErrorNames.h"
#include "mozilla/LazyIdleThread.h" #include "mozilla/LazyIdleThread.h"

View file

@ -55,7 +55,7 @@ IDBFileHandle::Create(IDBMutableFile* aMutableFile,
MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!"); MOZ_ASSERT(NS_IsMainThread(), "This won't work on non-main threads!");
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle); nsCOMPtr<nsIRunnable> runnable = do_QueryObject(fileHandle);
nsContentUtils::RunInMetastableState(runnable.forget()); nsContentUtils::AddPendingIDBTransaction(runnable.forget());
fileHandle->SetCreating(); fileHandle->SetCreating();

View file

@ -190,13 +190,9 @@ IDBTransaction::CreateVersionChange(
transaction->SetScriptOwner(aDatabase->GetScriptOwner()); transaction->SetScriptOwner(aDatabase->GetScriptOwner());
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
nsContentUtils::RunInMetastableState(runnable.forget());
transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor; transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
transaction->mNextObjectStoreId = aNextObjectStoreId; transaction->mNextObjectStoreId = aNextObjectStoreId;
transaction->mNextIndexId = aNextIndexId; transaction->mNextIndexId = aNextIndexId;
transaction->mCreating = true;
aDatabase->RegisterTransaction(transaction); aDatabase->RegisterTransaction(transaction);
transaction->mRegistered = true; transaction->mRegistered = true;
@ -226,7 +222,7 @@ IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase,
transaction->SetScriptOwner(aDatabase->GetScriptOwner()); transaction->SetScriptOwner(aDatabase->GetScriptOwner());
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction); nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
nsContentUtils::RunInMetastableState(runnable.forget()); nsContentUtils::AddPendingIDBTransaction(runnable.forget());
transaction->mCreating = true; transaction->mCreating = true;

View file

@ -22,6 +22,7 @@
#include "nsPluginStreamListenerPeer.h" #include "nsPluginStreamListenerPeer.h"
#include "nsIServiceManager.h" #include "nsIServiceManager.h"
#include "nsThreadUtils.h" #include "nsThreadUtils.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "nsPluginInstanceOwner.h" #include "nsPluginInstanceOwner.h"

View file

@ -508,110 +508,6 @@ Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport)); NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport));
} }
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;
}
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* JSObject*
Promise::GlobalJSObject() const Promise::GlobalJSObject() const
{ {

View file

@ -104,14 +104,6 @@ public:
// specializations in the .cpp for // specializations in the .cpp for
// the T values we support. // 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();
// WebIDL // WebIDL
nsIGlobalObject* GetParentObject() const nsIGlobalObject* GetParentObject() const

View file

@ -217,8 +217,16 @@ PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
/* static */ void /* static */ void
PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise) PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
{ {
CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
auto& uncaught = storage->mUncaughtRejections;
auto& outstanding = storage->mOutstandingRejections;
// See 8.1.4.7 Unhandled promise rejections, Step 5.1.4.
RefPtr<Promise> promise = Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
uint64_t promiseID = JS::GetPromiseID(aPromise);
outstanding.Put(promiseID, promise);
// This might OOM, but won't set a pending exception, so we'll just ignore it. // This might OOM, but won't set a pending exception, so we'll just ignore it.
if (CycleCollectedJSContext::Get()->mUncaughtRejections.append(aPromise)) { if (uncaught.append(aPromise)) {
FlushRejections::DispatchNeeded(); FlushRejections::DispatchNeeded();
} }
} }
@ -226,15 +234,20 @@ PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
/* void */ void /* void */ void
PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise) PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise)
{ {
CycleCollectedJSContext* storage = CycleCollectedJSContext::Get();
auto& uncaught = storage->mUncaughtRejections;
auto& outstanding = storage->mOutstandingRejections;
// If the promise is in our list of uncaught rejections, we haven't yet // If the promise is in our list of uncaught rejections, we haven't yet
// reported it as unhandled. In that case, just remove it from the list // reported it as unhandled. In that case, just remove it from the list
// and don't add it to the list of consumed rejections. // and don't add it to the list of consumed rejections.
auto& uncaughtRejections = CycleCollectedJSContext::Get()->mUncaughtRejections; uint64_t promiseID = JS::GetPromiseID(aPromise);
for (size_t i = 0; i < uncaughtRejections.length(); i++) { outstanding.Remove(promiseID);
if (uncaughtRejections[i] == aPromise) { for (size_t i = 0; i < uncaught.length(); i++) {
if (uncaught[i] == aPromise) {
// To avoid large amounts of memmoves, we don't shrink the vector here. // To avoid large amounts of memmoves, we don't shrink the vector here.
// Instead, we filter out nullptrs when iterating over the vector later. // Instead, we filter out nullptrs when iterating over the vector later.
uncaughtRejections[i].set(nullptr); uncaught[i].set(nullptr);
return; return;
} }
} }
@ -251,6 +264,7 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
auto& uncaught = storage->mUncaughtRejections; auto& uncaught = storage->mUncaughtRejections;
auto& consumed = storage->mConsumedRejections; auto& consumed = storage->mConsumedRejections;
auto& outstanding = storage->mOutstandingRejections;
AutoJSAPI jsapi; AutoJSAPI jsapi;
jsapi.Init(); jsapi.Init();
@ -267,6 +281,10 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
continue; continue;
} }
// Clean up outstanding rejected promises weak set
uint64_t promiseID = JS::GetPromiseID(promise);
outstanding.Remove(promiseID);
for (size_t j = 0; j < observers.Length(); ++j) { for (size_t j = 0; j < observers.Length(); ++j) {
RefPtr<UncaughtRejectionObserver> obs = RefPtr<UncaughtRejectionObserver> obs =
static_cast<UncaughtRejectionObserver*>(observers[j].get()); static_cast<UncaughtRejectionObserver*>(observers[j].get());
@ -274,9 +292,13 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
IgnoredErrorResult err; IgnoredErrorResult err;
obs->OnLeftUncaught(promise, err); obs->OnLeftUncaught(promise, err);
} }
// report error to console, unless marked by unhandledrejection event
bool reportError = !JS::GetPromiseIsReported(promise);
if (reportError) {
JSAutoCompartment ac(cx, promise); JSAutoCompartment ac(cx, promise);
Promise::ReportRejectedPromise(cx, promise); Promise::ReportRejectedPromise(cx, promise);
} }
}
storage->mUncaughtRejections.clear(); storage->mUncaughtRejections.clear();
// Notify observers of consumed Promise. // Notify observers of consumed Promise.

View file

@ -52,6 +52,7 @@
#include "ImportManager.h" #include "ImportManager.h"
#include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/EncodingUtils.h"
#include "mozilla/ConsoleReportCollector.h" #include "mozilla/ConsoleReportCollector.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/Unused.h" #include "mozilla/Unused.h"

View file

@ -828,8 +828,6 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMP
AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: AutoJSAPI() : AutoJSAPI()
{ {
MOZ_ASSERT(NS_IsMainThread());
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
Init(); Init();
@ -838,9 +836,12 @@ AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMP
void void
AutoSlowOperation::CheckForInterrupt() AutoSlowOperation::CheckForInterrupt()
{ {
// For now we support only main thread!
if (mIsMainThread) {
// JS_CheckForInterrupt expects us to be in a compartment. // JS_CheckForInterrupt expects us to be in a compartment.
JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope()); JSAutoCompartment ac(cx(), xpc::UnprivilegedJunkScope());
JS_CheckForInterrupt(cx()); JS_CheckForInterrupt(cx());
} }
}
} // namespace mozilla } // namespace mozilla

View file

@ -298,7 +298,6 @@ protected:
// AutoJSAPI, so Init must NOT be called on subclasses that use this. // AutoJSAPI, so Init must NOT be called on subclasses that use this.
AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType); AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, Type aType);
private:
mozilla::Maybe<JSAutoRequest> mAutoRequest; mozilla::Maybe<JSAutoRequest> mAutoRequest;
mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment; mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
JSContext *mCx; JSContext *mCx;
@ -307,6 +306,7 @@ private:
bool mIsMainThread; bool mIsMainThread;
Maybe<JS::WarningReporter> mOldWarningReporter; Maybe<JS::WarningReporter> mOldWarningReporter;
private:
void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal, void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
JSContext* aCx, bool aIsMainThread); JSContext* aCx, bool aIsMainThread);

View file

@ -160,7 +160,9 @@ interface WindowEventHandlers {
attribute EventHandler onpagehide; attribute EventHandler onpagehide;
attribute EventHandler onpageshow; attribute EventHandler onpageshow;
attribute EventHandler onpopstate; attribute EventHandler onpopstate;
attribute EventHandler onrejectionhandled;
attribute EventHandler onstorage; attribute EventHandler onstorage;
attribute EventHandler onunhandledrejection;
attribute EventHandler onunload; attribute EventHandler onunload;
}; };

View file

@ -0,0 +1,22 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/.
*
* The origin of this IDL file is
* https://html.spec.whatwg.org/multipage/webappapis.html#the-promiserejectionevent-interface
*/
[Constructor(DOMString type, PromiseRejectionEventInit eventInitDict),
Exposed=(Window,Worker)]
interface PromiseRejectionEvent : Event
{
[BinaryName="rejectedPromise"]
readonly attribute Promise<any> promise;
readonly attribute any reason;
};
dictionary PromiseRejectionEventInit : EventInit {
required Promise<any> promise;
any reason;
};

View file

@ -25,6 +25,8 @@ interface WorkerGlobalScope : EventTarget {
attribute EventHandler onoffline; attribute EventHandler onoffline;
attribute EventHandler ononline; attribute EventHandler ononline;
attribute EventHandler onrejectionhandled;
attribute EventHandler onunhandledrejection;
// also has additional members in a partial interface // also has additional members in a partial interface
}; };

View file

@ -691,6 +691,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'PopStateEvent.webidl', 'PopStateEvent.webidl',
'PopupBlockedEvent.webidl', 'PopupBlockedEvent.webidl',
'ProgressEvent.webidl', 'ProgressEvent.webidl',
'PromiseRejectionEvent.webidl',
'RecordErrorEvent.webidl', 'RecordErrorEvent.webidl',
'ScrollViewChangeEvent.webidl', 'ScrollViewChangeEvent.webidl',
'ServiceWorkerMessageEvent.webidl', 'ServiceWorkerMessageEvent.webidl',

View file

@ -1007,6 +1007,10 @@ public:
: mWorkerPrivate(aWorkerPrivate) : mWorkerPrivate(aWorkerPrivate)
{ {
MOZ_ASSERT(aWorkerPrivate); 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() ~WorkerJSContext()
@ -1092,26 +1096,14 @@ public:
} }
} }
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 RefPtr<MicroTaskRunnable> runnable(aRunnable);
// 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);
MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(runnable); MOZ_ASSERT(runnable);
std::queue<nsCOMPtr<nsIRunnable>>* microTaskQueue = nullptr; std::queue<RefPtr<MicroTaskRunnable>>* microTaskQueue = nullptr;
JSContext* cx = GetCurrentThreadJSContext(); JSContext* cx = GetCurrentThreadJSContext();
NS_ASSERTION(cx, "This should never be null!"); NS_ASSERTION(cx, "This should never be null!");
@ -1120,15 +1112,15 @@ public:
NS_ASSERTION(global, "This should never be null!"); NS_ASSERTION(global, "This should never be null!");
// On worker threads, if the current global is the worker global, we use the // 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 // 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)) { if (IsWorkerGlobal(global)) {
microTaskQueue = &mPromiseMicroTaskQueue; microTaskQueue = &GetMicroTaskQueue();
} else { } else {
MOZ_ASSERT(IsDebuggerGlobal(global) || IsDebuggerSandbox(global)); MOZ_ASSERT(IsDebuggerGlobal(global) || IsDebuggerSandbox(global));
microTaskQueue = &mDebuggerPromiseMicroTaskQueue; microTaskQueue = &GetDebuggerMicroTaskQueue();
} }
microTaskQueue->push(runnable.forget()); microTaskQueue->push(runnable.forget());

View file

@ -40,6 +40,7 @@
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/ContentEvents.h" #include "mozilla/ContentEvents.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/EventDispatcher.h" #include "mozilla/EventDispatcher.h"
#include "mozilla/Likely.h" #include "mozilla/Likely.h"
#include "mozilla/LoadContext.h" #include "mozilla/LoadContext.h"
@ -4815,8 +4816,10 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
static_cast<nsIRunnable*>(runnable)->Run(); static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release(); runnable->Release();
// Flush the promise queue. CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
Promise::PerformWorkerDebuggerMicroTaskCheckpoint(); if (ccjs) {
ccjs->PerformDebuggerMicroTaskCheckpoint();
}
if (debuggerRunnablesPending) { if (debuggerRunnablesPending) {
WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope(); WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
@ -5802,8 +5805,12 @@ WorkerPrivate::EnterDebuggerEventLoop()
{ {
MutexAutoLock lock(mMutex); MutexAutoLock lock(mMutex);
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
context->GetDebuggerMicroTaskQueue();
while (mControlQueue.IsEmpty() && while (mControlQueue.IsEmpty() &&
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) { !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
debuggerMtQueue.empty()) {
WaitForWorkerEvents(); WaitForWorkerEvents();
} }
@ -5812,6 +5819,11 @@ WorkerPrivate::EnterDebuggerEventLoop()
// XXXkhuey should we abort JS on the stack here if we got Abort above? // XXXkhuey should we abort JS on the stack here if we got Abort above?
} }
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
if (context) {
context->PerformDebuggerMicroTaskCheckpoint();
}
if (debuggerRunnablesPending) { if (debuggerRunnablesPending) {
// Start the periodic GC timer if it is not already running. // Start the periodic GC timer if it is not already running.
SetGCTimerMode(PeriodicTimer); SetGCTimerMode(PeriodicTimer);
@ -5828,8 +5840,10 @@ WorkerPrivate::EnterDebuggerEventLoop()
static_cast<nsIRunnable*>(runnable)->Run(); static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release(); runnable->Release();
// Flush the promise queue. CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
Promise::PerformWorkerDebuggerMicroTaskCheckpoint(); if (ccjs) {
ccjs->PerformDebuggerMicroTaskCheckpoint();
}
// Now *might* be a good time to GC. Let the JS engine make the decision. // Now *might* be a good time to GC. Let the JS engine make the decision.
if (JS::CurrentGlobalOrNull(cx)) { if (JS::CurrentGlobalOrNull(cx)) {
@ -6256,8 +6270,8 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
RefPtr<Function> callback = info->mHandler->GetCallback(); RefPtr<Function> callback = info->mHandler->GetCallback();
if (!callback) { if (!callback) {
// scope for the AutoEntryScript, so it comes off the stack before we do nsAutoMicroTask mt;
// Promise::PerformMicroTaskCheckpoint.
AutoEntryScript aes(global, reason, false); AutoEntryScript aes(global, reason, false);
// Evaluate the timeout expression. // Evaluate the timeout expression.
@ -6292,10 +6306,6 @@ WorkerPrivate::RunExpiredTimeouts(JSContext* aCx)
rv.SuppressException(); 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!"); NS_ASSERTION(mRunningExpiredTimeouts, "Someone changed this!");
} }

View file

@ -151,6 +151,8 @@ public:
IMPL_EVENT_HANDLER(online) IMPL_EVENT_HANDLER(online)
IMPL_EVENT_HANDLER(offline) IMPL_EVENT_HANDLER(offline)
IMPL_EVENT_HANDLER(rejectionhandled)
IMPL_EVENT_HANDLER(unhandledrejection)
void void
Dump(const Optional<nsAString>& aString) const; Dump(const Optional<nsAString>& aString) const;

View file

@ -100,6 +100,10 @@ class PromiseObject : public NativeObject
int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32(); int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED)); setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
} }
bool isReported() {
MOZ_ASSERT(isUnhandled());
return flags() & PROMISE_FLAG_REPORTED;
}
}; };
/** /**

View file

@ -4949,12 +4949,31 @@ JS::GetPromiseResult(JS::HandleObject promiseObj)
return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason(); return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason();
} }
JS_PUBLIC_API(bool)
JS::GetPromiseIsHandled(JS::HandleObject promise)
{
PromiseObject* promiseObj = &promise->as<PromiseObject>();
return !promiseObj->isUnhandled();
}
JS_PUBLIC_API(JSObject*) JS_PUBLIC_API(JSObject*)
JS::GetPromiseAllocationSite(JS::HandleObject promise) JS::GetPromiseAllocationSite(JS::HandleObject promise)
{ {
return promise->as<PromiseObject>().allocationSite(); return promise->as<PromiseObject>().allocationSite();
} }
JS_PUBLIC_API(bool)
JS::GetPromiseIsReported(JS::HandleObject promise)
{
return promise->as<PromiseObject>().isReported();
}
JS_PUBLIC_API(void)
JS::MarkPromiseRejectionReported(JS::HandleObject promise)
{
return promise->as<PromiseObject>().markAsReported();
}
JS_PUBLIC_API(JSObject*) JS_PUBLIC_API(JSObject*)
JS::GetPromiseResolutionSite(JS::HandleObject promise) JS::GetPromiseResolutionSite(JS::HandleObject promise)
{ {

View file

@ -4611,6 +4611,27 @@ GetPromiseID(JS::HandleObject promise);
extern JS_PUBLIC_API(JS::Value) extern JS_PUBLIC_API(JS::Value)
GetPromiseResult(JS::HandleObject promise); GetPromiseResult(JS::HandleObject promise);
/**
* Returns whether the given promise's rejection is already handled or not.
*
* The caller must check the given promise is rejected before checking it's
* handled or not.
*/
extern JS_PUBLIC_API(bool)
GetPromiseIsHandled(JS::HandleObject promise);
/**
* Returns whether the given promise's rejection is reported or not.
*/
extern JS_PUBLIC_API(bool)
GetPromiseIsReported(JS::HandleObject promise);
/**
* Mark the given promise's rejection as already reported.
*/
extern JS_PUBLIC_API(void)
MarkPromiseRejectionReported(JS::HandleObject promise);
/** /**
* Returns a js::SavedFrame linked list of the stack that lead to the given * Returns a js::SavedFrame linked list of the stack that lead to the given
* Promise's allocation. * Promise's allocation.

View file

@ -3485,21 +3485,6 @@ XPCJSContext::BeforeProcessTask(bool aMightBlock)
{ {
MOZ_ASSERT(NS_IsMainThread()); 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());
}
}
// Start the slow script timer. // Start the slow script timer.
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
mSlowScriptSecondHalf = false; mSlowScriptSecondHalf = false;

View file

@ -118,7 +118,9 @@ NS_EVENT_MESSAGE(eImageAbort)
NS_EVENT_MESSAGE(eLoadError) NS_EVENT_MESSAGE(eLoadError)
NS_EVENT_MESSAGE(eLoadEnd) NS_EVENT_MESSAGE(eLoadEnd)
NS_EVENT_MESSAGE(ePopState) NS_EVENT_MESSAGE(ePopState)
NS_EVENT_MESSAGE(eRejectionHandled)
NS_EVENT_MESSAGE(eStorage) NS_EVENT_MESSAGE(eStorage)
NS_EVENT_MESSAGE(eUnhandledRejection)
NS_EVENT_MESSAGE(eBeforeUnload) NS_EVENT_MESSAGE(eBeforeUnload)
NS_EVENT_MESSAGE(eReadyStateChange) NS_EVENT_MESSAGE(eReadyStateChange)

View file

@ -56,6 +56,7 @@
#include "mozilla/CycleCollectedJSContext.h" #include "mozilla/CycleCollectedJSContext.h"
#include <algorithm> #include <algorithm>
#include "mozilla/ArrayUtils.h" #include "mozilla/ArrayUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/AutoRestore.h" #include "mozilla/AutoRestore.h"
#include "mozilla/Move.h" #include "mozilla/Move.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
@ -69,6 +70,8 @@
#include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/PromiseDebugging.h" #include "mozilla/dom/PromiseDebugging.h"
#include "mozilla/dom/PromiseRejectionEvent.h"
#include "mozilla/dom/PromiseRejectionEventBinding.h"
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
#include "jsprf.h" #include "jsprf.h"
#include "js/Debug.h" #include "js/Debug.h"
@ -437,7 +440,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
, mPrevGCNurseryCollectionCallback(nullptr) , mPrevGCNurseryCollectionCallback(nullptr)
, mJSHolders(256) , mJSHolders(256)
, mDoingStableStates(false) , mDoingStableStates(false)
, mDisableMicroTaskCheckpoint(false) , mTargetedMicroTaskRecursionDepth(0)
, mMicroTaskLevel(0) , mMicroTaskLevel(0)
, mMicroTaskRecursionDepth(0) , mMicroTaskRecursionDepth(0)
, mOutOfMemoryState(OOMState::OK) , mOutOfMemoryState(OOMState::OK)
@ -458,8 +461,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
MOZ_ASSERT(!mDeferredFinalizerTable.Count()); MOZ_ASSERT(!mDeferredFinalizerTable.Count());
// Last chance to process any events. // Last chance to process any events.
ProcessMetastableStateQueue(mBaseRecursionDepth); CleanupIDBTransactions(mBaseRecursionDepth);
MOZ_ASSERT(mMetastableStateEvents.IsEmpty()); MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
ProcessStableStateQueue(); ProcessStableStateQueue();
MOZ_ASSERT(mStableStateEvents.IsEmpty()); MOZ_ASSERT(mStableStateEvents.IsEmpty());
@ -467,8 +470,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
// Clear mPendingException first, since it might be cycle collected. // Clear mPendingException first, since it might be cycle collected.
mPendingException = nullptr; mPendingException = nullptr;
MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty()); MOZ_ASSERT(mDebuggerMicroTaskQueue.empty());
MOZ_ASSERT(mPromiseMicroTaskQueue.empty()); MOZ_ASSERT(mPendingMicroTaskRunnables.empty());
mUncaughtRejections.reset(); mUncaughtRejections.reset();
mConsumedRejections.reset(); mConsumedRejections.reset();
@ -921,7 +924,7 @@ CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData)
self->OnLargeAllocationFailure(); self->OnLargeAllocationFailure();
} }
class PromiseJobRunnable final : public Runnable class PromiseJobRunnable final : public MicroTaskRunnable
{ {
public: public:
PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite, PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite,
@ -935,14 +938,20 @@ public:
} }
protected: protected:
NS_IMETHOD virtual void Run(AutoSlowOperation& aAso) override
Run() override
{ {
nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor()); nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor());
if (global && !global->IsDying()) { if (global && !global->IsDying()) {
mCallback->Call("promise callback"); mCallback->Call("promise callback");
aAso.CheckForInterrupt();
} }
return NS_OK; }
virtual bool Suppressed() override
{
nsIGlobalObject* global =
xpc::NativeGlobal(mCallback->CallbackPreserveColor());
return global && global->IsInSyncOperation();
} }
private: private:
@ -976,7 +985,7 @@ CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
if (aIncumbentGlobal) { if (aIncumbentGlobal) {
global = xpc::NativeGlobal(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()); self->DispatchToMicroTask(runnable.forget());
return true; return true;
} }
@ -988,15 +997,65 @@ CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
PromiseRejectionHandlingState state, PromiseRejectionHandlingState state,
void* aData) void* aData)
{ {
#ifdef DEBUG
CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData); CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
#endif // DEBUG
MOZ_ASSERT(aCx == self->Context()); MOZ_ASSERT(aCx == self->Context());
MOZ_ASSERT(Get() == self); MOZ_ASSERT(Get() == self);
// TODO: Bug 1549351 - Promise rejection event should not be sent for
// cross-origin scripts
// See HTML 8.1.6.3 HostPromiseRejectionTracker
PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
PromiseHashtable& outstanding = self->mOutstandingRejections;
uint64_t promiseID = JS::GetPromiseID(aPromise);
if (state == PromiseRejectionHandlingState::Unhandled) { if (state == PromiseRejectionHandlingState::Unhandled) {
PromiseDebugging::AddUncaughtRejection(aPromise); // Step 5.
// Unhandled Promises first go to the about-to-be-notified queue, processed
// after Microtasks as NotifyUnhandledRejections
RefPtr<Promise> promise = Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
aboutToBeNotified.AppendElement(promise);
} else { } else {
// Unhandled-then-handled Promises (ie.e Promises where the unhandledrejection event
// handler attached a handler) may fire the rejectionhandled event.
// Step 6.1.
for (size_t i = 0; i < aboutToBeNotified.Length(); i++) {
if (aboutToBeNotified[i] &&
aboutToBeNotified[i]->PromiseObj() == aPromise) {
// To avoid large amounts of memmoves, we don't shrink the vector
// here. Instead, we filter out nullptrs when iterating over the
// vector later.
aboutToBeNotified[i] = nullptr;
MOZ_ASSERT(!outstanding.Get(promiseID, nullptr));
return;
}
}
// Step 6.2. and 6.3.
RefPtr<Promise> promise;
if (outstanding.Remove(promiseID, getter_AddRefs(promise))) {
// Step 6.4.
nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) {
// Step 6.5.
// The spec says to do this in a global task, but we're just queuing an event,
// which will execute in the next event loop either way.
PromiseRejectionEventInit init;
init.mPromise = Promise::CreateFromExisting(global, aPromise);
init.mReason = JS::GetPromiseResult(aPromise);
RefPtr<PromiseRejectionEvent> event = PromiseRejectionEvent::Constructor(owner,
NS_LITERAL_STRING("rejectionhandled"), init);
RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(owner, event);
asyncDispatcher->PostDOMEvent();
}
}
// Finally, notifiy debug observers
PromiseDebugging::AddConsumedRejection(aPromise); PromiseDebugging::AddConsumedRejection(aPromise);
} }
} }
@ -1175,18 +1234,18 @@ CycleCollectedJSContext::SetPendingException(nsIException* aException)
mPendingException = aException; mPendingException = aException;
} }
std::queue<nsCOMPtr<nsIRunnable>>& std::queue<RefPtr<MicroTaskRunnable>>&
CycleCollectedJSContext::GetPromiseMicroTaskQueue() CycleCollectedJSContext::GetMicroTaskQueue()
{ {
MOZ_ASSERT(mJSContext); MOZ_ASSERT(mJSContext);
return mPromiseMicroTaskQueue; return mPendingMicroTaskRunnables;
} }
std::queue<nsCOMPtr<nsIRunnable>>& std::queue<RefPtr<MicroTaskRunnable>>&
CycleCollectedJSContext::GetDebuggerPromiseMicroTaskQueue() CycleCollectedJSContext::GetDebuggerMicroTaskQueue()
{ {
MOZ_ASSERT(mJSContext); MOZ_ASSERT(mJSContext);
return mDebuggerPromiseMicroTaskQueue; return mDebuggerMicroTaskQueue;
} }
nsCycleCollectionParticipant* nsCycleCollectionParticipant*
@ -1345,24 +1404,24 @@ CycleCollectedJSContext::ProcessStableStateQueue()
} }
void void
CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth) CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth)
{ {
MOZ_ASSERT(mJSContext); MOZ_ASSERT(mJSContext);
MOZ_RELEASE_ASSERT(!mDoingStableStates); MOZ_RELEASE_ASSERT(!mDoingStableStates);
mDoingStableStates = true; mDoingStableStates = true;
nsTArray<RunInMetastableStateData> localQueue = Move(mMetastableStateEvents); nsTArray<PendingIDBTransactionData> localQueue = Move(mPendingIDBTransactions);
for (uint32_t i = 0; i < localQueue.Length(); ++i) for (uint32_t i = 0; i < localQueue.Length(); ++i)
{ {
RunInMetastableStateData& data = localQueue[i]; PendingIDBTransactionData& data = localQueue[i];
if (data.mRecursionDepth != aRecursionDepth) { if (data.mRecursionDepth != aRecursionDepth) {
continue; continue;
} }
{ {
nsCOMPtr<nsIRunnable> runnable = data.mRunnable.forget(); nsCOMPtr<nsIRunnable> transaction = data.mTransaction.forget();
runnable->Run(); transaction->Run();
} }
localQueue.RemoveElementAt(i--); localQueue.RemoveElementAt(i--);
@ -1370,11 +1429,27 @@ CycleCollectedJSContext::ProcessMetastableStateQueue(uint32_t aRecursionDepth)
// If the queue has events in it now, they were added from something we called, // If the queue has events in it now, they were added from something we called,
// so they belong at the end of the queue. // so they belong at the end of the queue.
localQueue.AppendElements(mMetastableStateEvents); localQueue.AppendElements(mPendingIDBTransactions);
localQueue.SwapElements(mMetastableStateEvents); localQueue.SwapElements(mPendingIDBTransactions);
mDoingStableStates = false; 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());
}
}
void void
CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
{ {
@ -1382,39 +1457,27 @@ CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
// See HTML 6.1.4.2 Processing model // 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. // Step 4.1: Execute microtasks.
if (!mDisableMicroTaskCheckpoint) {
PerformMicroTaskCheckPoint(); PerformMicroTaskCheckPoint();
if (NS_IsMainThread()) {
Promise::PerformMicroTaskCheckpoint();
} else {
Promise::PerformWorkerMicroTaskCheckpoint();
}
}
// Step 4.2 Execute any events that were waiting for a stable state. // Step 4.2 Execute any events that were waiting for a stable state.
ProcessStableStateQueue(); ProcessStableStateQueue();
} }
void void
CycleCollectedJSContext::AfterProcessMicrotask() CycleCollectedJSContext::AfterProcessMicrotasks()
{ {
MOZ_ASSERT(mJSContext); MOZ_ASSERT(mJSContext);
AfterProcessMicrotask(RecursionDepth()); // Notify unhandled promise rejections:
// https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
if (mAboutToBeNotifiedRejectedPromises.Length()) {
RefPtr<NotifyUnhandledRejections> runnable = new NotifyUnhandledRejections(
this, std::move(mAboutToBeNotifiedRejectedPromises));
NS_DispatchToCurrentThread(runnable);
} }
// Cleanup Indexed Database transactions:
void // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth) CleanupIDBTransactions(RecursionDepth());
{
MOZ_ASSERT(mJSContext);
// Between microtasks, execute any events that were waiting for a microtask
// to complete.
ProcessMetastableStateQueue(aRecursionDepth);
} }
uint32_t uint32_t
@ -1431,12 +1494,12 @@ CycleCollectedJSContext::RunInStableState(already_AddRefed<nsIRunnable>&& aRunna
} }
void void
CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable) CycleCollectedJSContext::AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction)
{ {
MOZ_ASSERT(mJSContext); MOZ_ASSERT(mJSContext);
RunInMetastableStateData data; PendingIDBTransactionData data;
data.mRunnable = aRunnable; data.mTransaction = aTransaction;
MOZ_ASSERT(mOwningThread); MOZ_ASSERT(mOwningThread);
data.mRecursionDepth = RecursionDepth(); data.mRecursionDepth = RecursionDepth();
@ -1453,7 +1516,7 @@ CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aR
} }
#endif #endif
mMetastableStateEvents.AppendElement(Move(data)); mPendingIDBTransactions.AppendElement(Move(data));
} }
IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx, IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx,
@ -1659,14 +1722,14 @@ CycleCollectedJSContext::PrepareWaitingZonesForGC()
} }
void 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(NS_IsMainThread());
MOZ_ASSERT(runnable); MOZ_ASSERT(runnable);
mPromiseMicroTaskQueue.push(runnable.forget()); mPendingMicroTaskRunnables.push(runnable.forget());
} }
class AsyncMutationHandler final : public mozilla::Runnable class AsyncMutationHandler final : public mozilla::Runnable
@ -1682,41 +1745,61 @@ public:
} }
}; };
void bool
CycleCollectedJSContext::PerformMicroTaskCheckPoint() CycleCollectedJSContext::PerformMicroTaskCheckPoint()
{ {
if (mPendingMicroTaskRunnables.empty()) { if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) {
AfterProcessMicrotasks();
// Nothing to do, return early. // Nothing to do, return early.
return; return false;
} }
uint32_t currentDepth = RecursionDepth(); uint32_t currentDepth = RecursionDepth();
if (mMicroTaskRecursionDepth >= currentDepth) { if (mMicroTaskRecursionDepth >= currentDepth) {
// We are already executing microtasks for the current recursion depth. // 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()) { if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) {
// Special case for main thread where DOM mutations may happen when // Special case for main thread where DOM mutations may happen when
// it is not safe to run scripts. // it is not safe to run scripts.
nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); nsContentUtils::AddScriptRunner(new AsyncMutationHandler());
return; return false;
} }
mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth); mozilla::AutoRestore<uint32_t> restore(mMicroTaskRecursionDepth);
MOZ_ASSERT(currentDepth > 0); MOZ_ASSERT(currentDepth > 0);
mMicroTaskRecursionDepth = currentDepth; mMicroTaskRecursionDepth = currentDepth;
bool didProcess = false;
AutoSlowOperation aso; AutoSlowOperation aso;
std::queue<RefPtr<MicroTaskRunnable>> suppressed; std::queue<RefPtr<MicroTaskRunnable>> suppressed;
while (!mPendingMicroTaskRunnables.empty()) { for (;;) {
RefPtr<MicroTaskRunnable> runnable = RefPtr<MicroTaskRunnable> runnable;
mPendingMicroTaskRunnables.front().forget(); if (!mDebuggerMicroTaskQueue.empty()) {
runnable = mDebuggerMicroTaskQueue.front().forget();
mDebuggerMicroTaskQueue.pop();
} else if (!mPendingMicroTaskRunnables.empty()) {
runnable = mPendingMicroTaskRunnables.front().forget();
mPendingMicroTaskRunnables.pop(); mPendingMicroTaskRunnables.pop();
} else {
break;
}
if (runnable->Suppressed()) { 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); suppressed.push(runnable);
} else { } else {
didProcess = true;
runnable->Run(aso); runnable->Run(aso);
} }
} }
@ -1726,13 +1809,105 @@ CycleCollectedJSContext::PerformMicroTaskCheckPoint()
// for some time, but no longer than spinning the event loop nestedly // for some time, but no longer than spinning the event loop nestedly
// (sync XHR, alert, etc.) // (sync XHR, alert, etc.)
mPendingMicroTaskRunnables.swap(suppressed); mPendingMicroTaskRunnables.swap(suppressed);
AfterProcessMicrotasks();
return didProcess;
} }
void void
CycleCollectedJSContext::DispatchMicroTaskRunnable( CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
already_AddRefed<MicroTaskRunnable> aRunnable)
{ {
mPendingMicroTaskRunnables.push(aRunnable); // 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();
}
NS_IMETHODIMP
CycleCollectedJSContext::NotifyUnhandledRejections::Run()
{
// See HTML 8.1.4.7 Unhandled promise rejections
// with 'mUnhandledRejections' == copy of 'about-to-be-notified rejected'
for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
// Step 5.1.
RefPtr<Promise>& promise = mUnhandledRejections[i];
if (!promise)
continue;
JS::RootedObject promiseObj(mCx->RootingCx(), promise->PromiseObj());
MOZ_ASSERT(JS::IsPromiseObject(promiseObj));
// Step 5.1.1.
if (JS::GetPromiseIsHandled(promiseObj))
continue;
uint64_t promiseID = JS::GetPromiseID(promiseObj);
// Step 5.1.2.
bool raiseError = true;
if (nsCOMPtr<EventTarget> target =
do_QueryInterface(promise->GetParentObject())) {
PromiseRejectionEventInit init;
init.mPromise = promise;
init.mReason = JS::GetPromiseResult(promiseObj);
init.mCancelable = true;
RefPtr<PromiseRejectionEvent> event =
PromiseRejectionEvent::Constructor(
target, NS_LITERAL_STRING("unhandledrejection"), init);
// Step 5.1.4. (reordered)
// In contrast to the letter of the spec, this must be done before
// the event handler is called or the PromiseRejectionTrackerCallback
// will not see the correct state.
PromiseDebugging::AddUncaughtRejection(promiseObj);
target->DispatchEvent(event, &raiseError);
}
// Step 5.1.3. (implied)
// If the Promise became handled, PromiseRejectionTrackerCallback will have marked it.
// If not, keep it in the "uncaught" list for PromiseDebugging, and set the flag if
// preventDefault indicated we don't want it reported to console.
if (!JS::GetPromiseIsHandled(promiseObj) && !raiseError) {
JS::MarkPromiseRejectionReported(promiseObj);
}
}
return NS_OK;
}
nsresult
CycleCollectedJSContext::NotifyUnhandledRejections::Cancel()
{
for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
RefPtr<Promise>& promise = mUnhandledRejections[i];
if (!promise)
continue;
JS::RootedObject promiseObj(mCx->RootingCx(), promise->PromiseObj());
mCx->mOutstandingRejections.Remove(JS::GetPromiseID(promiseObj));
}
return NS_OK;
} }
void void

View file

@ -12,6 +12,7 @@
#include "mozilla/mozalloc.h" #include "mozilla/mozalloc.h"
#include "mozilla/MemoryReporting.h" #include "mozilla/MemoryReporting.h"
#include "mozilla/SegmentedVector.h" #include "mozilla/SegmentedVector.h"
#include "mozilla/dom/Promise.h"
#include "jsapi.h" #include "jsapi.h"
#include "jsfriendapi.h" #include "jsfriendapi.h"
@ -170,9 +171,6 @@ protected:
virtual void CustomOutOfMemoryCallback() {} virtual void CustomOutOfMemoryCallback() {}
virtual void CustomLargeAllocationFailureCallback() {} virtual void CustomLargeAllocationFailureCallback() {}
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
private: private:
void void
DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
@ -243,11 +241,11 @@ private:
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { }; virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
void TraceNativeGrayRoots(JSTracer* aTracer); void TraceNativeGrayRoots(JSTracer* aTracer);
void AfterProcessMicrotask(uint32_t aRecursionDepth); void AfterProcessMicrotasks();
public: public:
void ProcessStableStateQueue(); void ProcessStableStateQueue();
private: private:
void ProcessMetastableStateQueue(uint32_t aRecursionDepth); void CleanupIDBTransactions(uint32_t aRecursionDepth);
public: public:
enum DeferredFinalizeType { enum DeferredFinalizeType {
@ -306,8 +304,8 @@ public:
already_AddRefed<nsIException> GetPendingException() const; already_AddRefed<nsIException> GetPendingException() const;
void SetPendingException(nsIException* aException); void SetPendingException(nsIException* aException);
std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue(); std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue(); std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
nsCycleCollectionParticipant* GCThingParticipant(); nsCycleCollectionParticipant* GCThingParticipant();
nsCycleCollectionParticipant* ZoneParticipant(); nsCycleCollectionParticipant* ZoneParticipant();
@ -346,53 +344,25 @@ public:
return JS::RootingContext::get(mJSContext); 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: protected:
JSContext* MaybeContext() const { return mJSContext; } JSContext* MaybeContext() const { return mJSContext; }
public: public:
// nsThread entrypoints // nsThread entrypoints
virtual void BeforeProcessTask(bool aMightBlock) { }; virtual void BeforeProcessTask(bool aMightBlock);
virtual void AfterProcessTask(uint32_t aRecursionDepth); virtual void AfterProcessTask(uint32_t aRecursionDepth);
// microtask processor entry point
void AfterProcessMicrotask();
uint32_t RecursionDepth(); uint32_t RecursionDepth();
// Run in stable state (call through nsContentUtils) // Run in stable state (call through nsContentUtils)
void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable); 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 AddPendingIDBTransaction(already_AddRefed<nsIRunnable>&& aTransaction);
void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
// Get the current thread's CycleCollectedJSContext. Returns null if there // Get the current thread's CycleCollectedJSContext. Returns null if there
// isn't one. // isn't one.
@ -411,7 +381,7 @@ public:
void PrepareWaitingZonesForGC(); void PrepareWaitingZonesForGC();
// Queue an async microtask to the current main or worker thread. // 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. // Call EnterMicroTask when you're entering JS execution.
// Usually the best way to do this is to use nsAutoMicroTask. // Usually the best way to do this is to use nsAutoMicroTask.
@ -442,9 +412,9 @@ public:
mMicroTaskLevel = aLevel; mMicroTaskLevel = aLevel;
} }
void PerformMicroTaskCheckPoint(); bool PerformMicroTaskCheckPoint();
void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable); void PerformDebuggerMicroTaskCheckpoint();
// Storage for watching rejected promises waiting for some client to // Storage for watching rejected promises waiting for some client to
// consume their rejection. // consume their rejection.
@ -460,6 +430,12 @@ public:
// in the last turn of the event loop. // in the last turn of the event loop.
JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mConsumedRejections; JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mConsumedRejections;
// This is for the "outstanding rejected promises weak set" in the spec,
// https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set
typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable;
PromiseHashtable mOutstandingRejections;
nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */ >> mUncaughtRejectionObservers; nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */ >> mUncaughtRejectionObservers;
private: private:
@ -483,24 +459,50 @@ private:
nsCOMPtr<nsIException> mPendingException; nsCOMPtr<nsIException> mPendingException;
nsThread* mOwningThread; // Manual refcounting to avoid include hell. nsThread* mOwningThread; // Manual refcounting to avoid include hell.
struct RunInMetastableStateData struct PendingIDBTransactionData
{ {
nsCOMPtr<nsIRunnable> mRunnable; nsCOMPtr<nsIRunnable> mTransaction;
uint32_t mRecursionDepth; uint32_t mRecursionDepth;
}; };
nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents; nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
nsTArray<RunInMetastableStateData> mMetastableStateEvents; nsTArray<PendingIDBTransactionData> mPendingIDBTransactions;
uint32_t mBaseRecursionDepth; uint32_t mBaseRecursionDepth;
bool mDoingStableStates; 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; uint32_t mMicroTaskLevel;
std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables; std::queue<RefPtr<MicroTaskRunnable>> mPendingMicroTaskRunnables;
std::queue<RefPtr<MicroTaskRunnable>> mDebuggerMicroTaskQueue;
uint32_t mMicroTaskRecursionDepth; uint32_t mMicroTaskRecursionDepth;
// This implements about-to-be-notified rejected promises list in the spec.
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
typedef nsTArray<RefPtr<dom::Promise>> PromiseArray;
PromiseArray mAboutToBeNotifiedRejectedPromises;
class NotifyUnhandledRejections final : public CancelableRunnable {
public:
NotifyUnhandledRejections(CycleCollectedJSContext* aCx,
PromiseArray&& aPromises)
: CancelableRunnable(),
mCx(aCx),
mUnhandledRejections(std::move(aPromises)) {}
NS_IMETHOD Run() final;
nsresult Cancel() final;
private:
CycleCollectedJSContext* mCx;
PromiseArray mUnhandledRejections;
};
OOMState mOutOfMemoryState; OOMState mOutOfMemoryState;
OOMState mLargeAllocationFailureState; OOMState mLargeAllocationFailureState;