diff --git a/js/src/debugger/Debugger.cpp b/js/src/debugger/Debugger.cpp index 7715fc55f104..a1b485090e99 100644 --- a/js/src/debugger/Debugger.cpp +++ b/js/src/debugger/Debugger.cpp @@ -4136,6 +4136,8 @@ struct MOZ_STACK_CLASS Debugger::CallData { bool adoptDebuggeeValue(); bool adoptFrame(); bool adoptSource(); + bool enableAsyncStack(); + bool disableAsyncStack(); using Method = bool (CallData::*)(); @@ -6290,6 +6292,36 @@ bool Debugger::CallData::adoptSource() { return true; } +bool Debugger::CallData::enableAsyncStack() { + if (!args.requireAtLeast(cx, "Debugger.enableAsyncStack", 1)) { + return false; + } + Rooted global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); + if (!global) { + return false; + } + + global->realm()->isAsyncStackCapturingEnabled = true; + + args.rval().setUndefined(); + return true; +} + +bool Debugger::CallData::disableAsyncStack() { + if (!args.requireAtLeast(cx, "Debugger.disableAsyncStack", 1)) { + return false; + } + Rooted global(cx, dbg->unwrapDebuggeeArgument(cx, args[0])); + if (!global) { + return false; + } + + global->realm()->isAsyncStackCapturingEnabled = false; + + args.rval().setUndefined(); + return true; +} + const JSPropertySpec Debugger::properties[] = { JS_DEBUG_PSGS("onDebuggerStatement", getOnDebuggerStatement, setOnDebuggerStatement), @@ -6332,6 +6364,8 @@ const JSFunctionSpec Debugger::methods[] = { JS_DEBUG_FN("adoptDebuggeeValue", adoptDebuggeeValue, 1), JS_DEBUG_FN("adoptFrame", adoptFrame, 1), JS_DEBUG_FN("adoptSource", adoptSource, 1), + JS_DEBUG_FN("enableAsyncStack", enableAsyncStack, 1), + JS_DEBUG_FN("disableAsyncStack", disableAsyncStack, 1), JS_FS_END}; const JSFunctionSpec Debugger::static_methods[]{ diff --git a/js/src/doc/Debugger/Debugger.md b/js/src/doc/Debugger/Debugger.md index 20217c9d6ee4..269f7a6f78e1 100644 --- a/js/src/doc/Debugger/Debugger.md +++ b/js/src/doc/Debugger/Debugger.md @@ -518,6 +518,14 @@ the adopting debugger, this method will throw. Given `source` of type `Debugger.Source` which is owned by an arbitrary `Debugger`, return an equivalent `Debugger.Source` owned by this `Debugger`. +### `enableAsyncStack(global)` +Enable async stack capturing for the realm for the global object designated by +global. + +### `disableAsyncStack(global)` +Disable async stack capturing for the realm for the global object designated by +global. + ## Static methods of the Debugger Object The functions described below are not called with a `this` value. diff --git a/js/src/jit-test/tests/debug/async-stack.js b/js/src/jit-test/tests/debug/async-stack.js new file mode 100644 index 000000000000..015fc54c7821 --- /dev/null +++ b/js/src/jit-test/tests/debug/async-stack.js @@ -0,0 +1,35 @@ +// |jit-test| --async-stacks-capture-debuggee-only + +const g = newGlobal({newCompartment: true}); + +const code = ` +var stack = ""; + +async function Async() { + await 1; + stack = new Error().stack; +} + +function Sync() { + Async(); +} + +Sync(); +`; + +g.eval(code); +drainJobQueue(); +assertEq(g.stack.includes("Sync"), false); + +let dbg = new Debugger(); +dbg.enableAsyncStack(g); + +g.eval(code); +drainJobQueue(); +assertEq(g.stack.includes("Sync"), true); + +dbg.disableAsyncStack(g); + +g.eval(code); +drainJobQueue(); +assertEq(g.stack.includes("Sync"), false); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 82c5d29e7881..6d5dfc2a8ed3 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4593,8 +4593,12 @@ JS_PUBLIC_API bool JS::IsAsyncStackCaptureEnabledForRealm(JSContext* cx) { return false; } - return !cx->options().asyncStackCaptureDebuggeeOnly() || - cx->realm()->isDebuggee(); + if (!cx->options().asyncStackCaptureDebuggeeOnly() || + cx->realm()->isDebuggee()) { + return true; + } + + return cx->realm()->isAsyncStackCapturingEnabled; } JS_PUBLIC_API bool JS::CopyAsyncStack(JSContext* cx, diff --git a/js/src/vm/Realm.h b/js/src/vm/Realm.h index 25656ee540d1..2559abd71f70 100644 --- a/js/src/vm/Realm.h +++ b/js/src/vm/Realm.h @@ -431,6 +431,17 @@ class JS::Realm : public JS::shadow::Realm { // This prevents us from creating new wrappers for the compartment. bool nukedIncomingWrappers = false; + // Enable async stack capturing for this realm even if + // JS::ContextOptions::asyncStackCaptureDebuggeeOnly_ is true. + // + // No-op when JS::ContextOptions::asyncStack_ is false, or + // JS::ContextOptions::asyncStackCaptureDebuggeeOnly_ is false. + // + // This can be used as a lightweight alternative for making the global + // debuggee, if the async stack capturing is necessary but no other debugging + // features are used. + bool isAsyncStackCapturingEnabled = false; + private: void updateDebuggerObservesFlag(unsigned flag);