mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 14:54:25 +00:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
@@ -1383,6 +1383,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)
|
||||
{
|
||||
@@ -1390,9 +1414,8 @@ Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
||||
|
||||
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
|
||||
DoFinishNotificationImmediately();
|
||||
} else if (!mFinishNotificationTask.IsPending()) {
|
||||
RefPtr<nsRunnableMethod<Animation>> runnable =
|
||||
NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
|
||||
} else if (!mFinishNotificationTask) {
|
||||
RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
|
||||
context->DispatchToMicroTask(do_AddRef(runnable));
|
||||
mFinishNotificationTask = runnable.forget();
|
||||
}
|
||||
@@ -1415,9 +1438,13 @@ Animation::MaybeResolveFinishedPromise()
|
||||
}
|
||||
|
||||
void
|
||||
Animation::DoFinishNotificationImmediately()
|
||||
Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync)
|
||||
{
|
||||
mFinishNotificationTask.Revoke();
|
||||
if (aAsync && aAsync != mFinishNotificationTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFinishNotificationTask = nullptr;
|
||||
|
||||
if (PlayState() != AnimationPlayState::Finished) {
|
||||
return;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
|
||||
#include "mozilla/LinkedList.h"
|
||||
@@ -42,6 +43,7 @@ class AnimValuesStyleRule;
|
||||
|
||||
namespace dom {
|
||||
|
||||
class AsyncFinishNotification;
|
||||
class CSSAnimation;
|
||||
class CSSTransition;
|
||||
|
||||
@@ -378,7 +380,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);
|
||||
|
||||
/**
|
||||
@@ -446,7 +449,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
|
||||
|
||||
@@ -155,6 +155,13 @@ DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
|
||||
bool needsTicks = false;
|
||||
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);
|
||||
|
||||
for (Animation* animation = mAnimationOrder.getFirst(); animation;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "mozilla/dom/CustomElementRegistry.h"
|
||||
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/dom/CustomElementRegistryBinding.h"
|
||||
#include "mozilla/dom/HTMLElementBinding.h"
|
||||
#include "mozilla/dom/WebComponentsBinding.h"
|
||||
@@ -1153,7 +1152,7 @@ CustomElementReactionsStack::Enqueue(Element* aElement,
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
RefPtr<BackupQueueMicroTask> bqmt = new BackupQueueMicroTask(this);
|
||||
context->DispatchMicroTaskRunnable(bqmt.forget());
|
||||
context->DispatchToMicroTask(bqmt.forget());
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "js/GCHashTable.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/FunctionBinding.h"
|
||||
|
||||
@@ -5418,10 +5418,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));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -6472,9 +6472,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
|
||||
|
||||
@@ -1766,17 +1766,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);
|
||||
|
||||
/* Process viewport META data. This gives us information for the scale
|
||||
* and zoom of a page on mobile devices. We stick the information in
|
||||
|
||||
@@ -623,7 +623,7 @@ nsDOMMutationObserver::QueueMutationObserverMicroTask()
|
||||
|
||||
RefPtr<MutationObserverMicroTask> momt =
|
||||
new MutationObserverMicroTask();
|
||||
ccjs->DispatchMicroTaskRunnable(momt.forget());
|
||||
ccjs->DispatchToMicroTask(momt.forget());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -644,9 +644,8 @@ nsDOMMutationObserver::RescheduleForRun()
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<MutationObserverMicroTask> momt =
|
||||
new MutationObserverMicroTask();
|
||||
ccjs->DispatchMicroTaskRunnable(momt.forget());
|
||||
RefPtr<MutationObserverMicroTask> momt = new MutationObserverMicroTask();
|
||||
ccjs->DispatchToMicroTask(momt.forget());
|
||||
sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>;
|
||||
}
|
||||
|
||||
|
||||
@@ -908,6 +908,7 @@ GK_ATOM(onreadsuccess, "onreadsuccess")
|
||||
GK_ATOM(onready, "onready")
|
||||
GK_ATOM(onreadystatechange, "onreadystatechange")
|
||||
GK_ATOM(onreceived, "onreceived")
|
||||
GK_ATOM(onrejectionhandled, "onrejectionhandled")
|
||||
GK_ATOM(onremoteheld, "onremoteheld")
|
||||
GK_ATOM(onremoteresumed, "onremoteresumed")
|
||||
GK_ATOM(onrequestprogress, "onrequestprogress")
|
||||
@@ -951,6 +952,7 @@ GK_ATOM(ontransitionend, "ontransitionend")
|
||||
GK_ATOM(ontransitionrun, "ontransitionrun")
|
||||
GK_ATOM(ontransitionstart, "ontransitionstart")
|
||||
GK_ATOM(onunderflow, "onunderflow")
|
||||
GK_ATOM(onunhandledrejection, "onunhandledrejection")
|
||||
GK_ATOM(onunload, "onunload")
|
||||
GK_ATOM(onupdatefound, "onupdatefound")
|
||||
GK_ATOM(onupdateready, "onupdateready")
|
||||
|
||||
@@ -13011,12 +13011,6 @@ nsGlobalWindow::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) {
|
||||
sNestingLevel = nestingLevel;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,6 @@ void nsIGlobalObject::QueueMicrotask(VoidFunction& aCallback) {
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (context) {
|
||||
RefPtr<MicroTaskRunnable> mt = new QueuedMicrotask(this, aCallback);
|
||||
context->DispatchMicroTaskRunnable(mt.forget());
|
||||
context->DispatchToMicroTask(mt.forget());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "mozilla/Alignment.h"
|
||||
#include "mozilla/Array.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/DeferredFinalize.h"
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/CallbackObject.h"
|
||||
|
||||
@@ -78,11 +78,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
|
||||
@@ -290,11 +288,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ def idlTypeNeedsCycleCollection(type):
|
||||
type.isObject() or
|
||||
type.isSpiderMonkeyInterface()):
|
||||
return False
|
||||
elif type.isCallback() or type.isGeckoInterface():
|
||||
elif type.isCallback() or type.isPromise() or type.isGeckoInterface():
|
||||
return True
|
||||
elif type.isUnion():
|
||||
return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes)
|
||||
|
||||
@@ -1065,12 +1065,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;
|
||||
@@ -1080,12 +1076,6 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
|
||||
} else {
|
||||
result = listenerHolder.GetXPCOMCallback()->HandleEvent(aDOMEvent);
|
||||
}
|
||||
if (mIsMainThreadELM) {
|
||||
CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
|
||||
if (ccjs) {
|
||||
ccjs->LeaveMicroTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -578,14 +578,14 @@ WINDOW_EVENT(popstate,
|
||||
ePopState,
|
||||
EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
|
||||
eBasicEventClass)
|
||||
// Not supported yet
|
||||
// WINDOW_EVENT(redo)
|
||||
WINDOW_EVENT(rejectionhandled, eRejectionHandled,
|
||||
EventNameType_HTMLBodyOrFramesetOnly, eBasicEventClass)
|
||||
WINDOW_EVENT(storage,
|
||||
eStorage,
|
||||
EventNameType_HTMLBodyOrFramesetOnly,
|
||||
eBasicEventClass)
|
||||
// Not supported yet
|
||||
// WINDOW_EVENT(undo)
|
||||
WINDOW_EVENT(unhandledrejection, eUnhandledRejection,
|
||||
EventNameType_HTMLBodyOrFramesetOnly, eBasicEventClass)
|
||||
WINDOW_EVENT(unload,
|
||||
eUnload,
|
||||
(EventNameType_XUL | EventNameType_SVGSVG |
|
||||
|
||||
@@ -18,10 +18,11 @@
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "mozilla/dom/PermissionMessageUtils.h"
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "IndexedDatabase.h"
|
||||
#include "IndexedDatabaseInlines.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
@@ -870,22 +871,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/ErrorNames.h"
|
||||
#include "mozilla/LazyIdleThread.h"
|
||||
|
||||
@@ -55,7 +55,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->SetCreating();
|
||||
|
||||
|
||||
@@ -190,13 +190,9 @@ IDBTransaction::CreateVersionChange(
|
||||
|
||||
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
|
||||
transaction->mBackgroundActor.mVersionChangeBackgroundActor = aActor;
|
||||
transaction->mNextObjectStoreId = aNextObjectStoreId;
|
||||
transaction->mNextIndexId = aNextIndexId;
|
||||
transaction->mCreating = true;
|
||||
|
||||
aDatabase->RegisterTransaction(transaction);
|
||||
transaction->mRegistered = true;
|
||||
@@ -226,7 +222,7 @@ IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase,
|
||||
transaction->SetScriptOwner(aDatabase->GetScriptOwner());
|
||||
|
||||
nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
|
||||
nsContentUtils::RunInMetastableState(runnable.forget());
|
||||
nsContentUtils::AddPendingIDBTransaction(runnable.forget());
|
||||
|
||||
transaction->mCreating = true;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "nsPluginStreamListenerPeer.h"
|
||||
#include "nsIServiceManager.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsPluginInstanceOwner.h"
|
||||
|
||||
|
||||
@@ -508,110 +508,6 @@ Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
|
||||
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*
|
||||
Promise::GlobalJSObject() const
|
||||
{
|
||||
|
||||
@@ -104,14 +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();
|
||||
|
||||
// WebIDL
|
||||
|
||||
nsIGlobalObject* GetParentObject() const
|
||||
|
||||
@@ -217,8 +217,16 @@ PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
|
||||
/* static */ void
|
||||
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.
|
||||
if (CycleCollectedJSContext::Get()->mUncaughtRejections.append(aPromise)) {
|
||||
if (uncaught.append(aPromise)) {
|
||||
FlushRejections::DispatchNeeded();
|
||||
}
|
||||
}
|
||||
@@ -226,15 +234,20 @@ PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
|
||||
/* void */ void
|
||||
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
|
||||
// reported it as unhandled. In that case, just remove it from the list
|
||||
// and don't add it to the list of consumed rejections.
|
||||
auto& uncaughtRejections = CycleCollectedJSContext::Get()->mUncaughtRejections;
|
||||
for (size_t i = 0; i < uncaughtRejections.length(); i++) {
|
||||
if (uncaughtRejections[i] == aPromise) {
|
||||
uint64_t promiseID = JS::GetPromiseID(aPromise);
|
||||
outstanding.Remove(promiseID);
|
||||
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.
|
||||
// Instead, we filter out nullptrs when iterating over the vector later.
|
||||
uncaughtRejections[i].set(nullptr);
|
||||
uncaught[i].set(nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -251,6 +264,7 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
|
||||
auto& uncaught = storage->mUncaughtRejections;
|
||||
auto& consumed = storage->mConsumedRejections;
|
||||
auto& outstanding = storage->mOutstandingRejections;
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
@@ -267,6 +281,10 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
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) {
|
||||
RefPtr<UncaughtRejectionObserver> obs =
|
||||
static_cast<UncaughtRejectionObserver*>(observers[j].get());
|
||||
@@ -274,8 +292,12 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
IgnoredErrorResult err;
|
||||
obs->OnLeftUncaught(promise, err);
|
||||
}
|
||||
JSAutoCompartment ac(cx, promise);
|
||||
Promise::ReportRejectedPromise(cx, promise);
|
||||
// report error to console, unless marked by unhandledrejection event
|
||||
bool reportError = !JS::GetPromiseIsReported(promise);
|
||||
if (reportError) {
|
||||
JSAutoCompartment ac(cx, promise);
|
||||
Promise::ReportRejectedPromise(cx, promise);
|
||||
}
|
||||
}
|
||||
storage->mUncaughtRejections.clear();
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "ImportManager.h"
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/ConsoleReportCollector.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
@@ -828,8 +828,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();
|
||||
@@ -838,9 +836,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
|
||||
|
||||
@@ -298,7 +298,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;
|
||||
@@ -307,6 +306,7 @@ private:
|
||||
bool mIsMainThread;
|
||||
Maybe<JS::WarningReporter> mOldWarningReporter;
|
||||
|
||||
private:
|
||||
void InitInternal(nsIGlobalObject* aGlobalObject, JSObject* aGlobal,
|
||||
JSContext* aCx, bool aIsMainThread);
|
||||
|
||||
|
||||
@@ -160,7 +160,9 @@ interface WindowEventHandlers {
|
||||
attribute EventHandler onpagehide;
|
||||
attribute EventHandler onpageshow;
|
||||
attribute EventHandler onpopstate;
|
||||
attribute EventHandler onrejectionhandled;
|
||||
attribute EventHandler onstorage;
|
||||
attribute EventHandler onunhandledrejection;
|
||||
attribute EventHandler onunload;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -25,6 +25,8 @@ interface WorkerGlobalScope : EventTarget {
|
||||
|
||||
attribute EventHandler onoffline;
|
||||
attribute EventHandler ononline;
|
||||
attribute EventHandler onrejectionhandled;
|
||||
attribute EventHandler onunhandledrejection;
|
||||
// also has additional members in a partial interface
|
||||
};
|
||||
|
||||
|
||||
@@ -691,6 +691,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
'PopStateEvent.webidl',
|
||||
'PopupBlockedEvent.webidl',
|
||||
'ProgressEvent.webidl',
|
||||
'PromiseRejectionEvent.webidl',
|
||||
'RecordErrorEvent.webidl',
|
||||
'ScrollViewChangeEvent.webidl',
|
||||
'ServiceWorkerMessageEvent.webidl',
|
||||
|
||||
@@ -1007,6 +1007,10 @@ public:
|
||||
: mWorkerPrivate(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()
|
||||
@@ -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
|
||||
// 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 = GetCurrentThreadJSContext();
|
||||
NS_ASSERTION(cx, "This should never be null!");
|
||||
@@ -1120,15 +1112,15 @@ 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(IsDebuggerGlobal(global) || IsDebuggerSandbox(global));
|
||||
|
||||
microTaskQueue = &mDebuggerPromiseMicroTaskQueue;
|
||||
microTaskQueue = &GetDebuggerMicroTaskQueue();
|
||||
}
|
||||
|
||||
microTaskQueue->push(runnable.forget());
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ContentEvents.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/LoadContext.h"
|
||||
@@ -4815,8 +4816,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();
|
||||
@@ -5802,8 +5805,12 @@ WorkerPrivate::EnterDebuggerEventLoop()
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
|
||||
context->GetDebuggerMicroTaskQueue();
|
||||
while (mControlQueue.IsEmpty() &&
|
||||
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty())) {
|
||||
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
|
||||
debuggerMtQueue.empty()) {
|
||||
WaitForWorkerEvents();
|
||||
}
|
||||
|
||||
@@ -5812,6 +5819,11 @@ WorkerPrivate::EnterDebuggerEventLoop()
|
||||
// XXXkhuey should we abort JS on the stack here if we got Abort above?
|
||||
}
|
||||
|
||||
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
||||
if (context) {
|
||||
context->PerformDebuggerMicroTaskCheckpoint();
|
||||
}
|
||||
|
||||
if (debuggerRunnablesPending) {
|
||||
// Start the periodic GC timer if it is not already running.
|
||||
SetGCTimerMode(PeriodicTimer);
|
||||
@@ -5828,8 +5840,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)) {
|
||||
@@ -6256,8 +6270,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.
|
||||
@@ -6292,10 +6306,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!");
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,8 @@ public:
|
||||
|
||||
IMPL_EVENT_HANDLER(online)
|
||||
IMPL_EVENT_HANDLER(offline)
|
||||
IMPL_EVENT_HANDLER(rejectionhandled)
|
||||
IMPL_EVENT_HANDLER(unhandledrejection)
|
||||
|
||||
void
|
||||
Dump(const Optional<nsAString>& aString) const;
|
||||
|
||||
@@ -100,6 +100,10 @@ class PromiseObject : public NativeObject
|
||||
int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
|
||||
setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
|
||||
}
|
||||
bool isReported() {
|
||||
MOZ_ASSERT(isUnhandled());
|
||||
return flags() & PROMISE_FLAG_REPORTED;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -4949,12 +4949,31 @@ JS::GetPromiseResult(JS::HandleObject promiseObj)
|
||||
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::GetPromiseAllocationSite(JS::HandleObject promise)
|
||||
{
|
||||
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::GetPromiseResolutionSite(JS::HandleObject promise)
|
||||
{
|
||||
|
||||
@@ -4611,6 +4611,27 @@ GetPromiseID(JS::HandleObject promise);
|
||||
extern JS_PUBLIC_API(JS::Value)
|
||||
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
|
||||
* Promise's allocation.
|
||||
|
||||
@@ -3485,21 +3485,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());
|
||||
}
|
||||
}
|
||||
|
||||
// Start the slow script timer.
|
||||
mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
|
||||
mSlowScriptSecondHalf = false;
|
||||
|
||||
@@ -118,7 +118,9 @@ NS_EVENT_MESSAGE(eImageAbort)
|
||||
NS_EVENT_MESSAGE(eLoadError)
|
||||
NS_EVENT_MESSAGE(eLoadEnd)
|
||||
NS_EVENT_MESSAGE(ePopState)
|
||||
NS_EVENT_MESSAGE(eRejectionHandled)
|
||||
NS_EVENT_MESSAGE(eStorage)
|
||||
NS_EVENT_MESSAGE(eUnhandledRejection)
|
||||
NS_EVENT_MESSAGE(eBeforeUnload)
|
||||
NS_EVENT_MESSAGE(eReadyStateChange)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include <algorithm>
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
@@ -69,6 +70,8 @@
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseBinding.h"
|
||||
#include "mozilla/dom/PromiseDebugging.h"
|
||||
#include "mozilla/dom/PromiseRejectionEvent.h"
|
||||
#include "mozilla/dom/PromiseRejectionEventBinding.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "jsprf.h"
|
||||
#include "js/Debug.h"
|
||||
@@ -437,7 +440,7 @@ CycleCollectedJSContext::CycleCollectedJSContext()
|
||||
, mPrevGCNurseryCollectionCallback(nullptr)
|
||||
, mJSHolders(256)
|
||||
, mDoingStableStates(false)
|
||||
, mDisableMicroTaskCheckpoint(false)
|
||||
, mTargetedMicroTaskRecursionDepth(0)
|
||||
, mMicroTaskLevel(0)
|
||||
, mMicroTaskRecursionDepth(0)
|
||||
, mOutOfMemoryState(OOMState::OK)
|
||||
@@ -458,8 +461,8 @@ CycleCollectedJSContext::~CycleCollectedJSContext()
|
||||
MOZ_ASSERT(!mDeferredFinalizerTable.Count());
|
||||
|
||||
// Last chance to process any events.
|
||||
ProcessMetastableStateQueue(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mMetastableStateEvents.IsEmpty());
|
||||
CleanupIDBTransactions(mBaseRecursionDepth);
|
||||
MOZ_ASSERT(mPendingIDBTransactions.IsEmpty());
|
||||
|
||||
ProcessStableStateQueue();
|
||||
MOZ_ASSERT(mStableStateEvents.IsEmpty());
|
||||
@@ -467,8 +470,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();
|
||||
@@ -921,7 +924,7 @@ CycleCollectedJSContext::LargeAllocationFailureCallback(void* aData)
|
||||
self->OnLargeAllocationFailure();
|
||||
}
|
||||
|
||||
class PromiseJobRunnable final : public Runnable
|
||||
class PromiseJobRunnable final : public MicroTaskRunnable
|
||||
{
|
||||
public:
|
||||
PromiseJobRunnable(JS::HandleObject aCallback, JS::HandleObject aAllocationSite,
|
||||
@@ -935,14 +938,20 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
NS_IMETHOD
|
||||
Run() override
|
||||
virtual void Run(AutoSlowOperation& aAso) override
|
||||
{
|
||||
nsIGlobalObject* global = xpc::NativeGlobal(mCallback->CallbackPreserveColor());
|
||||
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:
|
||||
@@ -976,7 +985,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;
|
||||
}
|
||||
@@ -988,15 +997,65 @@ CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
|
||||
PromiseRejectionHandlingState state,
|
||||
void* aData)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
|
||||
#endif // DEBUG
|
||||
|
||||
MOZ_ASSERT(aCx == self->Context());
|
||||
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) {
|
||||
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 {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1175,18 +1234,18 @@ CycleCollectedJSContext::SetPendingException(nsIException* 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;
|
||||
}
|
||||
|
||||
nsCycleCollectionParticipant*
|
||||
@@ -1345,24 +1404,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--);
|
||||
@@ -1370,11 +1429,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());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth)
|
||||
{
|
||||
@@ -1382,39 +1457,27 @@ 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();
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask()
|
||||
CycleCollectedJSContext::AfterProcessMicrotasks()
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
AfterProcessMicrotask(RecursionDepth());
|
||||
}
|
||||
|
||||
void
|
||||
CycleCollectedJSContext::AfterProcessMicrotask(uint32_t aRecursionDepth)
|
||||
{
|
||||
MOZ_ASSERT(mJSContext);
|
||||
|
||||
// Between microtasks, execute any events that were waiting for a microtask
|
||||
// to complete.
|
||||
ProcessMetastableStateQueue(aRecursionDepth);
|
||||
// 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:
|
||||
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
||||
CleanupIDBTransactions(RecursionDepth());
|
||||
}
|
||||
|
||||
uint32_t
|
||||
@@ -1431,12 +1494,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();
|
||||
@@ -1453,7 +1516,7 @@ CycleCollectedJSContext::RunInMetastableState(already_AddRefed<nsIRunnable>&& aR
|
||||
}
|
||||
#endif
|
||||
|
||||
mMetastableStateEvents.AppendElement(Move(data));
|
||||
mPendingIDBTransactions.AppendElement(Move(data));
|
||||
}
|
||||
|
||||
IncrementalFinalizeRunnable::IncrementalFinalizeRunnable(CycleCollectedJSContext* aCx,
|
||||
@@ -1659,14 +1722,14 @@ CycleCollectedJSContext::PrepareWaitingZonesForGC()
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1682,41 +1745,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);
|
||||
}
|
||||
}
|
||||
@@ -1726,13 +1809,105 @@ 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)
|
||||
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();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
CycleCollectedJSContext::NotifyUnhandledRejections::Run()
|
||||
{
|
||||
mPendingMicroTaskRunnables.push(aRunnable);
|
||||
// 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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "mozilla/mozalloc.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/SegmentedVector.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
@@ -170,9 +171,6 @@ protected:
|
||||
virtual void CustomOutOfMemoryCallback() {}
|
||||
virtual void CustomLargeAllocationFailureCallback() {}
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
|
||||
std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
|
||||
|
||||
private:
|
||||
void
|
||||
DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
|
||||
@@ -243,11 +241,11 @@ private:
|
||||
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
|
||||
void TraceNativeGrayRoots(JSTracer* aTracer);
|
||||
|
||||
void AfterProcessMicrotask(uint32_t aRecursionDepth);
|
||||
void AfterProcessMicrotasks();
|
||||
public:
|
||||
void ProcessStableStateQueue();
|
||||
private:
|
||||
void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
|
||||
void CleanupIDBTransactions(uint32_t aRecursionDepth);
|
||||
|
||||
public:
|
||||
enum DeferredFinalizeType {
|
||||
@@ -306,8 +304,8 @@ public:
|
||||
already_AddRefed<nsIException> GetPendingException() const;
|
||||
void SetPendingException(nsIException* aException);
|
||||
|
||||
std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
|
||||
std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& GetMicroTaskQueue();
|
||||
std::queue<RefPtr<MicroTaskRunnable>>& GetDebuggerMicroTaskQueue();
|
||||
|
||||
nsCycleCollectionParticipant* GCThingParticipant();
|
||||
nsCycleCollectionParticipant* ZoneParticipant();
|
||||
@@ -346,53 +344,25 @@ 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();
|
||||
|
||||
uint32_t RecursionDepth();
|
||||
|
||||
// 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.
|
||||
@@ -411,7 +381,7 @@ public:
|
||||
void PrepareWaitingZonesForGC();
|
||||
|
||||
// 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.
|
||||
@@ -442,9 +412,9 @@ public:
|
||||
mMicroTaskLevel = aLevel;
|
||||
}
|
||||
|
||||
void PerformMicroTaskCheckPoint();
|
||||
bool PerformMicroTaskCheckPoint();
|
||||
|
||||
void DispatchMicroTaskRunnable(already_AddRefed<MicroTaskRunnable> aRunnable);
|
||||
void PerformDebuggerMicroTaskCheckpoint();
|
||||
|
||||
// Storage for watching rejected promises waiting for some client to
|
||||
// consume their rejection.
|
||||
@@ -460,6 +430,12 @@ public:
|
||||
// in the last turn of the event loop.
|
||||
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;
|
||||
|
||||
private:
|
||||
@@ -483,24 +459,50 @@ private:
|
||||
nsCOMPtr<nsIException> 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;
|
||||
|
||||
// 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 mLargeAllocationFailureState;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user