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

This commit is contained in:
2024-01-09 12:01:14 +08:00
42 changed files with 537 additions and 363 deletions
+32 -5
View File
@@ -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;
+5 -2
View File
@@ -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
+7
View File
@@ -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;
+1 -2
View File
@@ -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
+1
View File
@@ -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"
+12 -5
View File
@@ -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
+5 -10
View File
@@ -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
+3 -4
View File
@@ -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>;
}
+2
View File
@@ -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")
-6
View File
@@ -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;
}
+1 -1
View File
@@ -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());
}
}
-1
View File
@@ -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"
+6 -10
View File
@@ -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();
}
}
+1 -1
View File
@@ -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)
+2 -12
View File
@@ -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;
+4 -4
View File
@@ -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 |
+3 -2
View File
@@ -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"
+11 -6
View File
@@ -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();
}
}
}
+1
View File
@@ -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"
+1 -1
View File
@@ -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();
+1 -5
View File
@@ -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;
+1
View File
@@ -22,6 +22,7 @@
#include "nsPluginStreamListenerPeer.h"
#include "nsIServiceManager.h"
#include "nsThreadUtils.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/Preferences.h"
#include "nsPluginInstanceOwner.h"
-104
View File
@@ -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
{
-8
View File
@@ -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
+29 -7
View File
@@ -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();
+1
View File
@@ -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"
+6 -5
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)
: 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
+1 -1
View File
@@ -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);
+2
View File
@@ -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;
};
+22
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;
};
+2
View File
@@ -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
};
+1
View File
@@ -691,6 +691,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'PopStateEvent.webidl',
'PopupBlockedEvent.webidl',
'ProgressEvent.webidl',
'PromiseRejectionEvent.webidl',
'RecordErrorEvent.webidl',
'ScrollViewChangeEvent.webidl',
'ServiceWorkerMessageEvent.webidl',
+11 -19
View File
@@ -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());
+21 -11
View File
@@ -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!");
}
+2
View File
@@ -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;
+4
View File
@@ -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;
}
};
/**
+19
View File
@@ -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)
{
+21
View File
@@ -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.
-15
View File
@@ -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;
+2
View File
@@ -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)
+244 -69
View File
@@ -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
+49 -47
View File
@@ -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;