Bug 1083950. Add a way to get the promises that depend on a given promise via PromiseDebugging. r=nsm

This commit is contained in:
Boris Zbarsky 2014-10-19 22:27:36 -04:00
parent aeef299e3c
commit 0d9ab82c4b
10 changed files with 140 additions and 1 deletions

View File

@ -1226,6 +1226,32 @@ Promise::CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget)
return true;
}
void
Promise::GetDependentPromises(nsTArray<nsRefPtr<Promise>>& aPromises)
{
// We want to return promises that correspond to then() calls, Promise.all()
// calls, and Promise.race() calls.
//
// For the then() case, we have both resolve and reject callbacks that know
// what the next promise is.
//
// For the race() case, likewise.
//
// For the all() case, our reject callback knows what the next promise is, but
// our resolve callback just knows it needs to notify some
// PromiseNativeHandler, which itself only has an indirect relationship to the
// next promise.
//
// So we walk over our _reject_ callbacks and ask each of them what promise
// its dependent promise is.
for (size_t i = 0; i < mRejectCallbacks.Length(); ++i) {
Promise* p = mRejectCallbacks[i]->GetDependentPromise();
if (p) {
aPromises.AppendElement(p);
}
}
}
// A WorkerRunnable to resolve/reject the Promise on the worker thread.
class PromiseWorkerProxyRunnable : public workers::WorkerRunnable

View File

@ -182,6 +182,8 @@ protected:
return mResolvePending;
}
void GetDependentPromises(nsTArray<nsRefPtr<Promise>>& aPromises);
private:
friend class PromiseDebugging;

View File

@ -29,6 +29,10 @@ public:
virtual void Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) = 0;
// Return the Promise that this callback will end up resolving or
// rejecting, if any.
virtual Promise* GetDependentPromise() = 0;
enum Task {
Resolve,
Reject
@ -53,6 +57,11 @@ public:
void Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
Promise* GetDependentPromise() MOZ_OVERRIDE
{
return mNextPromise;
}
WrapperPromiseCallback(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
AnyCallback* aCallback);
@ -76,6 +85,11 @@ public:
void Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
Promise* GetDependentPromise() MOZ_OVERRIDE
{
return mPromise;
}
ResolvePromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal);
private:
@ -97,6 +111,11 @@ public:
void Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
Promise* GetDependentPromise() MOZ_OVERRIDE
{
return mPromise;
}
RejectPromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal);
private:
@ -117,6 +136,11 @@ public:
void Call(JSContext* aCx,
JS::Handle<JS::Value> aValue) MOZ_OVERRIDE;
Promise* GetDependentPromise() MOZ_OVERRIDE
{
return nullptr;
}
NativePromiseCallback(PromiseNativeHandler* aHandler,
Promise::PromiseState aState);

View File

@ -57,5 +57,12 @@ PromiseDebugging::GetFullfillmentStack(GlobalObject&, Promise& aPromise,
aStack.set(aPromise.mFullfillmentStack);
}
/* static */ void
PromiseDebugging::GetDependentPromises(GlobalObject&, Promise& aPromise,
nsTArray<nsRefPtr<Promise>>& aPromises)
{
aPromise.GetDependentPromises(aPromises);
}
} // namespace dom
} // namespace mozilla

View File

@ -8,6 +8,8 @@
#define mozilla_dom_PromiseDebugging_h
#include "js/TypeDecls.h"
#include "nsTArray.h"
#include "nsRefPtr.h"
namespace mozilla {
namespace dom {
@ -28,6 +30,8 @@ public:
JS::MutableHandle<JSObject*> aStack);
static void GetFullfillmentStack(GlobalObject&, Promise& aPromise,
JS::MutableHandle<JSObject*> aStack);
static void GetDependentPromises(GlobalObject&, Promise& aPromise,
nsTArray<nsRefPtr<Promise>>& aPromises);
};
} // namespace dom

View File

@ -30,3 +30,5 @@ LOCAL_INCLUDES += [
FINAL_LIBRARY = 'xul'
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']

View File

@ -0,0 +1,3 @@
[DEFAULT]
[test_dependentPromises.html]

View File

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1083950
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1083950</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 1083950 **/
var p = new Promise(() => {});
p.name = "p";
var q = p.then();
q.name = "q";
var r = p.then(null, () => {});
r.name = "r";
var s = Promise.all([p, q]);
s.name = "s";
var t = Promise.race([r, s]);
t.name = "t";
function getDependentNames(promise) {
return PromiseDebugging.getDependentPromises(promise).map((p) => p.name);
}
function arraysEqual(arr1, arr2, msg) {
is(arr1.length, arr2.length, msg + ": length");
for (var i = 0; i < arr1.length; ++i) {
is(arr1[i], arr2[i], msg + ": [" + i + "]");
}
}
arraysEqual(getDependentNames(p), ["q", "r", "s"], "deps for p");
arraysEqual(getDependentNames(q), ["s"], "deps for q");
arraysEqual(getDependentNames(r), ["t"], "deps for r");
arraysEqual(getDependentNames(s), ["t"], "deps for s");
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1083950">Mozilla Bug 1083950</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -17,7 +17,7 @@ callback AnyCallback = any (any value);
// REMOVE THE RELEVANT ENTRY FROM test_interfaces.html WHEN THIS IS IMPLEMENTED IN JS.
[Constructor(PromiseInit init),
Exposed=(Window,Worker)]
Exposed=(Window,Worker,System)]
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
// TODO bug 875289 - static Promise fulfill(any value);

View File

@ -37,4 +37,22 @@ interface PromiseDebugging {
* promise has not been fulfilled or was not fulfilled from script.
*/
static object? getFullfillmentStack(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.
*
* Once a promise is settled, it will generally notify its dependent promises
* and forget about them, so this is most useful on unsettled promises.
*
* Note that this function only returns the promises that directly depend on
* p. It does not recursively return promises that depend on promises that
* depend on p.
*/
static sequence<Promise<any>> getDependentPromises(Promise<any> p);
};