Skip to content

Commit b302044

Browse files
committedDec 19, 2023
Bug 1835614 - [devtools] Log function call arguments. r=devtools-reviewers,fluent-reviewers,devtools-backward-compat-reviewers,nchevobbe
Introduce a new global option in the tracer to log values. For now, it only triggers javascript function call arguments to be logged, but this will also impact the incoming feature logging native function calls, and also the other incoming feature to log the returned values. Differential Revision: https://phabricator.services.mozilla.com/D196019
1 parent 641c35b commit b302044

File tree

16 files changed

+282
-22
lines changed

16 files changed

+282
-22
lines changed
 

‎devtools/client/debugger/src/actions/ui.js

+6
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ export function setJavascriptTracingLogMethod(value) {
234234
};
235235
}
236236

237+
export function toggleJavascriptTracingValues() {
238+
return {
239+
type: "TOGGLE_JAVASCRIPT_TRACING_VALUES",
240+
};
241+
}
242+
237243
export function setHideOrShowIgnoredSources(shouldHide) {
238244
return ({ dispatch, getState }) => {
239245
dispatch({ type: "HIDE_IGNORED_SOURCES", shouldHide });

‎devtools/client/debugger/src/components/SecondaryPanes/CommandBar.js

+14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getIsCurrentThreadPaused,
1717
getIsThreadCurrentlyTracing,
1818
getJavascriptTracingLogMethod,
19+
getJavascriptTracingValues,
1920
} from "../../selectors";
2021
import { formatKeyShortcut } from "../../utils/text";
2122
import actions from "../../actions";
@@ -117,6 +118,7 @@ class CommandBar extends Component {
117118
topFrameSelected: PropTypes.bool.isRequired,
118119
toggleTracing: PropTypes.func.isRequired,
119120
logMethod: PropTypes.string.isRequired,
121+
logValues: PropTypes.bool.isRequired,
120122
setJavascriptTracingLogMethod: PropTypes.func.isRequired,
121123
setHideOrShowIgnoredSources: PropTypes.func.isRequired,
122124
toggleSourceMapIgnoreList: PropTypes.func.isRequired,
@@ -242,6 +244,16 @@ class CommandBar extends Component {
242244
this.props.setJavascriptTracingLogMethod(LOG_METHODS.STDOUT);
243245
},
244246
},
247+
{ type: "separator" },
248+
{
249+
id: "debugger-trace-menu-item-log-values",
250+
label: L10N.getStr("traceValues"),
251+
type: "checkbox",
252+
checked: this.props.logValues,
253+
click: () => {
254+
this.props.toggleJavascriptTracingValues();
255+
},
256+
},
245257
];
246258
showMenu(event, items);
247259
},
@@ -410,11 +422,13 @@ const mapStateToProps = state => ({
410422
isPaused: getIsCurrentThreadPaused(state),
411423
isTracingEnabled: getIsThreadCurrentlyTracing(state, getCurrentThread(state)),
412424
logMethod: getJavascriptTracingLogMethod(state),
425+
logValues: getJavascriptTracingValues(state),
413426
});
414427

