import from UXP: Issue #2435 - Implement notifying of rejected promises (9126a483)

This commit is contained in:
2024-01-09 15:26:51 +08:00
parent 48c48f4ab8
commit c0ee45d977
4 changed files with 165 additions and 2 deletions
+107 -2
View File
@@ -57,6 +57,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"
@@ -71,6 +72,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"
@@ -1021,16 +1024,53 @@ 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
PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
uint64_t promiseID = JS::GetPromiseID(aPromise);
if (state == PromiseRejectionHandlingState::Unhandled) {
PromiseDebugging::AddUncaughtRejection(aPromise);
RefPtr<Promise> promise = Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
aboutToBeNotified.AppendElement(promise);
unhandled.Put(promiseID, promise);
} else {
PromiseDebugging::AddConsumedRejection(aPromise);
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(unhandled.Get(promiseID, nullptr));
unhandled.Remove(promiseID);
return;
}
}
RefPtr<Promise> promise;
unhandled.Remove(promiseID, getter_AddRefs(promise));
if (!promise) {
nsIGlobalObject* global = xpc::NativeGlobal(aPromise);
if (nsCOMPtr<EventTarget> owner = do_QueryInterface(global)) {
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();
}
}
}
}
@@ -1442,6 +1482,13 @@ void
CycleCollectedJSContext::AfterProcessMicrotasks()
{
MOZ_ASSERT(mJSContext);
// 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());
@@ -1813,6 +1860,64 @@ CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
AfterProcessMicrotasks();
}
NS_IMETHODIMP
CycleCollectedJSContext::NotifyUnhandledRejections::Run()
{
for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) {
RefPtr<Promise>& promise = mUnhandledRejections[i];
if (!promise)
continue;
JS::RootedObject promiseObj(mCx->RootingCx(), promise->PromiseObj());
MOZ_ASSERT(JS::IsPromiseObject(promiseObj));
// Only fire unhandledrejection if the promise is still not handled;
uint64_t promiseID = JS::GetPromiseID(promiseObj);
if (!JS::GetPromiseIsHandled(promiseObj)) {
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);
// We don't use the result of dispatching event here to check whether to
// report the Promise to console.
bool dummy = true;
target->DispatchEvent(event, &dummy);
}
}
if (!JS::GetPromiseIsHandled(promiseObj)) {
MOZ_ASSERT(mCx->mPendingUnhandledRejections.Get(promiseID, nullptr));
mCx->mPendingUnhandledRejections.Remove(promiseID);
}
// If a rejected promise is being handled in "unhandledrejection" event
// handler, it should be removed from the table in
// PromiseRejectionTrackerCallback.
MOZ_ASSERT(!mCx->mPendingUnhandledRejections.Get(promiseID, nullptr));
}
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->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj));
}
return NS_OK;
}
void
CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope,
js::ScriptEnvironmentPreparer::Closure& closure)