mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 911216 - Part 24: Use promise rejection tracking to report unhandled rejections to the console. r=bz,Paolo
This commit is contained in:
parent
0f6f35176e
commit
b0d2250794
@ -940,6 +940,35 @@ Promise::MaybeRejectWithNull()
|
||||
MaybeSomething(JS::NullHandleValue, &Promise::MaybeReject);
|
||||
}
|
||||
|
||||
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
void
|
||||
Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
|
||||
{
|
||||
MOZ_ASSERT(!js::IsWrapper(aPromise));
|
||||
|
||||
MOZ_ASSERT(JS::GetPromiseState(aPromise) == JS::PromiseState::Rejected);
|
||||
|
||||
JS::Rooted<JS::Value> result(aCx, JS::GetPromiseResult(aPromise));
|
||||
|
||||
js::ErrorReport report(aCx);
|
||||
if (!report.init(aCx, result, js::ErrorReport::NoSideEffects)) {
|
||||
JS_ClearPendingException(aCx);
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
|
||||
bool isMainThread = MOZ_LIKELY(NS_IsMainThread());
|
||||
bool isChrome = isMainThread ? nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(aPromise))
|
||||
: GetCurrentThreadWorkerPrivate()->IsChromeWorker();
|
||||
nsGlobalWindow* win = isMainThread ? xpc::WindowGlobalOrNull(aPromise) : nullptr;
|
||||
xpcReport->Init(report.report(), report.message(), isChrome, win ? win->AsInner()->WindowID() : 0);
|
||||
|
||||
// Now post an event to do the real reporting async
|
||||
NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport));
|
||||
}
|
||||
#endif // defined(SPIDERMONKEY_PROMISE)
|
||||
|
||||
bool
|
||||
Promise::PerformMicroTaskCheckpoint()
|
||||
{
|
||||
|
@ -26,7 +26,7 @@
|
||||
// that need to be removed once clients have been put together
|
||||
// to take advantage of the new mechanism. New code should not
|
||||
// depend on code #ifdefed to this #define.
|
||||
#define DOM_PROMISE_DEPRECATED_REPORTING 1
|
||||
#define DOM_PROMISE_DEPRECATED_REPORTING !SPIDERMONKEY_PROMISE
|
||||
|
||||
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
|
||||
#include "mozilla/dom/workers/bindings/WorkerFeature.h"
|
||||
@ -112,6 +112,9 @@ public:
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
static already_AddRefed<Promise>
|
||||
Create(nsIGlobalObject* aGlobal, ErrorResult& aRv);
|
||||
|
||||
// Reports a rejected Promise by sending an error report.
|
||||
static void ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise);
|
||||
#else
|
||||
static already_AddRefed<Promise>
|
||||
Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
|
||||
|
@ -230,11 +230,8 @@ PromiseDebugging::Shutdown()
|
||||
/* static */ void
|
||||
PromiseDebugging::FlushUncaughtRejections()
|
||||
{
|
||||
// XXXbz figure out the plan
|
||||
#ifndef SPIDERMONKEY_PROMISE
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
FlushRejections::FlushSync();
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
}
|
||||
|
||||
#ifndef SPIDERMONKEY_PROMISE
|
||||
@ -342,7 +339,90 @@ PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef SPIDERMONKEY_PROMISE
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
|
||||
/* static */ void
|
||||
PromiseDebugging::AddUncaughtRejection(JS::HandleObject aPromise)
|
||||
{
|
||||
// This might OOM, but won't set a pending exception, so we'll just ignore it.
|
||||
if (CycleCollectedJSRuntime::Get()->mUncaughtRejections.append(aPromise)) {
|
||||
FlushRejections::DispatchNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/* void */ void
|
||||
PromiseDebugging::AddConsumedRejection(JS::HandleObject aPromise)
|
||||
{
|
||||
// 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 = CycleCollectedJSRuntime::Get()->mUncaughtRejections;
|
||||
for (size_t i = 0; i < uncaughtRejections.length(); i++) {
|
||||
if (uncaughtRejections[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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// This might OOM, but won't set a pending exception, so we'll just ignore it.
|
||||
if (CycleCollectedJSRuntime::Get()->mConsumedRejections.append(aPromise)) {
|
||||
FlushRejections::DispatchNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
{
|
||||
CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
|
||||
|
||||
auto& uncaught = storage->mUncaughtRejections;
|
||||
auto& consumed = storage->mConsumedRejections;
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
// Notify observers of uncaught Promise.
|
||||
auto& observers = storage->mUncaughtRejectionObservers;
|
||||
|
||||
for (size_t i = 0; i < uncaught.length(); i++) {
|
||||
JS::RootedObject promise(cx, uncaught[i]);
|
||||
// Filter out nullptrs which might've been added by
|
||||
// PromiseDebugging::AddConsumedRejection.
|
||||
if (!promise) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < observers.Length(); ++j) {
|
||||
RefPtr<UncaughtRejectionObserver> obs =
|
||||
static_cast<UncaughtRejectionObserver*>(observers[j].get());
|
||||
|
||||
IgnoredErrorResult err;
|
||||
obs->OnLeftUncaught(promise, err);
|
||||
}
|
||||
JSAutoCompartment ac(cx, promise);
|
||||
Promise::ReportRejectedPromise(cx, promise);
|
||||
}
|
||||
storage->mUncaughtRejections.clear();
|
||||
|
||||
// Notify observers of consumed Promise.
|
||||
|
||||
for (size_t i = 0; i < consumed.length(); i++) {
|
||||
JS::RootedObject promise(cx, consumed[i]);
|
||||
|
||||
for (size_t j = 0; j < observers.Length(); ++j) {
|
||||
RefPtr<UncaughtRejectionObserver> obs =
|
||||
static_cast<UncaughtRejectionObserver*>(observers[j].get());
|
||||
|
||||
IgnoredErrorResult err;
|
||||
obs->OnConsumed(promise, err);
|
||||
}
|
||||
}
|
||||
storage->mConsumedRejections.clear();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* static */ void
|
||||
PromiseDebugging::AddUncaughtRejection(Promise& aPromise)
|
||||
@ -372,13 +452,10 @@ PromiseDebugging::GetPromiseID(GlobalObject&,
|
||||
aID = sIDPrefix;
|
||||
aID.AppendInt(promiseID);
|
||||
}
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
/* static */ void
|
||||
PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
{
|
||||
// XXXbz talk to till about replacement for this stuff.
|
||||
#ifndef SPIDERMONKEY_PROMISE
|
||||
CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
|
||||
|
||||
// The Promise that have been left uncaught (rejected and last in
|
||||
@ -439,8 +516,8 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
|
||||
obs->OnConsumed(*promise, err); // Ignore errors
|
||||
}
|
||||
}
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
}
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
@ -26,6 +26,8 @@ class GlobalObject;
|
||||
class UncaughtRejectionObserver;
|
||||
class FlushRejections;
|
||||
|
||||
void TriggerFlushRejections();
|
||||
|
||||
class PromiseDebugging
|
||||
{
|
||||
public:
|
||||
@ -63,13 +65,18 @@ public:
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
// Mechanism for watching uncaught instances of Promise.
|
||||
// XXXbz figure out the plan
|
||||
static void AddUncaughtRejectionObserver(GlobalObject&,
|
||||
UncaughtRejectionObserver& aObserver);
|
||||
static bool RemoveUncaughtRejectionObserver(GlobalObject&,
|
||||
UncaughtRejectionObserver& aObserver);
|
||||
|
||||
#ifndef SPIDERMONKEY_PROMISE
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
// Mark a Promise as having been left uncaught at script completion.
|
||||
static void AddUncaughtRejection(JS::HandleObject);
|
||||
// Mark a Promise previously added with `AddUncaughtRejection` as
|
||||
// eventually consumed.
|
||||
static void AddConsumedRejection(JS::HandleObject);
|
||||
#else
|
||||
// Mark a Promise as having been left uncaught at script completion.
|
||||
static void AddUncaughtRejection(Promise&);
|
||||
// Mark a Promise previously added with `AddUncaughtRejection` as
|
||||
@ -78,7 +85,6 @@ public:
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
// Propagate the informations from AddUncaughtRejection
|
||||
// and AddConsumedRejection to observers.
|
||||
// XXXbz figure out the plan.
|
||||
static void FlushUncaughtRejections();
|
||||
|
||||
protected:
|
||||
|
@ -38,7 +38,11 @@ callback interface UncaughtRejectionObserver {
|
||||
* caught, i.e. if its `then` callback is called, `onConsumed` will
|
||||
* be called.
|
||||
*/
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
void onLeftUncaught(object p);
|
||||
#else
|
||||
void onLeftUncaught(Promise<any> p);
|
||||
#endif SPIDERMONKEY_PROMISE
|
||||
|
||||
/**
|
||||
* A Promise previously left uncaught is not the last in its
|
||||
@ -47,7 +51,11 @@ callback interface UncaughtRejectionObserver {
|
||||
* @param p A Promise that was previously left in uncaught state is
|
||||
* now caught, i.e. it is not the last in its chain anymore.
|
||||
*/
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
void onConsumed(object p);
|
||||
#else
|
||||
void onConsumed(Promise<any> p);
|
||||
#endif SPIDERMONKEY_PROMISE
|
||||
};
|
||||
|
||||
[ChromeOnly, Exposed=(Window,System)]
|
||||
|
@ -857,6 +857,9 @@ add_test({
|
||||
self.writeManifest(["test_simple_uncaught_rejection.js"])
|
||||
|
||||
self.assertTestResult(False)
|
||||
self.assertInLog(TEST_FAIL_STRING)
|
||||
self.assertInLog("test_simple_uncaught_rejection.js:3:3")
|
||||
self.assertInLog("Test rejection.")
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
@ -869,6 +872,9 @@ add_test({
|
||||
self.writeManifest(["test_simple_uncaught_rejection_jsm.js"])
|
||||
|
||||
self.assertTestResult(False)
|
||||
self.assertInLog(TEST_FAIL_STRING)
|
||||
self.assertInLog("test_simple_uncaught_rejection_jsm.js:4:16")
|
||||
self.assertInLog("Test rejection.")
|
||||
self.assertEquals(1, self.x.testCount)
|
||||
self.assertEquals(0, self.x.passCount)
|
||||
self.assertEquals(1, self.x.failCount)
|
||||
|
@ -69,6 +69,7 @@
|
||||
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseBinding.h"
|
||||
#include "mozilla/dom/PromiseDebugging.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "jsprf.h"
|
||||
#include "js/Debug.h"
|
||||
@ -471,6 +472,11 @@ CycleCollectedJSRuntime::~CycleCollectedJSRuntime()
|
||||
MOZ_ASSERT(mDebuggerPromiseMicroTaskQueue.empty());
|
||||
MOZ_ASSERT(mPromiseMicroTaskQueue.empty());
|
||||
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
mUncaughtRejections.reset();
|
||||
mConsumedRejections.reset();
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
JS_DestroyRuntime(mJSRuntime);
|
||||
mJSRuntime = nullptr;
|
||||
nsCycleCollector_forgetJSRuntime();
|
||||
@ -540,6 +546,9 @@ CycleCollectedJSRuntime::Initialize(JSRuntime* aParentRuntime,
|
||||
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
JS::SetEnqueuePromiseJobCallback(mJSRuntime, EnqueuePromiseJobCallback, this);
|
||||
JS::SetPromiseRejectionTrackerCallback(mJSRuntime, PromiseRejectionTrackerCallback, this);
|
||||
mUncaughtRejections.init(mJSRuntime, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
|
||||
mConsumedRejections.init(mJSRuntime, JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>(js::SystemAllocPolicy()));
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
JS::dbg::SetDebuggerMallocSizeOf(mJSRuntime, moz_malloc_size_of);
|
||||
@ -948,6 +957,28 @@ CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx,
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
/* static */
|
||||
void
|
||||
CycleCollectedJSRuntime::PromiseRejectionTrackerCallback(JSContext* aCx,
|
||||
JS::HandleObject aPromise,
|
||||
PromiseRejectionHandlingState state,
|
||||
void* aData)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
|
||||
#endif // DEBUG
|
||||
MOZ_ASSERT(JS_GetRuntime(aCx) == self->Runtime());
|
||||
MOZ_ASSERT(Get() == self);
|
||||
|
||||
if (state == PromiseRejectionHandlingState::Unhandled) {
|
||||
PromiseDebugging::AddUncaughtRejection(aPromise);
|
||||
} else {
|
||||
PromiseDebugging::AddConsumedRejection(aPromise);
|
||||
}
|
||||
}
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
struct JsGcTracer : public TraceCallbacks
|
||||
{
|
||||
virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
|
||||
|
@ -221,6 +221,12 @@ private:
|
||||
JS::HandleObject aJob,
|
||||
JS::HandleObject aAllocationSite,
|
||||
void* aData);
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
static void PromiseRejectionTrackerCallback(JSContext* aCx,
|
||||
JS::HandleObject aPromise,
|
||||
PromiseRejectionHandlingState state,
|
||||
void* aData);
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
|
||||
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
|
||||
void TraceNativeGrayRoots(JSTracer* aTracer);
|
||||
@ -362,11 +368,24 @@ public:
|
||||
|
||||
// Storage for watching rejected promises waiting for some client to
|
||||
// consume their rejection.
|
||||
#ifdef SPIDERMONKEY_PROMISE
|
||||
// Promises in this list have been rejected in the last turn of the
|
||||
// event loop without the rejection being handled.
|
||||
// Note that this can contain nullptrs in place of promises removed because
|
||||
// they're consumed before it'd be reported.
|
||||
JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mUncaughtRejections;
|
||||
|
||||
// Promises in this list have previously been reported as rejected
|
||||
// (because they were in the above list), but the rejection was handled
|
||||
// in the last turn of the event loop.
|
||||
JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mConsumedRejections;
|
||||
#else
|
||||
// We store values as `nsISupports` to avoid adding compile-time dependencies
|
||||
// from xpcom to dom/promise, but they can really only have a single concrete
|
||||
// type.
|
||||
nsTArray<nsCOMPtr<nsISupports /* Promise */>> mUncaughtRejections;
|
||||
nsTArray<nsCOMPtr<nsISupports /* Promise */ >> mConsumedRejections;
|
||||
#endif // SPIDERMONKEY_PROMISE
|
||||
nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */ >> mUncaughtRejectionObservers;
|
||||
|
||||
private:
|
||||
|
Loading…
Reference in New Issue
Block a user