--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -9,43 +9,35 @@
#include "jsfriendapi.h"
#include "js/Debug.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/MediaStreamError.h"
-#include "mozilla/Atomics.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/Preferences.h"
#include "PromiseCallback.h"
-#include "PromiseDebugging.h"
#include "PromiseNativeHandler.h"
#include "PromiseWorkerProxy.h"
#include "nsContentUtils.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "nsJSPrincipals.h"
#include "nsJSUtils.h"
#include "nsPIDOMWindow.h"
#include "nsJSEnvironment.h"
#include "nsIScriptObjectPrincipal.h"
#include "xpcpublic.h"
#include "nsGlobalWindow.h"
namespace mozilla {
namespace dom {
-namespace {
-// Generator used by Promise::GetID.
-Atomic<uintptr_t> gIDGenerator(0);
-}
-
-
using namespace workers;
NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
// This class processes the promise's callbacks with promise's result.
class PromiseCallbackTask MOZ_FINAL : public nsRunnable
{
public:
@@ -248,21 +240,17 @@ private:
NS_DECL_OWNINGTHREAD;
};
// Promise
NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
tmp->MaybeReportRejectedOnce();
-#else
- tmp->mResult = JS::UndefinedValue();
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
@@ -289,37 +277,29 @@ NS_INTERFACE_MAP_END
Promise::Promise(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal)
, mResult(JS::UndefinedValue())
, mAllocationStack(nullptr)
, mRejectionStack(nullptr)
, mFullfillmentStack(nullptr)
, mState(Pending)
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
, mHadRejectCallback(false)
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
- , mTaskPending(false)
, mResolvePending(false)
- , mIsLastInChain(true)
- , mWasNotifiedAsUncaught(false)
- , mID(0)
{
MOZ_ASSERT(mGlobal);
mozilla::HoldJSObjects(this);
mCreationTimestamp = TimeStamp::Now();
}
Promise::~Promise()
{
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
MaybeReportRejectedOnce();
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
mozilla::DropJSObjects(this);
}
JSObject*
Promise::WrapObject(JSContext* aCx)
{
return PromiseBinding::Wrap(aCx, this);
}
@@ -950,36 +930,27 @@ Promise::AppendNativeHandler(PromiseNati
AppendCallbacks(resolveCb, rejectCb);
}
void
Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
PromiseCallback* aRejectCallback)
{
- MOZ_ASSERT(aResolveCallback);
- MOZ_ASSERT(aRejectCallback);
-
- if (mIsLastInChain && mState == PromiseState::Rejected) {
- // This rejection is now consumed.
- PromiseDebugging::AddConsumedRejection(*this);
- // Note that we may not have had the opportunity to call
- // RunResolveTask() yet, so we may never have called
- // `PromiseDebugging:AddUncaughtRejection`.
+ if (aResolveCallback) {
+ mResolveCallbacks.AppendElement(aResolveCallback);
}
- mIsLastInChain = false;
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
- // Now that there is a callback, we don't need to report anymore.
- mHadRejectCallback = true;
- RemoveFeature();
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+ if (aRejectCallback) {
+ mHadRejectCallback = true;
+ mRejectCallbacks.AppendElement(aRejectCallback);
- mResolveCallbacks.AppendElement(aResolveCallback);
- mRejectCallbacks.AppendElement(aRejectCallback);
+ // Now that there is a callback, we don't need to report anymore.
+ RemoveFeature();
+ }
// If promise's state is fulfilled, queue a task to process our fulfill
// callbacks with promise's result. If promise's state is rejected, queue a
// task to process our reject callbacks with promise's result.
if (mState != Pending) {
EnqueueCallbackTasks();
}
}
@@ -1022,17 +993,16 @@ Promise::DispatchToMicroTask(nsIRunnable
CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue =
runtime->GetPromiseMicroTaskQueue();
microtaskQueue.AppendElement(aRunnable);
}
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
Promise::MaybeReportRejected()
{
if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
return;
}
AutoJSAPI jsapi;
@@ -1067,17 +1037,16 @@ Promise::MaybeReportRejected()
// Now post an event to do the real reporting async
// Since Promises preserve their wrapper, it is essential to nsRefPtr<> the
// AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
// will leak. See Bug 958684.
nsRefPtr<AsyncErrorReporter> r =
new AsyncErrorReporter(CycleCollectedJSRuntime::Get()->Runtime(), xpcReport);
NS_DispatchToMainThread(r);
}
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
Promise::MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
if (mResolvePending) {
return;
}
@@ -1157,26 +1126,16 @@ Promise::Settle(JS::Handle<JS::Value> aV
AutoJSAPI jsapi;
jsapi.Init();
JSContext* cx = jsapi.cx();
JS::RootedObject wrapper(cx, GetWrapper());
MOZ_ASSERT(wrapper); // We preserved it
JSAutoCompartment ac(cx, wrapper);
JS::dbg::onPromiseSettled(cx, wrapper);
- if (aState == PromiseState::Rejected &&
- mIsLastInChain) {
- // The Promise has just been rejected, and it is last in chain.
- // We need to inform PromiseDebugging.
- // If the Promise is eventually not the last in chain anymore,
- // we will need to inform PromiseDebugging again.
- PromiseDebugging::AddUncaughtRejection(*this);
- }
-
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
// If the Promise was rejected, and there is no reject handler already setup,
// watch for thread shutdown.
if (aState == PromiseState::Rejected &&
!mHadRejectCallback &&
!NS_IsMainThread()) {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
@@ -1185,17 +1144,16 @@ Promise::Settle(JS::Handle<JS::Value> aV
if (NS_WARN_IF(!worker->AddFeature(worker->GetJSContext(), mFeature))) {
// To avoid a false RemoveFeature().
mFeature = nullptr;
// Worker is shutting down, report rejection immediately since it is
// unlikely that reject callbacks will be added after this point.
MaybeReportRejectedOnce();
}
}
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
EnqueueCallbackTasks();
}
void
Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
PromiseState aState)
{
@@ -1220,17 +1178,16 @@ Promise::EnqueueCallbackTasks()
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
nsRefPtr<PromiseCallbackTask> task =
new PromiseCallbackTask(this, callbacks[i], mResult);
DispatchToMicroTask(task);
}
}
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
Promise::RemoveFeature()
{
if (mFeature) {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->RemoveFeature(worker->GetJSContext(), mFeature);
mFeature = nullptr;
@@ -1240,17 +1197,16 @@ Promise::RemoveFeature()
bool
PromiseReportRejectFeature::Notify(JSContext* aCx, workers::Status aStatus)
{
MOZ_ASSERT(aStatus > workers::Running);
mPromise->MaybeReportRejectedOnce();
// After this point, `this` has been deleted by RemoveFeature!
return true;
}
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
bool
Promise::CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget)
{
JS::Rooted<JSObject*> stack(aCx);
if (!JS::CaptureCurrentStack(aCx, &stack)) {
return false;
}
@@ -1483,18 +1439,10 @@ template<>
void Promise::MaybeRejectBrokenly(const nsRefPtr<DOMError>& aArg) {
MaybeSomething(aArg, &Promise::MaybeReject);
}
template<>
void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
MaybeSomething(aArg, &Promise::MaybeReject);
}
-uint64_t
-Promise::GetID() {
- if (mID != 0) {
- return mID;
- }
- return mID = ++gIDGenerator;
-}
-
} // namespace dom
} // namespace mozilla
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -17,65 +17,54 @@
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/WeakPtr.h"
#include "nsWrapperCache.h"
#include "nsAutoPtr.h"
#include "js/TypeDecls.h"
#include "mozilla/dom/workers/bindings/WorkerFeature.h"
-// Bug 1083361 introduces a new mechanism for tracking uncaught
-// rejections. This #define serves to track down the parts of code
-// 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
-
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class AnyCallback;
class DOMError;
class MediaStreamError;
class PromiseCallback;
class PromiseInit;
class PromiseNativeHandler;
class PromiseDebugging;
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
class Promise;
class PromiseReportRejectFeature : public workers::WorkerFeature
{
// The Promise that owns this feature.
Promise* mPromise;
public:
explicit PromiseReportRejectFeature(Promise* aPromise)
: mPromise(aPromise)
{
MOZ_ASSERT(mPromise);
}
virtual bool
Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE;
};
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
class Promise : public nsISupports,
public nsWrapperCache,
public SupportsWeakPtr<Promise>
{
friend class NativePromiseCallback;
friend class PromiseResolverTask;
friend class PromiseTask;
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
friend class PromiseReportRejectFeature;
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
friend class PromiseWorkerProxy;
friend class PromiseWorkerProxyRunnable;
friend class RejectPromiseCallback;
friend class ResolvePromiseCallback;
friend class ThenableResolverTask;
friend class WrapperPromiseCallback;
public:
@@ -177,19 +166,16 @@ public:
const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
static already_AddRefed<Promise>
Race(const GlobalObject& aGlobal,
const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
void AppendNativeHandler(PromiseNativeHandler* aRunnable);
- // Return a unique-to-the-process identifier for this Promise.
- uint64_t GetID();
-
protected:
// Do NOT call this unless you're Promise::Create. I wish we could enforce
// that from inside this class too, somehow.
explicit Promise(nsIGlobalObject* aGlobal);
virtual ~Promise();
// Queue an async microtask to current main or worker thread.
@@ -207,31 +193,16 @@ protected:
bool IsPending()
{
return mResolvePending;
}
void GetDependentPromises(nsTArray<nsRefPtr<Promise>>& aPromises);
- bool IsLastInChain() const
- {
- return mIsLastInChain;
- }
-
- void SetNotifiedAsUncaught()
- {
- mWasNotifiedAsUncaught = true;
- }
-
- bool WasNotifiedAsUncaught() const
- {
- return mWasNotifiedAsUncaught;
- }
-
private:
friend class PromiseDebugging;
enum PromiseState {
Pending,
Resolved,
Rejected
};
@@ -255,28 +226,26 @@ private:
void EnqueueCallbackTasks();
void Settle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
void MaybeSettle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
void AppendCallbacks(PromiseCallback* aResolveCallback,
PromiseCallback* aRejectCallback);
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
// If we have been rejected and our mResult is a JS exception,
// report it to the error console.
// Use MaybeReportRejectedOnce() for actual calls.
void MaybeReportRejected();
void MaybeReportRejectedOnce() {
MaybeReportRejected();
RemoveFeature();
mResult = JS::UndefinedValue();
}
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
void MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue);
void MaybeRejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue);
void ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue);
@@ -315,19 +284,17 @@ private:
CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise,
int32_t aTask);
static JSObject*
CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask);
void HandleException(JSContext* aCx);
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void RemoveFeature();
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
// Capture the current stack and store it in aTarget. If false is
// returned, an exception is presumably pending on aCx.
bool CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget);
nsRefPtr<nsIGlobalObject> mGlobal;
nsTArray<nsRefPtr<PromiseCallback> > mResolveCallbacks;
@@ -343,46 +310,29 @@ private:
// have a rejection stack.
JS::Heap<JSObject*> mRejectionStack;
// mFullfillmentStack is only set when the promise is fulfilled directly from
// script, by calling Promise.resolve() or the fulfillment callback we pass to
// the PromiseInit function. Promises that are fulfilled internally do not
// have a fulfillment stack.
JS::Heap<JSObject*> mFullfillmentStack;
PromiseState mState;
+ bool mHadRejectCallback;
-#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
- bool mHadRejectCallback;
+ bool mResolvePending;
// If a rejected promise on a worker has no reject callbacks attached, it
// needs to know when the worker is shutting down, to report the error on the
// console before the worker's context is deleted. This feature is used for
// that purpose.
nsAutoPtr<PromiseReportRejectFeature> mFeature;
-#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
-
- bool mTaskPending;
- bool mResolvePending;
-
- // `true` if this Promise is the last in the chain, or `false` if
- // another Promise has been created from this one by a call to
- // `then`, `all`, `race`, etc.
- bool mIsLastInChain;
-
- // `true` if PromiseDebugging has already notified at least one observer that
- // this promise was left uncaught, `false` otherwise.
- bool mWasNotifiedAsUncaught;
// The time when this promise was created.
TimeStamp mCreationTimestamp;
// The time when this promise transitioned out of the pending state.
TimeStamp mSettlementTimestamp;
-
- // Once `GetID()` has been called, a unique-to-the-process identifier for this
- // promise. Until then, `0`.
- uint64_t mID;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_Promise_h
--- a/dom/promise/PromiseDebugging.cpp
+++ b/dom/promise/PromiseDebugging.cpp
@@ -1,73 +1,26 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
-#include "js/Value.h"
-#include "nsThreadUtils.h"
+#include "mozilla/dom/PromiseDebugging.h"
-#include "mozilla/CycleCollectedJSRuntime.h"
-#include "mozilla/ThreadLocal.h"
+#include "js/Value.h"
+
#include "mozilla/TimeStamp.h"
-
#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/PromiseDebugging.h"
#include "mozilla/dom/PromiseDebuggingBinding.h"
namespace mozilla {
namespace dom {
-namespace {
-
-class FlushRejections: public nsCancelableRunnable
-{
-public:
- static void Init() {
- if (!sDispatched.init()) {
- MOZ_CRASH("Could not initialize FlushRejections::sDispatched");
- }
- sDispatched.set(false);
- }
- static void DispatchNeeded() {
- if (sDispatched.get()) {
- // An instance of `FlushRejections` has already been dispatched
- // and not run yet. No need to dispatch another one.
- return;
- }
- sDispatched.set(true);
- NS_DispatchToCurrentThread(new FlushRejections());
- }
- nsresult Run()
- {
- sDispatched.set(false);
-
- // Call the callbacks if necessary.
- // Note that these callbacks may in turn cause Promise to turn
- // uncaught or consumed. Since `sDispatched` is `false`,
- // `FlushRejections` will be called once again, on an ulterior
- // tick.
- PromiseDebugging::FlushUncaughtRejections();
- return NS_OK;
- }
-private:
- // `true` if an instance of `FlushRejections` is currently dispatched
- // and has not been executed yet.
- static ThreadLocal<bool> sDispatched;
-};
-
-/* static */ ThreadLocal<bool>
-FlushRejections::sDispatched;
-
-} // namespace
-
/* static */ void
PromiseDebugging::GetState(GlobalObject&, Promise& aPromise,
PromiseDebuggingStateHolder& aState)
{
switch (aPromise.mState) {
case Promise::Pending:
aState.mState = PromiseDebuggingState::Pending;
break;
@@ -79,40 +32,16 @@ PromiseDebugging::GetState(GlobalObject&
case Promise::Rejected:
aState.mState = PromiseDebuggingState::Rejected;
JS::ExposeValueToActiveJS(aPromise.mResult);
aState.mReason = aPromise.mResult;
break;
}
}
-/*static */ nsString
-PromiseDebugging::sIDPrefix;
-
-/* static */ void
-PromiseDebugging::Init()
-{
- FlushRejections::Init();
-
- // Generate a prefix for identifiers: "PromiseDebugging.$processid."
- sIDPrefix = NS_LITERAL_STRING("PromiseDebugging.");
- if (XRE_GetProcessType() == GeckoProcessType_Content) {
- sIDPrefix.AppendInt(ContentChild::GetSingleton()->GetID());
- sIDPrefix.Append('.');
- } else {
- sIDPrefix.AppendLiteral("0.");
- }
-}
-
-/* static */ void
-PromiseDebugging::Shutdown()
-{
- sIDPrefix.SetIsVoid(true);
-}
-
/* static */ void
PromiseDebugging::GetAllocationStack(GlobalObject&, Promise& aPromise,
JS::MutableHandle<JSObject*> aStack)
{
aStack.set(aPromise.mAllocationStack);
}
/* static */ void
@@ -149,112 +78,10 @@ PromiseDebugging::GetTimeToSettle(Global
if (aPromise.mState == Promise::Pending) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return 0;
}
return (aPromise.mSettlementTimestamp -
aPromise.mCreationTimestamp).ToMilliseconds();
}
-/* static */ void
-PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
- UncaughtRejectionObserver& aObserver)
-{
- CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
- nsTArray<nsRefPtr<UncaughtRejectionObserver>>& observers = storage->mUncaughtRejectionObservers;
- observers.AppendElement(&aObserver);
-}
-
-/* static */ void
-PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
- UncaughtRejectionObserver& aObserver)
-{
- CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
- nsTArray<nsRefPtr<UncaughtRejectionObserver>>& observers = storage->mUncaughtRejectionObservers;
- for (size_t i = 0; i < observers.Length(); ++i) {
- if (*observers[i] == aObserver) {
- observers.RemoveElementAt(i);
- return;
- }
- }
-}
-
-/* static */ void
-PromiseDebugging::AddUncaughtRejection(Promise& aPromise)
-{
- CycleCollectedJSRuntime::Get()->mUncaughtRejections.AppendElement(&aPromise);
- FlushRejections::DispatchNeeded();
-}
-
-/* void */ void
-PromiseDebugging::AddConsumedRejection(Promise& aPromise)
-{
- CycleCollectedJSRuntime::Get()->mConsumedRejections.AppendElement(&aPromise);
- FlushRejections::DispatchNeeded();
-}
-
-/* static */ void
-PromiseDebugging::GetPromiseID(GlobalObject&,
- Promise& aPromise,
- nsString& aID)
-{
- uint64_t promiseID = aPromise.GetID();
- aID = sIDPrefix;
- aID.AppendInt(promiseID);
-}
-
-/* static */ void
-PromiseDebugging::FlushUncaughtRejections()
-{
- CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
-
- // The Promise that have been left uncaught (rejected and last in
- // their chain) since the last call to this function.
- nsTArray<nsRefPtr<Promise>> uncaught;
- storage->mUncaughtRejections.SwapElements(uncaught);
-
- // The Promise that have been left uncaught at some point, but that
- // have eventually had their `then` method called.
- nsTArray<nsRefPtr<Promise>> consumed;
- storage->mConsumedRejections.SwapElements(consumed);
-
- nsTArray<nsRefPtr<UncaughtRejectionObserver>>& observers = storage->mUncaughtRejectionObservers;
-
- // Notify observers of uncaught Promise.
-
- for (size_t i = 0; i < uncaught.Length(); ++i) {
- nsRefPtr<Promise> promise = uncaught[i];
- if (!promise->IsLastInChain()) {
- // This promise is not the last in the chain anymore,
- // so the error has been caught at some point.
- continue;
- }
-
- // For the moment, the Promise is still at the end of the
- // chain. Let's inform observers, so that they may decide whether
- // to report it.
- for (size_t j = 0; j < observers.Length(); ++j) {
- ErrorResult rv;
- observers[j]->OnLeftUncaught(*promise, rv);
- // Ignore errors
- }
-
- promise->SetNotifiedAsUncaught();
- }
-
- // Notify observers of consumed Promise.
-
- for (size_t i = 0; i < consumed.Length(); ++i) {
- nsRefPtr<Promise> promise = consumed[i];
- if (!promise->WasNotifiedAsUncaught()) {
- continue;
- }
-
- MOZ_ASSERT(!promise->IsLastInChain());
- for (size_t j = 0; j < observers.Length(); ++j) {
- ErrorResult rv;
- observers[j]->OnConsumed(*promise, rv); // Ignore errors
- }
- }
-}
-
} // namespace dom
} // namespace mozilla
--- a/dom/promise/PromiseDebugging.h
+++ b/dom/promise/PromiseDebugging.h
@@ -15,62 +15,32 @@ namespace mozilla {
class ErrorResult;
namespace dom {
class Promise;
struct PromiseDebuggingStateHolder;
class GlobalObject;
-class UncaughtRejectionObserver;
class PromiseDebugging
{
public:
- static void Init();
- static void Shutdown();
-
static void GetState(GlobalObject&, Promise& aPromise,
PromiseDebuggingStateHolder& aState);
static void GetAllocationStack(GlobalObject&, Promise& aPromise,
JS::MutableHandle<JSObject*> aStack);
static void GetRejectionStack(GlobalObject&, Promise& aPromise,
JS::MutableHandle<JSObject*> aStack);
static void GetFullfillmentStack(GlobalObject&, Promise& aPromise,
JS::MutableHandle<JSObject*> aStack);
static void GetDependentPromises(GlobalObject&, Promise& aPromise,
nsTArray<nsRefPtr<Promise>>& aPromises);
static double GetPromiseLifetime(GlobalObject&, Promise& aPromise);
static double GetTimeToSettle(GlobalObject&, Promise& aPromise,
ErrorResult& aRv);
-
- static void GetPromiseID(GlobalObject&, Promise&, nsString&);
-
- // Mechanism for watching uncaught instances of Promise.
- static void AddUncaughtRejectionObserver(GlobalObject&,
- UncaughtRejectionObserver& aObserver);
- static void RemoveUncaughtRejectionObserver(GlobalObject&,
- UncaughtRejectionObserver& aObserver);
-
- // Mark a Promise as having been left uncaught at script completion.
- static void AddUncaughtRejection(Promise&);
- // Mark a Promise previously added with `AddUncaughtRejection` as
- // eventually consumed.
- static void AddConsumedRejection(Promise&);
- // Propagate the informations from AddUncaughtRejection
- // and AddConsumedRejection to observers.
- static void FlushUncaughtRejections();
-private:
- // Identity of the process.
- // This property is:
- // - set during initialization of the layout module,
- // prior to any Worker using it;
- // - read by both the main thread and the Workers;
- // - unset during shutdown of the layout module,
- // after any Worker has been shutdown.
- static nsString sIDPrefix;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_PromiseDebugging_h
--- a/dom/promise/moz.build
+++ b/dom/promise/moz.build
@@ -19,20 +19,16 @@ UNIFIED_SOURCES += [
'PromiseCallback.cpp',
'PromiseDebugging.cpp'
]
FAIL_ON_WARNINGS = True
LOCAL_INCLUDES += [
'../base',
- '../ipc',
'../workers',
]
-include('/ipc/chromium/chromium-config.mozbuild')
-
FINAL_LIBRARY = 'xul'
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
-BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
deleted file mode 100644
--- a/dom/promise/tests/browser.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-# 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/.
-
-[DEFAULT]
-
-[browser_monitorUncaught.js]
\ No newline at end of file
deleted file mode 100644
--- a/dom/promise/tests/browser_monitorUncaught.js
+++ /dev/null
@@ -1,262 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-add_task(function* test_globals() {
- Assert.equal(Promise.defer || undefined, undefined, "We are testing DOM Promise.");
- Assert.notEqual(PromiseDebugging, undefined, "PromiseDebugging is available.");
-});
-
-add_task(function* test_promiseID() {
- let p1 = new Promise(resolve => {});
- let p2 = new Promise(resolve => {});
- let p3 = p2.then(null, null);
- let promise = [p1, p2, p3];
-
- let identifiers = promise.map(PromiseDebugging.getPromiseID);
- info("Identifiers: " + JSON.stringify(identifiers));
- let idSet = new Set(identifiers);
- Assert.equal(idSet.size, identifiers.length,
- "PromiseDebugging.getPromiseID returns a distinct id per promise");
-
- let identifiers2 = promise.map(PromiseDebugging.getPromiseID);
- Assert.equal(JSON.stringify(identifiers),
- JSON.stringify(identifiers2),
- "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise");
-});
-
-add_task(function* test_observe_uncaught() {
- // The names of Promise instances
- let names = new Map();
-
- // The results for UncaughtPromiseObserver callbacks.
- let CallbackResults = function(name) {
- this.name = name;
- this.expected = new Set();
- this.observed = new Set();
- this.blocker = new Promise(resolve => this.resolve = resolve);
- };
- CallbackResults.prototype = {
- observe: function(promise) {
- info(this.name + " observing Promise " + names.get(promise));
- Assert.equal(PromiseDebugging.getState(promise).state, "rejected",
- this.name + " observed a rejected Promise");
- if (!this.expected.has(promise)) {
- Assert.ok(false,
- this.name + " observed a Promise that it expected to observe, " +
- names.get(promise) +
- " (" + PromiseDebugging.getPromiseID(promise) +
- ", " + PromiseDebugging.getAllocationStack(promise) + ")");
-
- }
- Assert.ok(this.expected.delete(promise),
- this.name + " observed a Promise that it expected to observe, " +
- names.get(promise) + " (" + PromiseDebugging.getPromiseID(promise) + ")");
- Assert.ok(!this.observed.has(promise),
- this.name + " observed a Promise that it has not observed yet");
- this.observed.add(promise);
- if (this.expected.size == 0) {
- this.resolve();
- } else {
- info(this.name + " is still waiting for " + this.expected.size + " observations:");
- info(JSON.stringify([names.get(x) for (x of this.expected.values())]));
- }
- },
- };
-
- let onLeftUncaught = new CallbackResults("onLeftUncaught");
- let onConsumed = new CallbackResults("onConsumed");
-
- let observer = {
- onLeftUncaught: function(promise, data) {
- onLeftUncaught.observe(promise);
- },
- onConsumed: function(promise) {
- onConsumed.observe(promise);
- },
- };
-
- let resolveLater = function(delay = 20) {
- return new Promise((resolve, reject) => setTimeout(resolve, delay));
- };
- let rejectLater = function(delay = 20) {
- return new Promise((resolve, reject) => setTimeout(reject, delay));
- };
- let makeSamples = function*() {
- yield {
- promise: Promise.resolve(0),
- name: "Promise.resolve",
- };
- yield {
- promise: Promise.resolve(resolve => resolve(0)),
- name: "Resolution callback",
- };
- yield {
- promise: Promise.resolve(0).then(null, null),
- name: "`then(null, null)`"
- };
- yield {
- promise: Promise.reject(0).then(null, () => {}),
- name: "Reject and catch immediately",
- };
- yield {
- promise: resolveLater(),
- name: "Resolve later",
- };
- yield {
- promise: Promise.reject("Simple rejection"),
- leftUncaught: true,
- consumed: false,
- name: "Promise.reject",
- };
-
- // Reject a promise now, consume it later.
- let p = Promise.reject("Reject now, consume later");
- setTimeout(() => p.then(null, () => {
- info("Consumed promise");
- }), 200);
- yield {
- promise: p,
- leftUncaught: true,
- consumed: true,
- name: "Reject now, consume later",
- };
-
- yield {
- promise: Promise.all([
- Promise.resolve("Promise.all"),
- rejectLater()
- ]),
- leftUncaught: true,
- name: "Rejecting through Promise.all"
- };
- yield {
- promise: Promise.race([
- resolveLater(500),
- Promise.reject(),
- ]),
- leftUncaught: true, // The rejection wins the race.
- name: "Rejecting through Promise.race",
- };
- yield {
- promise: Promise.race([
- Promise.resolve(),
- rejectLater(500)
- ]),
- leftUncaught: false, // The resolution wins the race.
- name: "Resolving through Promise.race",
- };
-
- let boom = new Error("`throw` in the constructor");
- yield {
- promise: new Promise(() => { throw boom; }),
- leftUncaught: true,
- name: "Throwing in the constructor",
- };
-
- let rejection = Promise.reject("`reject` during resolution");
- yield {
- promise: rejection,
- leftUncaught: false,
- consumed: false, // `rejection` is consumed immediately (see below)
- name: "Promise.reject, again",
- };
-
- yield {
- promise: new Promise(resolve => resolve(rejection)),
- leftUncaught: true,
- consumed: false,
- name: "Resolving with a rejected promise",
- };
-
- yield {
- promise: Promise.resolve(0).then(() => rejection),
- leftUncaught: true,
- consumed: false,
- name: "Returning a rejected promise from success handler",
- };
-
- yield {
- promise: Promise.resolve(0).then(() => { throw new Error(); }),
- leftUncaught: true,
- consumed: false,
- name: "Throwing during the call to the success callback",
- };
- };
- let samples = [];
- for (let s of makeSamples()) {
- samples.push(s);
- info("Promise '" + s.name + "' has id " + PromiseDebugging.getPromiseID(s.promise));
- }
-
- PromiseDebugging.addUncaughtRejectionObserver(observer);
-
- for (let s of samples) {
- names.set(s.promise, s.name);
- if (s.leftUncaught || false) {
- onLeftUncaught.expected.add(s.promise);
- }
- if (s.consumed || false) {
- onConsumed.expected.add(s.promise);
- }
- }
-
- info("Test setup, waiting for callbacks.");
- yield onLeftUncaught.blocker;
-
- info("All calls to onLeftUncaught are complete.");
- if (onConsumed.expected.size != 0) {
- info("onConsumed is still waiting for the following Promise:");
- info(JSON.stringify([names.get(x) for (x of onConsumed.expected.values())]));
- yield onConsumed.blocker;
- }
-
- info("All calls to onConsumed are complete.");
- PromiseDebugging.removeUncaughtRejectionObserver(observer);
-});
-
-
-add_task(function* test_uninstall_observer() {
- let Observer = function() {
- this.blocker = new Promise(resolve => this.resolve = resolve);
- this.active = true;
- };
- Observer.prototype = {
- set active(x) {
- this._active = x;
- if (x) {
- PromiseDebugging.addUncaughtRejectionObserver(this);
- } else {
- PromiseDebugging.removeUncaughtRejectionObserver(this);
- }
- },
- onLeftUncaught: function() {
- Assert.ok(this._active, "This observer is active.");
- this.resolve();
- },
- onConsumed: function() {
- Assert.ok(false, "We should not consume any Promise.");
- },
- };
-
- info("Adding an observer.");
- let deactivate = new Observer();
- Promise.reject("I am an uncaught rejection.");
- yield deactivate.blocker;
- Assert.ok(true, "The observer has observed an uncaught Promise.");
- deactivate.active = false;
- info("Removing the observer, it should not observe any further uncaught Promise.");
-
- info("Rejecting a Promise and waiting a little to give a chance to observers.");
- let wait = new Observer();
- Promise.reject("I am another uncaught rejection.");
- yield wait.blocker;
- yield new Promise(resolve => setTimeout(resolve, 100));
- // Normally, `deactivate` should not be notified of the uncaught rejection.
- wait.active = false;
-
-});
--- a/dom/webidl/PromiseDebugging.webidl
+++ b/dom/webidl/PromiseDebugging.webidl
@@ -9,52 +9,16 @@
dictionary PromiseDebuggingStateHolder {
PromiseDebuggingState state = "pending";
any value;
any reason;
};
enum PromiseDebuggingState { "pending", "fulfilled", "rejected" };
-/**
- * An observer for Promise that _may_ be leaking uncaught rejections.
- *
- * It is generally a programming error to leave a Promise rejected and
- * not consume its rejection. The information exposed by this
- * interface is designed to allow clients to track down such Promise,
- * i.e. Promise that are currently
- * - in `rejected` state;
- * - last of their chain.
- *
- * Note, however, that a promise in such a state at the end of a tick
- * may eventually be consumed in some ulterior tick. Implementers of
- * this interface are responsible for presenting the information
- * in a meaningful manner.
- */
-callback interface UncaughtRejectionObserver {
- /**
- * A Promise has been left in `rejected` state and is the
- * last in its chain.
- *
- * @param p A currently uncaught Promise. If `p` is is eventually
- * caught, i.e. if its `then` callback is called, `onConsumed` will
- * be called.
- */
- void onLeftUncaught(Promise<any> p);
-
- /**
- * A Promise previously left uncaught is not the last in its
- * chain anymore.
- *
- * @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.
- */
- void onConsumed(Promise<any> p);
-};
-
[ChromeOnly, Exposed=(Window,System)]
interface PromiseDebugging {
static PromiseDebuggingStateHolder getState(Promise<any> p);
/**
* Return the stack to the promise's allocation point. This can
* return null if the promise was not created from script.
*/
@@ -70,22 +34,16 @@ interface PromiseDebugging {
/**
* Return the stack to the promise's fulfillment point, if the
* fulfillment happened from script. This can return null if the
* promise has not been fulfilled or was not fulfilled from script.
*/
static object? getFullfillmentStack(Promise<any> p);
/**
- * Return an identifier for a promise. This identifier is guaranteed
- * to be unique to this instance of Firefox.
- */
- static DOMString getPromiseID(Promise<any> p);
-
- /**
* Get the promises directly depending on a given promise. These are:
*
* 1) Return values of then() calls on the promise
* 2) Return values of Promise.all() if the given promise was passed in as one
* of the arguments.
* 3) Return values of Promise.race() if the given promise was passed in as
* one of the arguments.
*
@@ -105,18 +63,9 @@ interface PromiseDebugging {
/*
* Get the number of milliseconds elapsed between the promise being created
* and being settled. Throws NS_ERROR_UNEXPECTED if the promise has not
* settled.
*/
[Throws]
static DOMHighResTimeStamp getTimeToSettle(Promise<any> p);
-
- /**
- * Watching uncaught rejections on the current thread.
- *
- * Adding an observer twice will cause it to be notified twice
- * of events.
- */
- static void addUncaughtRejectionObserver(UncaughtRejectionObserver o);
- static void removeUncaughtRejectionObserver(UncaughtRejectionObserver o);
};
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -62,17 +62,16 @@
#include "CacheObserver.h"
#include "DisplayItemClip.h"
#include "ActiveLayerTracker.h"
#include "CounterStyleManager.h"
#include "FrameLayerBuilder.h"
#include "AudioChannelService.h"
#include "mozilla/dom/DataStoreService.h"
-#include "mozilla/dom/PromiseDebugging.h"
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#include "nsXULContentUtils.h"
#include "nsXULPrototypeCache.h"
#include "nsXULTooltipListener.h"
#include "inDOMView.h"
@@ -296,18 +295,16 @@ nsLayoutStatics::Initialize()
CacheObserver::Init();
CounterStyleManager::InitializeBuiltinCounterStyles();
CameraPreferences::Initialize();
IMEStateManager::Init();
- PromiseDebugging::Init();
-
return NS_OK;
}
void
nsLayoutStatics::Shutdown()
{
// Don't need to shutdown nsWindowMemoryReporter, that will be done by the
// memory reporter manager.
@@ -429,11 +426,9 @@ nsLayoutStatics::Shutdown()
DisplayItemClip::Shutdown();
nsDocument::XPCOMShutdown();
CacheObserver::Shutdown();
CameraPreferences::Shutdown();
-
- PromiseDebugging::Shutdown();
}
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -1294,9 +1294,8 @@ CycleCollectedJSRuntime::OnOutOfMemory()
void
CycleCollectedJSRuntime::OnLargeAllocationFailure()
{
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reporting);
CustomLargeAllocationFailureCallback();
AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, OOMState::Reported);
}
-
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -11,20 +11,16 @@
#include "jsapi.h"
#include "nsCycleCollector.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsTArray.h"
-#include "mozilla/dom/PromiseDebugging.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/PromiseDebuggingBinding.h"
-
class nsCycleCollectionNoteRootCallback;
class nsIException;
class nsIRunnable;
namespace js {
struct Class;
}
@@ -290,22 +286,16 @@ public:
MOZ_ASSERT(mJSRuntime);
return mJSRuntime;
}
// Get the current thread's CycleCollectedJSRuntime. Returns null if there
// isn't one.
static CycleCollectedJSRuntime* Get();
- // Storage for watching rejected promises waiting for some client to
- // consume their rejection.
- nsTArray<nsRefPtr<dom::Promise>> mUncaughtRejections;
- nsTArray<nsRefPtr<dom::Promise>> mConsumedRejections;
- nsTArray<nsRefPtr<dom::UncaughtRejectionObserver>> mUncaughtRejectionObservers;
-
private:
JSGCThingParticipant mGCThingCycleCollectorGlobal;
JSZoneParticipant mJSZoneCycleCollectorGlobal;
JSRuntime* mJSRuntime;
nsDataHashtable<nsPtrHashKey<void>, nsScriptObjectTracer*> mJSHolders;