mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-06 15:27:30 +09:00
Issue #2435 - Implement notifying of rejected promises
Based-on: m-c 1362272
This commit is contained in:
parent
65d72c5682
commit
9126a4836f
4 changed files with 165 additions and 2 deletions
|
|
@ -4949,6 +4949,13 @@ JS::GetPromiseResult(JS::HandleObject promiseObj)
|
||||||
return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason();
|
return promise->state() == JS::PromiseState::Fulfilled ? promise->value() : promise->reason();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS_PUBLIC_API(bool)
|
||||||
|
JS::GetPromiseIsHandled(JS::HandleObject promise)
|
||||||
|
{
|
||||||
|
PromiseObject* promiseObj = &promise->as<PromiseObject>();
|
||||||
|
return !promiseObj->isUnhandled();
|
||||||
|
}
|
||||||
|
|
||||||
JS_PUBLIC_API(JSObject*)
|
JS_PUBLIC_API(JSObject*)
|
||||||
JS::GetPromiseAllocationSite(JS::HandleObject promise)
|
JS::GetPromiseAllocationSite(JS::HandleObject promise)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4611,6 +4611,15 @@ GetPromiseID(JS::HandleObject promise);
|
||||||
extern JS_PUBLIC_API(JS::Value)
|
extern JS_PUBLIC_API(JS::Value)
|
||||||
GetPromiseResult(JS::HandleObject promise);
|
GetPromiseResult(JS::HandleObject promise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given promise's rejection is already handled or not.
|
||||||
|
*
|
||||||
|
* The caller must check the given promise is rejected before checking it's
|
||||||
|
* handled or not.
|
||||||
|
*/
|
||||||
|
extern JS_PUBLIC_API(bool)
|
||||||
|
GetPromiseIsHandled(JS::HandleObject promise);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a js::SavedFrame linked list of the stack that lead to the given
|
* Returns a js::SavedFrame linked list of the stack that lead to the given
|
||||||
* Promise's allocation.
|
* Promise's allocation.
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
#include "mozilla/CycleCollectedJSContext.h"
|
#include "mozilla/CycleCollectedJSContext.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "mozilla/ArrayUtils.h"
|
#include "mozilla/ArrayUtils.h"
|
||||||
|
#include "mozilla/AsyncEventDispatcher.h"
|
||||||
#include "mozilla/AutoRestore.h"
|
#include "mozilla/AutoRestore.h"
|
||||||
#include "mozilla/Move.h"
|
#include "mozilla/Move.h"
|
||||||
#include "mozilla/MemoryReporting.h"
|
#include "mozilla/MemoryReporting.h"
|
||||||
|
|
@ -69,6 +70,8 @@
|
||||||
#include "mozilla/dom/Promise.h"
|
#include "mozilla/dom/Promise.h"
|
||||||
#include "mozilla/dom/PromiseBinding.h"
|
#include "mozilla/dom/PromiseBinding.h"
|
||||||
#include "mozilla/dom/PromiseDebugging.h"
|
#include "mozilla/dom/PromiseDebugging.h"
|
||||||
|
#include "mozilla/dom/PromiseRejectionEvent.h"
|
||||||
|
#include "mozilla/dom/PromiseRejectionEventBinding.h"
|
||||||
#include "mozilla/dom/ScriptSettings.h"
|
#include "mozilla/dom/ScriptSettings.h"
|
||||||
#include "jsprf.h"
|
#include "jsprf.h"
|
||||||
#include "js/Debug.h"
|
#include "js/Debug.h"
|
||||||
|
|
@ -994,16 +997,53 @@ CycleCollectedJSContext::PromiseRejectionTrackerCallback(JSContext* aCx,
|
||||||
PromiseRejectionHandlingState state,
|
PromiseRejectionHandlingState state,
|
||||||
void* aData)
|
void* aData)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
|
||||||
CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
|
CycleCollectedJSContext* self = static_cast<CycleCollectedJSContext*>(aData);
|
||||||
#endif // DEBUG
|
|
||||||
MOZ_ASSERT(aCx == self->Context());
|
MOZ_ASSERT(aCx == self->Context());
|
||||||
MOZ_ASSERT(Get() == self);
|
MOZ_ASSERT(Get() == self);
|
||||||
|
|
||||||
|
// TODO: Bug 1549351 - Promise rejection event should not be sent for
|
||||||
|
// cross-origin scripts
|
||||||
|
|
||||||
|
PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises;
|
||||||
|
PromiseHashtable& unhandled = self->mPendingUnhandledRejections;
|
||||||
|
uint64_t promiseID = JS::GetPromiseID(aPromise);
|
||||||
|
|
||||||
if (state == PromiseRejectionHandlingState::Unhandled) {
|
if (state == PromiseRejectionHandlingState::Unhandled) {
|
||||||
PromiseDebugging::AddUncaughtRejection(aPromise);
|
PromiseDebugging::AddUncaughtRejection(aPromise);
|
||||||
|
RefPtr<Promise> promise = Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise);
|
||||||
|
aboutToBeNotified.AppendElement(promise);
|
||||||
|
unhandled.Put(promiseID, promise);
|
||||||
} else {
|
} else {
|
||||||
PromiseDebugging::AddConsumedRejection(aPromise);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1415,6 +1455,13 @@ void
|
||||||
CycleCollectedJSContext::AfterProcessMicrotasks()
|
CycleCollectedJSContext::AfterProcessMicrotasks()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(mJSContext);
|
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:
|
// Cleanup Indexed Database transactions:
|
||||||
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
|
||||||
CleanupIDBTransactions(RecursionDepth());
|
CleanupIDBTransactions(RecursionDepth());
|
||||||
|
|
@ -1783,6 +1830,64 @@ CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint()
|
||||||
AfterProcessMicrotasks();
|
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
|
void
|
||||||
CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope,
|
CycleCollectedJSContext::EnvironmentPreparer::invoke(JS::HandleObject scope,
|
||||||
js::ScriptEnvironmentPreparer::Closure& closure)
|
js::ScriptEnvironmentPreparer::Closure& closure)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "mozilla/mozalloc.h"
|
#include "mozilla/mozalloc.h"
|
||||||
#include "mozilla/MemoryReporting.h"
|
#include "mozilla/MemoryReporting.h"
|
||||||
#include "mozilla/SegmentedVector.h"
|
#include "mozilla/SegmentedVector.h"
|
||||||
|
#include "mozilla/dom/Promise.h"
|
||||||
#include "jsapi.h"
|
#include "jsapi.h"
|
||||||
#include "jsfriendapi.h"
|
#include "jsfriendapi.h"
|
||||||
|
|
||||||
|
|
@ -474,6 +475,47 @@ private:
|
||||||
|
|
||||||
uint32_t mMicroTaskRecursionDepth;
|
uint32_t mMicroTaskRecursionDepth;
|
||||||
|
|
||||||
|
// This implements about-to-be-notified rejected promises list in the spec.
|
||||||
|
// https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list
|
||||||
|
typedef nsTArray<RefPtr<dom::Promise>> PromiseArray;
|
||||||
|
PromiseArray mAboutToBeNotifiedRejectedPromises;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// We use different data structure and opposite logic here to achieve the same
|
||||||
|
// effect. Basically this is used for tracking the rejected promise that does
|
||||||
|
// NOT need firing a rejectionhandled event. We will check the table to see if
|
||||||
|
// firing rejectionhandled event is required when a rejected promise is being
|
||||||
|
// handled.
|
||||||
|
//
|
||||||
|
// The rejected promise will be stored in the table if
|
||||||
|
// - it is unhandled, and
|
||||||
|
// - The unhandledrejection is not yet fired.
|
||||||
|
//
|
||||||
|
// And be removed when
|
||||||
|
// - it is handled, or
|
||||||
|
// - A unhandledrejection is fired and it isn't being handled in event
|
||||||
|
// handler.
|
||||||
|
typedef nsRefPtrHashtable<nsUint64HashKey, dom::Promise> PromiseHashtable;
|
||||||
|
PromiseHashtable mPendingUnhandledRejections;
|
||||||
|
|
||||||
|
class NotifyUnhandledRejections final : public CancelableRunnable {
|
||||||
|
public:
|
||||||
|
NotifyUnhandledRejections(CycleCollectedJSContext* aCx,
|
||||||
|
PromiseArray&& aPromises)
|
||||||
|
: CancelableRunnable(),
|
||||||
|
mCx(aCx),
|
||||||
|
mUnhandledRejections(std::move(aPromises)) {}
|
||||||
|
|
||||||
|
NS_IMETHOD Run() final;
|
||||||
|
|
||||||
|
nsresult Cancel() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CycleCollectedJSContext* mCx;
|
||||||
|
PromiseArray mUnhandledRejections;
|
||||||
|
};
|
||||||
|
|
||||||
OOMState mOutOfMemoryState;
|
OOMState mOutOfMemoryState;
|
||||||
OOMState mLargeAllocationFailureState;
|
OOMState mLargeAllocationFailureState;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue