mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-02 20:20:00 +00:00
Bug 1148593 - Create async stack in callback objects. r=bz, r=fitzgen
--HG-- extra : rebase_source : f9b507d8f005dbca6f40f510ca41a0cbb03aebf9
This commit is contained in:
parent
a37b7fa928
commit
9f3b16bf4c
@ -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'
|
||||
|
60
dom/base/test/test_async_setTimeout_stack.html
Normal file
60
dom/base/test/test_async_setTimeout_stack.html
Normal file
@ -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]
|
||||
|
108
dom/bindings/test/test_async_stacks.html
Normal file
108
dom/bindings/test/test_async_stacks.html
Normal file
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user