Bug 1148593 - Create async stack in callback objects. r=bz, r=fitzgen

--HG--
extra : rebase_source : f9b507d8f005dbca6f40f510ca41a0cbb03aebf9
This commit is contained in:
Tom Tromey 2015-07-24 07:01:00 -04:00
Родитель a37b7fa928
Коммит 9f3b16bf4c
23 изменённых файлов: 421 добавлений и 55 удалений

Просмотреть файл

@ -28,8 +28,10 @@ function* ifTestingSupported() {
isnot($(".call-item-stack", callItem.target), null,
"There should be a stack container available now for the draw call.");
is($all(".call-item-stack-fn", callItem.target).length, 4,
"There should be 4 functions on the stack for the draw call.");
// We may have more than 4 functions, depending on whether async
// stacks are available.
ok($all(".call-item-stack-fn", callItem.target).length >= 4,
"There should be at least 4 functions on the stack for the draw call.");
ok($all(".call-item-stack-fn-name", callItem.target)[0].getAttribute("value")
.includes("C()"),

Просмотреть файл

@ -29,8 +29,10 @@ function* ifTestingSupported() {
isnot($(".call-item-stack", callItem.target), null,
"There should be a stack container available now for the draw call.");
is($all(".call-item-stack-fn", callItem.target).length, 4,
"There should be 4 functions on the stack for the draw call.");
// We may have more than 4 functions, depending on whether async
// stacks are available.
ok($all(".call-item-stack-fn", callItem.target).length >= 4,
"There should be at least 4 functions on the stack for the draw call.");
let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-location", callItem.target));

Просмотреть файл

@ -40,8 +40,10 @@ function* ifTestingSupported() {
"There should be a stack container available now for the draw call.");
is($(".call-item-stack", callItem.target).hidden, false,
"The stack container should now be visible.");
is($all(".call-item-stack-fn", callItem.target).length, 4,
"There should be 4 functions on the stack for the draw call.");
// We may have more than 4 functions, depending on whether async
// stacks are available.
ok($all(".call-item-stack-fn", callItem.target).length >= 4,
"There should be at least 4 functions on the stack for the draw call.");
EventUtils.sendMouseEvent({ type: "dblclick" }, contents, window);
@ -53,8 +55,10 @@ function* ifTestingSupported() {
"There should still be a stack container available for the draw call.");
is($(".call-item-stack", callItem.target).hidden, true,
"The stack container should now be hidden.");
is($all(".call-item-stack-fn", callItem.target).length, 4,
"There should still be 4 functions on the stack for the draw call.");
// We may have more than 4 functions, depending on whether async
// stacks are available.
ok($all(".call-item-stack-fn", callItem.target).length >= 4,
"There should still be at least 4 functions on the stack for the draw call.");
yield teardown(panel);
finish();

Просмотреть файл

@ -94,7 +94,7 @@ if (Services.prefs.getBoolPref("javascript.options.asyncstack")) {
let frame = markers[0].endStack;
ok(frame.parent.asyncParent !== null, "Parent frame has async parent");
is(frame.parent.asyncParent.asyncCause, "Promise",
is(frame.parent.asyncParent.asyncCause, "promise callback",
"Async parent has correct cause");
is(frame.parent.asyncParent.functionDisplayName, "makePromise",
"Async parent has correct function name");

Просмотреть файл

@ -248,6 +248,8 @@ support-files =
[test_anonymousContent_insert.html]
[test_anonymousContent_manipulate_content.html]
[test_appname_override.html]
[test_async_setTimeout_stack.html]
[test_async_setTimeout_stack_across_globals.html]
[test_audioWindowUtils.html]
[test_audioNotification.html]
skip-if = buildapp == 'mulet'

Просмотреть файл

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1142577 - Async stacks for setTimeout</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
<pre id="stack"></pre>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("Testing async stacks across setTimeout");
function getFunctionName(frame) {
return frame.slice(0, frame.indexOf("@"));
}
function a() { b() }
function b() { c() }
function c() { setTimeout(d, 1) }
function d() { e() }
function e() { f() }
function f() { setTimeout(g, 1) }
function g() { h() }
function h() { i() }
function i() {
var stackString = Error().stack;
document.getElementById("stack").textContent = stackString;
var frames = stackString
.split("\n")
.map(getFunctionName)
.filter(function (name) { return !!name; });
is(frames[0], "i");
is(frames[1], "h");
is(frames[2], "g");
is(frames[3], "setTimeout handler*SimpleTest_setTimeoutShim");
is(frames[4], "f");
is(frames[5], "e");
is(frames[6], "d");
is(frames[7], "setTimeout handler*SimpleTest_setTimeoutShim");
is(frames[8], "c");
is(frames[9], "b");
is(frames[10], "a");
SimpleTest.finish();
}
SpecialPowers.pushPrefEnv(
{"set": [['javascript.options.asyncstack', true]]},
a);
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1142577 - Async stacks for setTimeout</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
<pre id="stack"></pre>
<iframe id="iframe"></iframe>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var otherGlobal = document.getElementById("iframe").contentWindow;
function getFunctionName(frame) {
return frame.slice(0, frame.indexOf("@"));
}
function a() { b() }
function b() { c() }
function c() { otherGlobal.setTimeout(d, 1) }
function d() { e() }
function e() { f() }
function f() { otherGlobal.setTimeout(g, 1) }
function g() { h() }
function h() { i() }
function i() {
var stackString = Error().stack;
document.getElementById("stack").textContent = stackString;
var frames = stackString
.split("\n")
.map(getFunctionName)
.filter(function (name) { return !!name; });
is(frames[0], "i");
is(frames[1], "h");
is(frames[2], "g");
is(frames[3], "setTimeout handler*f");
is(frames[4], "e");
is(frames[5], "d");
is(frames[6], "setTimeout handler*c");
is(frames[7], "b");
is(frames[8], "a");
SimpleTest.finish();
}
SpecialPowers.pushPrefEnv(
{"set": [['javascript.options.asyncstack', true]]},
a);
</script>
</body>
</html>

Просмотреть файл

@ -43,6 +43,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
@ -169,6 +170,16 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
}
}
mAsyncStack.emplace(cx, aCallback->GetCreationStack());
if (*mAsyncStack) {
mAsyncCause.emplace(cx, JS_NewStringCopyZ(cx, aExecutionReason));
if (*mAsyncCause) {
mAsyncStackSetter.emplace(cx, *mAsyncStack, *mAsyncCause);
} else {
JS_ClearPendingException(cx);
}
}
// Enter the compartment of our callback, so we can actually work with it.
//
// Note that if the callback is a wrapper, this will not be the same

Просмотреть файл

@ -30,6 +30,7 @@
#include "nsWrapperCache.h"
#include "nsJSEnvironment.h"
#include "xpcpublic.h"
#include "jsapi.h"
namespace mozilla {
namespace dom {
@ -56,7 +57,15 @@ public:
explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
nsIGlobalObject *aIncumbentGlobal)
{
Init(aCallback, aIncumbentGlobal);
if (aCx && JS::RuntimeOptionsRef(aCx).asyncStack()) {
JS::RootedObject stack(aCx);
if (!JS::CaptureCurrentStack(aCx, &stack)) {
JS_ClearPendingException(aCx);
}
Init(aCallback, stack, aIncumbentGlobal);
} else {
Init(aCallback, nullptr, aIncumbentGlobal);
}
}
JS::Handle<JSObject*> Callback() const
@ -65,6 +74,15 @@ public:
return CallbackPreserveColor();
}
JSObject* GetCreationStack() const
{
JSObject* result = mCreationStack;
if (result) {
JS::ExposeObjectToActiveJS(result);
}
return result;
}
/*
* This getter does not change the color of the JSObject meaning that the
* object returned is not guaranteed to be kept alive past the next CC.
@ -112,7 +130,8 @@ protected:
explicit CallbackObject(CallbackObject* aCallbackObject)
{
Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal);
Init(aCallbackObject->mCallback, aCallbackObject->mCreationStack,
aCallbackObject->mIncumbentGlobal);
}
bool operator==(const CallbackObject& aOther) const
@ -125,12 +144,14 @@ protected:
}
private:
inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal)
inline void Init(JSObject* aCallback, JSObject* aCreationStack,
nsIGlobalObject* aIncumbentGlobal)
{
MOZ_ASSERT(aCallback && !mCallback);
// Set script objects before we hold, on the off chance that a GC could
// somehow happen in there... (which would be pretty odd, granted).
mCallback = aCallback;
mCreationStack = aCreationStack;
if (aIncumbentGlobal) {
mIncumbentGlobal = aIncumbentGlobal;
mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject();
@ -147,12 +168,14 @@ protected:
MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback);
if (mCallback) {
mCallback = nullptr;
mCreationStack = nullptr;
mIncumbentJSGlobal = nullptr;
mozilla::DropJSObjects(this);
}
}
JS::Heap<JSObject*> mCallback;
JS::Heap<JSObject*> mCreationStack;
// Ideally, we'd just hold a reference to the nsIGlobalObject, since that's
// what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't
// hold the actual JS global alive. So we maintain an additional pointer to
@ -213,6 +236,11 @@ protected:
// always within a request during its lifetime.
Maybe<JS::Rooted<JSObject*> > mRootedCallable;
// Members which are used to set the async stack.
Maybe<JS::Rooted<JSObject*>> mAsyncStack;
Maybe<JS::Rooted<JSString*>> mAsyncCause;
Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
// Can't construct a JSAutoCompartment without a JSContext either. Also,
// Put mAc after mAutoEntryScript so that we exit the compartment before
// we pop the JSContext. Though in practice we'll often manually order

Просмотреть файл

@ -8,6 +8,7 @@ support-files =
file_proxies_via_xray.html
forOf_iframe.html
[test_async_stacks.html]
[test_ByteString.html]
[test_InstanceOf.html]
[test_bug560072.html]

Просмотреть файл

@ -0,0 +1,108 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1148593
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1148593</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
/** Test for Bug 1148593 **/
SimpleTest.waitForExplicitFinish();
var TESTS;
function nextTest() {
var t = TESTS.pop();
if (t) {
t();
} else {
SimpleTest.finish();
}
}
function checkStack(functionName) {
try {
noSuchFunction();
} catch (e) {
ok(e.stack.indexOf(functionName) >= 0, "stack includes " + functionName);
}
nextTest();
}
function eventListener() {
checkStack("registerEventListener");
}
function registerEventListener(link) {
link.onload = eventListener;
}
function eventTest() {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "data:text/css,";
registerEventListener(link);
document.body.appendChild(link);
}
function xhrListener() {
checkStack("xhrTest");
}
function xhrTest() {
var ourFile = location.href;
var x = new XMLHttpRequest();
x.onload = xhrListener;
x.open("get", ourFile, true);
x.send();
}
function rafListener() {
checkStack("rafTest");
}
function rafTest() {
requestAnimationFrame(rafListener);
}
var intervalId;
function intervalHandler() {
clearInterval(intervalId);
checkStack("intervalTest");
}
function intervalTest() {
intervalId = setInterval(intervalHandler, 5);
}
function postMessageHandler(ev) {
ev.stopPropagation();
checkStack("postMessageTest");
}
function postMessageTest() {
window.addEventListener("message", postMessageHandler, true);
window.postMessage("whatever", "*");
}
function runTests() {
TESTS = [postMessageTest, intervalTest, rafTest, xhrTest, eventTest];
nextTest();
}
addLoadEvent(function() {
SpecialPowers.pushPrefEnv(
{"set": [['javascript.options.asyncstack', true]]},
runTests);
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148593">Mozilla Bug 1148593</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

Просмотреть файл

@ -15,6 +15,17 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
SimpleTest.waitForExplicitFinish();
function doTest() {
var file = location.href;
var asyncFrame;
/* Async parent frames from pushPrefEnv don't show up in e10s. */
var isE10S = !SpecialPowers.Services.wm.getMostRecentWindow("navigator:browser");
if (!isE10S && SpecialPowers.getBoolPref("javascript.options.asyncstack")) {
asyncFrame = `Async*@${file}:153:1
`;
} else {
asyncFrame = "";
}
var t = new TestInterfaceJS();
try {
t.testThrowError();
@ -25,12 +36,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.name, "Error", "Should not have an interesting name here");
is(e.message, "We are an Error", "Should have the right message");
is(e.stack,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:20:7\n",
`doTest@${file}:31:7
${asyncFrame}`,
"Exception stack should still only show our code");
is(e.fileName,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should have the right file name");
is(e.lineNumber, 20, "Should have the right line number");
is(e.lineNumber, 31, "Should have the right line number");
is(e.columnNumber, 7, "Should have the right column number");
}
@ -45,12 +57,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.code, DOMException.NOT_SUPPORTED_ERR,
"Should have the right 'code'");
is(e.stack,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:38:7\n",
`doTest@${file}:50:7
${asyncFrame}`,
"Exception stack should still only show our code");
is(e.filename,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should still have the right file name");
is(e.lineNumber, 38, "Should still have the right line number");
is(e.lineNumber, 50, "Should still have the right line number");
todo_isnot(e.columnNumber, 0,
"No column number support for DOMException yet");
}
@ -65,12 +78,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.message, "We are a TypeError",
"Should also have the right message (2)");
is(e.stack,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:59:7\n",
`doTest@${file}:72:7
${asyncFrame}`,
"Exception stack for TypeError should only show our code");
is(e.fileName,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should still have the right file name for TypeError");
is(e.lineNumber, 59, "Should still have the right line number for TypeError");
is(e.lineNumber, 72, "Should still have the right line number for TypeError");
is(e.columnNumber, 7, "Should have the right column number for TypeError");
}
@ -84,14 +98,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.message, "missing argument 0 when calling function Array.indexOf",
"Should also have the right message (3)");
is(e.stack,
"doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:78:45\n" +
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:78:7\n"
,
`doTest/<@${file}:92:45
doTest@${file}:92:7
${asyncFrame}`,
"Exception stack for TypeError should only show our code (3)");
is(e.fileName,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should still have the right file name for TypeError (3)");
is(e.lineNumber, 78, "Should still have the right line number for TypeError (3)");
is(e.lineNumber, 92, "Should still have the right line number for TypeError (3)");
is(e.columnNumber, 45, "Should have the right column number for TypeError (3)");
}
@ -104,12 +118,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (4)");
is(e.message, "", "Message should be sanitized (5)");
is(e.stack,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:99:7\n",
`doTest@${file}:113:7
${asyncFrame}`,
"Exception stack for sanitized exception should only show our code (4)");
is(e.filename,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should still have the right file name for sanitized exception (4)");
is(e.lineNumber, 99, "Should still have the right line number for sanitized exception (4)");
is(e.lineNumber, 113, "Should still have the right line number for sanitized exception (4)");
todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (4)");
}
@ -122,12 +137,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (5)");
is(e.message, "", "Message should be sanitized (5)");
is(e.stack,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:117:7\n",
`doTest@${file}:132:7
${asyncFrame}`,
"Exception stack for sanitized exception should only show our code (5)");
is(e.filename,
"http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
file,
"Should still have the right file name for sanitized exception (5)");
is(e.lineNumber, 117, "Should still have the right line number for sanitized exception (5)");
is(e.lineNumber, 132, "Should still have the right line number for sanitized exception (5)");
todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (5)");
}

Просмотреть файл

@ -37,23 +37,32 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
function doTest() {
var t = new TestInterfaceJS();
/* Async parent frames from pushPrefEnv don't show up in e10s. */
var isE10S = !SpecialPowers.Services.wm.getMostRecentWindow("navigator:browser");
var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
var ourFile = "http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html";
var ourFile = location.href;
var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:121:1
` : "";
Promise.all([
t.testPromiseWithThrowingChromePromiseInit().then(
ensurePromiseFail.bind(null, 1),
checkExn.bind(null, 44, "NS_ERROR_UNEXPECTED", "", undefined,
checkExn.bind(null, 48, "NS_ERROR_UNEXPECTED", "", undefined,
ourFile, 1,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:44:7\n")),
`doTest@${ourFile}:48:7
` +
parentFrame)),
t.testPromiseWithThrowingContentPromiseInit(function() {
thereIsNoSuchContentFunction1();
}).then(
ensurePromiseFail.bind(null, 2),
checkExn.bind(null, 50, "ReferenceError",
checkExn.bind(null, 56, "ReferenceError",
"thereIsNoSuchContentFunction1 is not defined",
undefined, ourFile, 2,
"doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:50:11\ndoTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:49:7\n")),
`doTest/<@${ourFile}:56:11
doTest@${ourFile}:55:7
` +
parentFrame)),
t.testPromiseWithThrowingChromeThenFunction().then(
ensurePromiseFail.bind(null, 3),
checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
@ -61,10 +70,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
thereIsNoSuchContentFunction2();
}).then(
ensurePromiseFail.bind(null, 4),
checkExn.bind(null, 61, "ReferenceError",
checkExn.bind(null, 70, "ReferenceError",
"thereIsNoSuchContentFunction2 is not defined",
undefined, ourFile, 4,
"doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:61:11\n" + (asyncStack ? "Async*doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:60:7\n" : ""))),
`doTest/<@${ourFile}:70:11
` +
(asyncStack ? `Async*doTest@${ourFile}:69:7
` : "") +
parentFrame)),
t.testPromiseWithThrowingChromeThenable().then(
ensurePromiseFail.bind(null, 5),
checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
@ -72,22 +85,27 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1107592
then: function() { thereIsNoSuchContentFunction3(); }
}).then(
ensurePromiseFail.bind(null, 6),
checkExn.bind(null, 72, "ReferenceError",
checkExn.bind(null, 85, "ReferenceError",
"thereIsNoSuchContentFunction3 is not defined",
undefined, ourFile, 6,
"doTest/<.then@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:72:32\n")),
`doTest/<.then@${ourFile}:85:32
`)),
t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
ensurePromiseFail.bind(null, 7),
checkExn.bind(null, 79, "NotFoundError",
checkExn.bind(null, 93, "NotFoundError",
"We are a second DOMException",
DOMException.NOT_FOUND_ERR, ourFile, 7,
"doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:79:7\n")),
`doTest@${ourFile}:93:7
` +
parentFrame)),
t.testPromiseWithDOMExceptionThrowingThenFunction().then(
ensurePromiseFail.bind(null, 8),
checkExn.bind(null, asyncStack ? 85 : 0, "NetworkError",
checkExn.bind(null, asyncStack ? 101 : 0, "NetworkError",
"We are a third DOMException",
DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
asyncStack ? "Async*doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:85:7\n" : "")),
(asyncStack ? `Async*doTest@${ourFile}:101:7
` +
parentFrame : ""))),
t.testPromiseWithDOMExceptionThrowingThenable().then(
ensurePromiseFail.bind(null, 9),
checkExn.bind(null, 0, "TypeMismatchError",

Просмотреть файл

@ -943,7 +943,8 @@ CallFunctionWithAsyncStack(JSContext* cx, unsigned argc, Value* vp)
RootedObject stack(cx, &args[1].toObject());
RootedString asyncCause(cx, args[2].toString());
JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause);
JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause,
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
return Call(cx, UndefinedHandleValue, function,
JS::HandleValueArray::empty(), args.rval());
}

Просмотреть файл

@ -4730,10 +4730,12 @@ JS_RestoreFrameChain(JSContext* cx)
}
JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
JSContext* cx, HandleObject stack, HandleString asyncCause)
JSContext* cx, HandleObject stack, HandleString asyncCause,
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind kind)
: cx(cx),
oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations)
oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations),
oldAsyncCallIsExplicit(cx->runtime()->asyncCallIsExplicit)
{
CHECK_REQUEST(cx);
@ -4748,6 +4750,7 @@ JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
cx->runtime()->asyncStackForNewActivations = asyncStack;
cx->runtime()->asyncCauseForNewActivations = asyncCause;
cx->runtime()->asyncCallIsExplicit = kind == AsyncCallKind::EXPLICIT;
}
JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
@ -4755,6 +4758,7 @@ JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
cx->runtime()->asyncCauseForNewActivations = oldAsyncCause;
cx->runtime()->asyncStackForNewActivations =
oldAsyncStack ? &oldAsyncStack->as<SavedFrame>() : nullptr;
cx->runtime()->asyncCallIsExplicit = oldAsyncCallIsExplicit;
}
/************************************************************************/

Просмотреть файл

@ -4027,14 +4027,25 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
JSContext* cx;
RootedObject oldAsyncStack;
RootedString oldAsyncCause;
bool oldAsyncCallIsExplicit;
public:
enum class AsyncCallKind {
// The ordinary kind of call, where we may apply an async
// parent if there is no ordinary parent.
IMPLICIT,
// An explicit async parent, e.g., callFunctionWithAsyncStack,
// where we always want to override any ordinary parent.
EXPLICIT
};
// The stack parameter cannot be null by design, because it would be
// ambiguous whether that would clear any scheduled async stack and make the
// normal stack reappear in the new call, or just keep the async stack
// already scheduled for the new call, if any.
AutoSetAsyncStackForNewCalls(JSContext* cx, HandleObject stack,
HandleString asyncCause);
HandleString asyncCause,
AsyncCallKind kind = AsyncCallKind::IMPLICIT);
~AutoSetAsyncStackForNewCalls();
};

Просмотреть файл

@ -128,6 +128,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
asmJSActivationStack_(nullptr),
asyncStackForNewActivations(nullptr),
asyncCauseForNewActivations(nullptr),
asyncCallIsExplicit(false),
entryMonitor(nullptr),
parentRuntime(parentRuntime),
interrupt_(false),

Просмотреть файл

@ -697,6 +697,12 @@ struct JSRuntime : public JS::shadow::Runtime,
*/
JSString* asyncCauseForNewActivations;
/*
* True if the async call was explicitly requested, e.g. via
* callFunctionWithAsyncStack.
*/
bool asyncCallIsExplicit;
/* If non-null, report JavaScript entry points to this monitor. */
JS::dbg::AutoEntryMonitor* entryMonitor;

Просмотреть файл

@ -912,6 +912,17 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
while (!iter.done()) {
Activation& activation = *iter.activation();
if (asyncActivation && asyncActivation != &activation) {
// We found an async stack in the previous activation, and we
// walked past the oldest frame of that activation, we're done.
// However, we only want to use the async parent if it was
// explicitly requested; if we got here otherwise, we have
// a direct parent, which we prefer.
if (asyncActivation->asyncCallIsExplicit())
break;
asyncActivation = nullptr;
}
if (!asyncActivation) {
asyncStack = activation.asyncStack();
if (asyncStack) {
@ -923,10 +934,6 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
asyncCause = activation.asyncCause();
asyncActivation = &activation;
}
} else if (asyncActivation != &activation) {
// We found an async stack in the previous activation, and we
// walked past the oldest frame of that activation, we're done.
break;
}
AutoLocationValueRooter location(cx);

Просмотреть файл

@ -868,11 +868,13 @@ Activation::Activation(JSContext* cx, Kind kind)
hideScriptedCallerCount_(0),
asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit),
entryMonitor_(cx->runtime_->entryMonitor),
kind_(kind)
{
cx->runtime_->asyncStackForNewActivations = nullptr;
cx->runtime_->asyncCauseForNewActivations = nullptr;
cx->runtime_->asyncCallIsExplicit = false;
cx->runtime_->entryMonitor = nullptr;
cx->runtime_->activation_ = this;
}
@ -886,6 +888,7 @@ Activation::~Activation()
cx_->runtime_->entryMonitor = entryMonitor_;
cx_->runtime_->asyncCauseForNewActivations = asyncCause_;
cx_->runtime_->asyncStackForNewActivations = asyncStack_;
cx_->runtime_->asyncCallIsExplicit = asyncCallIsExplicit_;
}
bool

Просмотреть файл

@ -1130,6 +1130,10 @@ class Activation
// Value of asyncCause to be attached to asyncStack_.
RootedString asyncCause_;
// True if the async call was explicitly requested, e.g. via
// callFunctionWithAsyncStack.
bool asyncCallIsExplicit_;
// The entry point monitor that was set on cx_->runtime() when this
// Activation was created. Subclasses should report their entry frame's
// function or script here.
@ -1215,6 +1219,10 @@ class Activation
return asyncCause_;
}
bool asyncCallIsExplicit() const {
return asyncCallIsExplicit_;
}
private:
Activation(const Activation& other) = delete;
void operator=(const Activation& other) = delete;

Просмотреть файл

@ -2824,7 +2824,8 @@ nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function,
if (!asyncCauseString)
return NS_ERROR_OUT_OF_MEMORY;
JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString);
JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString,
JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
if (!JS_CallFunctionValue(cx, nullptr, function,
JS::HandleValueArray::empty(), retval))

Просмотреть файл

@ -43,6 +43,10 @@
let Cu = this.require ? require("chrome").Cu : Components.utils;
let Cc = this.require ? require("chrome").Cc : Components.classes;
let Ci = this.require ? require("chrome").Ci : Components.interfaces;
// If we can access Components, then we use it to capture an async
// parent stack trace; see scheduleWalkerLoop. However, as it might
// not be available (see above), users of this must check it first.
let Components_ = this.require ? require("chrome").components : Components;
// If Cu is defined, use it to lazily define the FinalizationWitnessService.
if (Cu) {
@ -737,7 +741,15 @@ this.PromiseWalker = {
// If Cu is defined, this file is loaded on the main thread. Otherwise, it
// is loaded on the worker thread.
if (Cu) {
DOMPromise.resolve().then(() => this.walkerLoop());
let stack = Components_ ? Components_.stack : null;
if (stack) {
DOMPromise.resolve().then(() => {
Cu.callFunctionWithAsyncStack(this.walkerLoop.bind(this), stack,
"Promise")
});
} else {
DOMPromise.resolve().then(() => this.walkerLoop());
}
} else {
setImmediate(this.walkerLoop);
}