415428
export default connect(mapStateToProps, {
416429
toggleTracing: actions.toggleTracing,
417430
setJavascriptTracingLogMethod: actions.setJavascriptTracingLogMethod,
431+
toggleJavascriptTracingValues: actions.toggleJavascriptTracingValues,
418432
resume: actions.resume,
419433
stepIn: actions.stepIn,
420434
stepOut: actions.stepOut,

‎devtools/client/debugger/src/reducers/ui.js

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const initialUIState = ({ supportsDebuggerStatementIgnore } = {}) => ({
3636
editorWrappingEnabled: prefs.editorWrapping,
3737
javascriptEnabled: true,
3838
javascriptTracingLogMethod: prefs.javascriptTracingLogMethod,
39+
javascriptTracingValues: prefs.javascriptTracingValues,
3940
mutableSearchOptions: prefs.searchOptions || {
4041
[searchKeys.FILE_SEARCH]: {
4142
regexMatch: false,
@@ -164,6 +165,14 @@ function update(state = initialUIState(), action) {
164165
return { ...state, javascriptTracingLogMethod: action.value };
165166
}
166167

168+
case "TOGGLE_JAVASCRIPT_TRACING_VALUES": {
169+
prefs.javascriptTracingValues = !prefs.javascriptTracingValues;
170+
return {
171+
...state,
172+
javascriptTracingValues: prefs.javascriptTracingValues,
173+
};
174+
}
175+
167176
case "SET_SEARCH_OPTIONS": {
168177
state.mutableSearchOptions[action.searchKey] = {
169178
...state.mutableSearchOptions[action.searchKey],

‎devtools/client/debugger/src/selectors/ui.js

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export function getJavascriptTracingLogMethod(state) {
7272
return state.ui.javascriptTracingLogMethod;
7373
}
7474

75+
export function getJavascriptTracingValues(state) {
76+
return state.ui.javascriptTracingValues;
77+
}
78+
7579
export function getSearchOptions(state, searchKey) {
7680
return state.ui.mutableSearchOptions[searchKey];
7781
}

‎devtools/client/debugger/src/utils/prefs.js

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ if (isNode()) {
4545
pref("devtools.debugger.log-actions", true);
4646
pref("devtools.debugger.log-event-breakpoints", false);
4747
pref("devtools.debugger.javascript-tracing-log-method", "console");
48+
pref("devtools.debugger.javascript-tracing-values", false);
4849
pref("devtools.debugger.hide-ignored-sources", false);
4950
pref("devtools.debugger.source-map-ignore-list-enabled", true);
5051
pref("devtools.debugger.features.wasm", true);
@@ -104,6 +105,7 @@ export const prefs = new PrefsHelper("devtools", {
104105
"String",
105106
"debugger.javascript-tracing-log-method",
106107
],
108+
javascriptTracingValues: ["Bool", "debugger.javascript-tracing-values"],
107109
hideIgnoredSources: ["Bool", "debugger.hide-ignored-sources"],
108110
sourceMapIgnoreListEnabled: [
109111
"Bool",

‎devtools/client/debugger/test/mochitest/browser_dbg-javascript-tracer.js

+48-7
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ add_task(async function () {
1515
info("Enable the tracing");
1616
await clickElement(dbg, "trace");
1717

18-
const topLevelThread =
18+
const topLevelThreadActorID =
1919
dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID;
2020
info("Wait for tracing to be enabled");
2121
await waitForState(dbg, state => {
22-
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThread);
22+
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
2323
});
2424

2525
ok(
@@ -89,7 +89,7 @@ add_task(async function () {
8989
await clickElement(dbg, "trace");
9090
info("Wait for tracing to be disabled");
9191
await waitForState(dbg, state => {
92-
return !dbg.selectors.getIsThreadCurrentlyTracing(topLevelThread);
92+
return !dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
9393
});
9494

9595
invokeInTab("inline_script2");
@@ -179,10 +179,10 @@ add_task(async function testPageKeyShortcut() {
179179

180180
const dbg = await initDebuggerWithAbsoluteURL("data:text/html,key-shortcut");
181181

182-
const topLevelThread =
182+
const topLevelThreadActorID =
183183
dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID;
184184
ok(
185-
!dbg.selectors.getIsThreadCurrentlyTracing(topLevelThread),
185+
!dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID),
186186
"Tracing is disabled on debugger opening"
187187
);
188188

@@ -212,7 +212,7 @@ add_task(async function testPageKeyShortcut() {
212212

213213
info("Wait for tracing to be enabled");
214214
await waitForState(dbg, state => {
215-
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThread);
215+
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
216216
});
217217

218218
is(
@@ -232,7 +232,7 @@ add_task(async function testPageKeyShortcut() {
232232

233233
info("Wait for tracing to be disabled");
234234
await waitForState(dbg, state => {
235-
return !dbg.selectors.getIsThreadCurrentlyTracing(topLevelThread);
235+
return !dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
236236
});
237237
});
238238

@@ -316,3 +316,44 @@ add_task(async function testPageKeyShortcutWithoutDebugger() {
316316
info("Wait for tracing to be disabled");
317317
await onTracingStateDisabled;
318318
});
319+
320+
add_task(async function testTracingValues() {
321+
await pushPref("devtools.debugger.features.javascript-tracing", true);
322+
// Cover tracing function argument values
323+
const jsCode = `function foo() { bar(1, ["array"], { attribute: 3 }, BigInt(4), Infinity, Symbol("6"), "7"); }; function bar(a, b, c) {}`;
324+
const dbg = await initDebuggerWithAbsoluteURL(
325+
"data:text/html," + encodeURIComponent(`<script>${jsCode}</script>`)
326+
);
327+
328+
await openContextMenuInDebugger(dbg, "trace", 4);
329+
const toggled = waitForDispatch(
330+
dbg.store,
331+
"TOGGLE_JAVASCRIPT_TRACING_VALUES"
332+
);
333+
selectContextMenuItem(dbg, `#debugger-trace-menu-item-log-values`);
334+
await toggled;
335+
ok(true, "Toggled the log values setting");
336+
337+
await clickElement(dbg, "trace");
338+
339+
const topLevelThreadActorID =
340+
dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID;
341+
info("Wait for tracing to be enabled");
342+
await waitForState(dbg, state => {
343+
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
344+
});
345+
346+
invokeInTab("foo");
347+
348+
await hasConsoleMessage(dbg, "λ foo()");
349+
await hasConsoleMessage(dbg, "λ bar");
350+
const { value } = await findConsoleMessage(dbg, "λ bar");
351+
is(
352+
value,
353+
`interpreter⟶λ bar(1, \nArray [ "array" ]\n, \nObject { attribute: 3 }\n, 4n, Infinity, Symbol("6"), "7")`,
354+
"The argument were printed for bar()"
355+
);
356+
357+
// Reset the log values setting
358+
Services.prefs.clearUserPref("devtools.debugger.javascript-tracing-values");
359+
});

‎devtools/client/locales/en-US/debugger.properties

+6
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ traceInWebConsole=Trace in the web console
142142
# This is used to force logging JavaScript traces in the stdout.
143143
traceInStdout=Trace in the stdout
144144

145+
# LOCALIZATION NOTE (traceValues): The label that is displayed in the context menu
146+
# of the trace button, which is in the top of the debugger right sidebar.
147+
# This is used to enable logging arguments passed to function calls
148+
# as well as returned values (only for JS function calls, but not native function calls)
149+
traceValues=Log function arguments and returned values
150+
145151
# LOCALIZATION NOTE (resumeButtonTooltip): The label that is displayed on the pause
146152
# button when the debugger is in a paused state.
147153
resumeButtonTooltip=Resume %S

‎devtools/client/preferences/debugger.js

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pref("devtools.debugger.map-scopes-enabled", false);
5151
pref("devtools.debugger.log-actions", false);
5252
pref("devtools.debugger.log-event-breakpoints", false);
5353
pref("devtools.debugger.javascript-tracing-log-method", "console");
54+
pref("devtools.debugger.javascript-tracing-values", false);
5455

5556
pref("devtools.debugger.features.wasm", true);
5657
pref("devtools.debugger.features.code-folding", false);

‎devtools/client/webconsole/test/browser/browser_jsterm_trace_command.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,19 @@ add_task(async function () {
4343
// Instead the frontend log a message as a console API message.
4444
msg = await evaluateExpressionInConsole(
4545
hud,
46-
":trace --logMethod console --prefix foo",
46+
":trace --logMethod console --prefix foo --values",
4747
"console-api"
4848
);
4949
is(msg.textContent.trim(), "Started tracing to Web Console");
5050

5151
info("Trigger some code to log some traces");
5252
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
53-
content.wrappedJSObject.main();
53+
content.wrappedJSObject.main("arg", 2);
5454
});
5555

56+
// Assert that we also see the custom prefix, as well as function arguments
5657
await waitFor(
57-
() => !!findTracerMessages(hud, `foo: interpreter⟶λ main`).length
58+
() => !!findTracerMessages(hud, `foo: interpreter⟶λ main("arg", 2)`).length
5859
);
5960

6061
info("Test toggling the tracer OFF");

‎devtools/server/actors/tracer.js

+37-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ const { tracerSpec } = require("resource://devtools/shared/specs/tracer.js");
1919

2020
const { throttle } = require("resource://devtools/shared/throttle.js");
2121

22+
const {
23+
makeDebuggeeValue,
24+
createValueGripForTarget,
25+
} = require("devtools/server/actors/object/utils");
26+
2227
const {
2328
TYPES,
2429
getResourceWatcher,
@@ -85,8 +90,18 @@ class TracerActor extends Actor {
8590
return false;
8691
}
8792

88-
startTracing(logMethod = LOG_METHODS.STDOUT) {
89-
this.#startTracing({ logMethod });
93+
/**
94+
* Start tracing.
95+
*
96+
* @param {String} logMethod
97+
* The output method used by the tracer.
98+
* See `LOG_METHODS` for potential values.
99+
* @param {Object} options
100+
* Options used to configure JavaScriptTracer.
101+
* See `JavaScriptTracer.startTracing`.
102+
*/
103+
startTracing(logMethod = LOG_METHODS.STDOUT, options = {}) {
104+
this.#startTracing({ ...options, logMethod });
90105
}
91106

92107
#startTracing(options) {
@@ -109,11 +124,14 @@ class TracerActor extends Actor {
109124
onTracingInfiniteLoop: this.onTracingInfiniteLoop.bind(this),
110125
};
111126
addTracingListener(this.tracingListener);
127+
this.traceValues = !!options.traceValues;
112128
startTracing({
113129
global: this.targetActor.window || this.targetActor.workerGlobal,
114130
prefix: options.prefix || "",
115131
// Enable receiving the `currentDOMEvent` being passed to `onTracingFrame`
116132
traceDOMEvents: true,
133+
// Enable tracing function arguments as well as returned values
134+
traceValues: !!options.traceValues,
117135
});
118136
}
119137

@@ -238,6 +256,22 @@ class TracerActor extends Actor {
238256
});
239257
}
240258

259+
let args = undefined;
260+
// Log arguments, but only when this feature is enabled as it introduce
261+
// some significant overhead in perf as well as memory as it may hold the objects in memory.
262+
if (this.traceValues) {
263+
args = [];
264+
for (let arg of frame.arguments) {
265+
// Debugger.Frame.arguments contains either a Debugger.Object or primitive object
266+
if (arg?.unsafeDereference) {
267+
arg = arg.unsafeDereference();
268+
}
269+
// Instantiate a object actor so that the tools can easily inspect these objects
270+
const dbgObj = makeDebuggeeValue(this.targetActor, arg);
271+
args.push(createValueGripForTarget(this.targetActor, dbgObj));
272+
}
273+
}
274+
241275
// Create a message object that fits Console Message Watcher expectations
242276
this.throttledTraces.push({
243277
resourceType: JSTRACER_TRACE,
@@ -251,6 +285,7 @@ class TracerActor extends Actor {
251285
lineNumber,
252286
columnNumber: columnNumber - columnBase,
253287
sourceId: script.source.id,
288+
args,
254289
});
255290
this.throttleEmitTraces();
256291
} else if (this.logMethod == LOG_METHODS.PROFILER) {

‎devtools/server/actors/webconsole/commands/manager.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ loader.lazyGetter(this, "l10n", () => {
1616
});
1717
const USAGE_STRING_MAPPING = {
1818
block: "webconsole-commands-usage-block",
19-
trace: "webconsole-commands-usage-trace",
19+
trace: "webconsole-commands-usage-trace2",
2020
unblock: "webconsole-commands-usage-unblock",
2121
};
2222

@@ -871,6 +871,7 @@ WebConsoleCommandsManager.register({
871871
const enabled = tracerActor.toggleTracing({
872872
logMethod,
873873
prefix: args.prefix || null,
874+
traceValues: !!args.values,
874875
});
875876

876877
owner.helperResult = {
@@ -879,5 +880,5 @@ WebConsoleCommandsManager.register({
879880
logMethod,
880881
};
881882
},
882-
validArguments: ["logMethod", "prefix"],
883+
validArguments: ["logMethod", "prefix", "values"],
883884
});

‎devtools/server/tracer/tests/xpcshell/test_tracer.js

+32
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,35 @@ add_task(async function testTracingJSMGlobal() {
137137

138138
removeTracingListener(listenerSandbox.listener);
139139
});
140+
141+
add_task(async function testTracingValues() {
142+
// Test the `traceValues` flag
143+
const sandbox = Cu.Sandbox("https://example.com");
144+
Cu.evalInSandbox(
145+
`function foo() { bar(-0, 1, ["array"], { attribute: 3 }, "4", BigInt(5), Symbol("6"), Infinity, undefined, null, false, NaN, function foo() {}, function () {}, class MyClass {}); }; function bar(a, b, c) {}`,
146+
sandbox
147+
);
148+
149+
// Pass an override method to catch all strings tentatively logged to stdout
150+
const logs = [];
151+
function loggingMethod(str) {
152+
logs.push(str);
153+
}
154+
155+
info("Start tracing");
156+
startTracing({ global: sandbox, traceValues: true, loggingMethod });
157+
158+
info("Call some code");
159+
sandbox.foo();
160+
161+
Assert.equal(logs.length, 3);
162+
Assert.equal(logs[0], "Start tracing JavaScript\n");
163+
Assert.stringContains(logs[1], "λ foo()");
164+
Assert.stringContains(
165+
logs[2],
166+
`λ bar(-0, 1, Array(1), [object Object], "4", BigInt(5), Symbol(6), Infinity, undefined, null, false, NaN, function foo(), function anonymous(), class MyClass)`
167+
);
168+
169+
info("Stop tracing");
170+
stopTracing();
171+
});

0 commit comments

Comments
 (0)
Failed to load comments.