Skip to content

Commit dfe6db9

Browse files
committedJan 30, 2024
Bug 1848159 - [devtools] Introduce an option to start tracing on next page load. r=devtools-reviewers,devtools-backward-compat-reviewers,nchevobbe
Because the JavaScript tracer is currently running in each process/thread/target actor independently, it is more complex to make it record across navigations as it may be spawn in a distinct process. While it is fine for stdout and webconsole outputs (we could just spawn many concurrent tracers in each process), this is more complex for the experimental "profiler" output. For now, the profiler output automatically stops on target destruction and will open the profiler result. By having an option to start recording on next page load, it prevents starting the tracer and prevent logging traces for the current WindowGlobal. It should help focus on the new document. For the profiler output, it prevents having the profiler to show up for the previous WindowGlobal. Sideby tweak: * stop passing logMethod and consider, like other option to be coming from the preferences. * fix confusing state when debugging a page running in the same process. Differential Revision: https://phabricator.services.mozilla.com/D196874
1 parent e76f9a1 commit dfe6db9

File tree

13 files changed

+226
-30
lines changed

13 files changed

+226
-30
lines changed
 

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
44

5-
import { getIsJavascriptTracingEnabled } from "../selectors/index";
5+
import {
6+
getIsJavascriptTracingEnabled,
7+
getJavascriptTracingLogMethod,
8+
} from "../selectors/index";
69
import { PROMISE } from "./utils/middleware/promise";
710

811
/**
9-
* Toggle ON/OFF Javascript tracing for all targets,
10-
* using the specified log method.
11-
*
12-
* @param {string} logMethod
13-
* Can be "stdout" or "console". See TracerActor.
12+
* Toggle ON/OFF Javascript tracing for all targets.
1413
*/
15-
export function toggleTracing(logMethod) {
14+
export function toggleTracing() {
1615
return async ({ dispatch, getState, client, panel }) => {
1716
// For now, the UI can only toggle all the targets all at once.
1817
const isTracingEnabled = getIsJavascriptTracingEnabled(getState());
18+
const logMethod = getJavascriptTracingLogMethod(getState());
1919

2020
// Automatically open the split console when enabling tracing to the console
2121
if (!isTracingEnabled && logMethod == "console") {
@@ -24,7 +24,7 @@ export function toggleTracing(logMethod) {
2424

2525
return dispatch({
2626
type: "TOGGLE_TRACING",
27-
[PROMISE]: client.toggleTracing(logMethod),
27+
[PROMISE]: client.toggleTracing(),
2828
enabled: !isTracingEnabled,
2929
});
3030
};

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,9 @@ export function copyToClipboard(location) {
226226
}
227227

228228
export function setJavascriptTracingLogMethod(value) {
229-
return ({ dispatch, getState }) => {
230-
dispatch({
231-
type: "SET_JAVASCRIPT_TRACING_LOG_METHOD",
232-
value,
233-
});
229+
return {
230+
type: "SET_JAVASCRIPT_TRACING_LOG_METHOD",
231+
value,
234232
};
235233
}
236234

@@ -246,6 +244,12 @@ export function toggleJavascriptTracingOnNextInteraction() {
246244
};
247245
}
248246

247+
export function toggleJavascriptTracingOnNextLoad() {
248+
return {
249+
type: "TOGGLE_JAVASCRIPT_TRACING_ON_NEXT_LOAD",
250+
};
251+
}
252+
249253
export function setHideOrShowIgnoredSources(shouldHide) {
250254
return ({ dispatch, getState }) => {
251255
dispatch({ type: "HIDE_IGNORED_SOURCES", shouldHide });

‎devtools/client/debugger/src/client/firefox/commands.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ function forEachThread(iteratee) {
111111
return Promise.all(promises);
112112
}
113113

114-
async function toggleTracing(logMethod) {
115-
return commands.tracerCommand.toggle(logMethod);
114+
async function toggleTracing() {
115+
return commands.tracerCommand.toggle();
116116
}
117117

118118
function resume(thread, frameId) {

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getJavascriptTracingLogMethod,
2020
getJavascriptTracingValues,
2121
getJavascriptTracingOnNextInteraction,
22+
getJavascriptTracingOnNextLoad,
2223
} from "../../selectors/index";
2324
import { formatKeyShortcut } from "../../utils/text";
2425
import actions from "../../actions/index";
@@ -230,7 +231,7 @@ class CommandBar extends Component {
230231
this.props.logMethod
231232
),
232233
onClick: event => {
233-
this.props.toggleTracing(this.props.logMethod);
234+
this.props.toggleTracing();
234235
},
235236
onContextMenu: event => {
236237
event.preventDefault();
@@ -261,21 +262,31 @@ class CommandBar extends Component {
261262
},
262263
{ type: "separator" },
263264
{
264-
id: "debugger-trace-menu-item-log-values",
265-
label: L10N.getStr("traceValues"),
265+
id: "debugger-trace-menu-item-next-interaction",
266+
label: L10N.getStr("traceOnNextInteraction"),
266267
type: "checkbox",
267-
checked: this.props.logValues,
268+
checked: this.props.traceOnNextInteraction,
268269
click: () => {
269-
this.props.toggleJavascriptTracingValues();
270+
this.props.toggleJavascriptTracingOnNextInteraction();
270271
},
271272
},
272273
{
273-
id: "debugger-trace-menu-item-next-interaction",
274-
label: L10N.getStr("traceOnNextInteraction"),
274+
id: "debugger-trace-menu-item-next-load",
275+
label: L10N.getStr("traceOnNextLoad"),
275276
type: "checkbox",
276-
checked: this.props.traceOnNextInteraction,
277+
checked: this.props.traceOnNextLoad,
277278
click: () => {
278-
this.props.toggleJavascriptTracingOnNextInteraction();
279+
this.props.toggleJavascriptTracingOnNextLoad();
280+
},
281+
},
282+
{ type: "separator" },
283+
{
284+
id: "debugger-trace-menu-item-log-values",
285+
label: L10N.getStr("traceValues"),
286+
type: "checkbox",
287+
checked: this.props.logValues,
288+
click: () => {
289+
this.props.toggleJavascriptTracingValues();
279290
},
280291
},
281292
];
@@ -452,6 +463,7 @@ const mapStateToProps = state => ({
452463
logMethod: getJavascriptTracingLogMethod(state),
453464
logValues: getJavascriptTracingValues(state),
454465
traceOnNextInteraction: getJavascriptTracingOnNextInteraction(state),
466+
traceOnNextLoad: getJavascriptTracingOnNextLoad(state),
455467
});
456468

457469
export default connect(mapStateToProps, {
@@ -460,6 +472,7 @@ export default connect(mapStateToProps, {
460472
toggleJavascriptTracingValues: actions.toggleJavascriptTracingValues,
461473
toggleJavascriptTracingOnNextInteraction:
462474
actions.toggleJavascriptTracingOnNextInteraction,
475+
toggleJavascriptTracingOnNextLoad: actions.toggleJavascriptTracingOnNextLoad,
463476
resume: actions.resume,
464477
stepIn: actions.stepIn,
465478
stepOut: actions.stepOut,

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

+9
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const initialUIState = () => ({
3939
javascriptTracingLogMethod: prefs.javascriptTracingLogMethod,
4040
javascriptTracingValues: prefs.javascriptTracingValues,
4141
javascriptTracingOnNextInteraction: prefs.javascriptTracingOnNextInteraction,
42+
javascriptTracingOnNextLoad: prefs.javascriptTracingOnNextLoad,
4243
mutableSearchOptions: prefs.searchOptions || {
4344
[searchKeys.FILE_SEARCH]: {
4445
regexMatch: false,
@@ -190,6 +191,14 @@ function update(state = initialUIState(), action) {
190191
};
191192
}
192193

194+
case "TOGGLE_JAVASCRIPT_TRACING_ON_NEXT_LOAD": {
195+
prefs.javascriptTracingOnNextLoad = !prefs.javascriptTracingOnNextLoad;
196+
return {
197+
...state,
198+
javascriptTracingOnNextLoad: prefs.javascriptTracingOnNextLoad,
199+
};
200+
}
201+
193202
case "SET_SEARCH_OPTIONS": {
194203
state.mutableSearchOptions[action.searchKey] = {
195204
...state.mutableSearchOptions[action.searchKey],

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

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ export function getJavascriptTracingOnNextInteraction(state) {
9292
return state.ui.javascriptTracingOnNextInteraction;
9393
}
9494

95+
export function getJavascriptTracingOnNextLoad(state) {
96+
return state.ui.javascriptTracingOnNextLoad;
97+
}
98+
9599
export function getSearchOptions(state, searchKey) {
96100
return state.ui.mutableSearchOptions[searchKey];
97101
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ if (isNode()) {
4747
pref("devtools.debugger.javascript-tracing-log-method", "console");
4848
pref("devtools.debugger.javascript-tracing-values", false);
4949
pref("devtools.debugger.javascript-tracing-on-next-interaction", false);
50+
pref("devtools.debugger.javascript-tracing-on-next-load", false);
5051
pref("devtools.debugger.hide-ignored-sources", false);
5152
pref("devtools.debugger.source-map-ignore-list-enabled", true);
5253
pref("devtools.debugger.features.wasm", true);
@@ -113,6 +114,10 @@ export const prefs = new PrefsHelper("devtools", {
113114
"Bool",
114115
"debugger.javascript-tracing-on-next-interaction",
115116
],
117+
javascriptTracingOnNextLoad: [
118+
"Bool",
119+
"debugger.javascript-tracing-on-next-load",
120+
],
116121
hideIgnoredSources: ["Bool", "debugger.hide-ignored-sources"],
117122
sourceMapIgnoreListEnabled: [
118123
"Bool",

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

+105
Original file line numberDiff line numberDiff line change
@@ -590,3 +590,108 @@ add_task(async function testInteractionBetweenDebuggerAndConsole() {
590590
info("Also wait for stop message in the console");
591591
await hasConsoleMessage(dbg, "Stopped tracing");
592592
});
593+
594+
add_task(async function testTracingOnNextLoad() {
595+
await pushPref("devtools.debugger.features.javascript-tracing", true);
596+
await pushPref(
597+
"devtools.debugger.features.javascript-tracing-log-method",
598+
"console"
599+
);
600+
// Cover tracing function argument values
601+
const jsCode = `function foo() {}; function bar() {}; foo(); dump("plop\\n")`;
602+
const dbg = await initDebuggerWithAbsoluteURL(
603+
"data:text/html," +
604+
encodeURIComponent(`<script>${jsCode}</script><body></body>`)
605+
);
606+
607+
await openContextMenuInDebugger(dbg, "trace", 4);
608+
const toggled = waitForDispatch(
609+
dbg.store,
610+
"TOGGLE_JAVASCRIPT_TRACING_ON_NEXT_LOAD"
611+
);
612+
selectContextMenuItem(dbg, `#debugger-trace-menu-item-next-load`);
613+
await toggled;
614+
ok(true, "Toggled the trace on next load");
615+
616+
info(
617+
"Wait for the split console to be automatically displayed when toggling this setting"
618+
);
619+
await dbg.toolbox.getPanelWhenReady("webconsole");
620+
621+
invokeInTab("bar");
622+
623+
// Let some time for the call to be traced
624+
await wait(500);
625+
626+
is(
627+
(await findConsoleMessages(dbg.toolbox, "λ bar")).length,
628+
0,
629+
"The code isn't logged as the tracer shouldn't be started yet"
630+
);
631+
632+
await clickElement(dbg, "trace");
633+
634+
const traceButton = findElement(dbg, "trace");
635+
// Wait for the trace button to be highlighted
636+
await waitFor(() => {
637+
return traceButton.classList.contains("pending");
638+
}, "The tracer button is pending before reloading the page");
639+
640+
info("Add an iframe to trigger a target creation");
641+
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
642+
const iframe = content.document.createElement("iframe");
643+
iframe.src = "data:text/html,iframe";
644+
content.document.body.appendChild(iframe);
645+
});
646+
647+
// Let some time to regress and start the tracer
648+
await wait(500);
649+
650+
ok(
651+
traceButton.classList.contains("pending"),
652+
"verify that the tracer is still pending after the iframe creation"
653+
);
654+
655+
// Reload the page to trigger the tracer
656+
await reload(dbg);
657+
658+
let topLevelThreadActorID =
659+
dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID;
660+
info("Wait for tracing to be enabled after page reload");
661+
await waitForState(dbg, state => {
662+
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
663+
});
664+
ok(
665+
traceButton.classList.contains("active"),
666+
"The tracer button is now active after reload"
667+
);
668+
669+
info("Wait for the split console to be displayed");
670+
await dbg.toolbox.getPanelWhenReady("webconsole");
671+
672+
// Ensure that the very early code is traced
673+
await hasConsoleMessage(dbg, "λ foo");
674+
675+
is(
676+
(await findConsoleMessages(dbg.toolbox, "λ bar")).length,
677+
0,
678+
"The code ran before the reload isn't logged"
679+
);
680+
681+
await clickElement(dbg, "trace");
682+
683+
topLevelThreadActorID =
684+
dbg.toolbox.commands.targetCommand.targetFront.threadFront.actorID;
685+
info("Wait for tracing to be disabled");
686+
await waitForState(dbg, state => {
687+
return dbg.selectors.getIsThreadCurrentlyTracing(topLevelThreadActorID);
688+
});
689+
await waitFor(() => {
690+
return !traceButton.classList.contains("active");
691+
}, "The tracer button is no longer active after stop request");
692+
693+
// Reset the trace on next interaction setting
694+
Services.prefs.clearUserPref(
695+
"devtools.debugger.javascript-tracing-on-next-interaction"
696+
);
697+
});

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

+5
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ traceValues=Log function arguments and returned values
153153
# This is used to automatically start the tracing on next user interaction (mousedown/keydown)
154154
traceOnNextInteraction=Trace only on next user interaction (mousedown/keydown)
155155

156+
# LOCALIZATION NOTE (traceOnNextLoad): The label that is displayed in the context menu
157+
# of the trace button, which is in the top of the debugger right sidebar.
158+
# This is used to automatically start the tracing on next page load.
159+
traceOnNextLoad=Trace only on next page load (reload or navigation)
160+
156161
# LOCALIZATION NOTE (resumeButtonTooltip): The label that is displayed on the pause
157162
# button when the debugger is in a paused state.
158163
resumeButtonTooltip=Resume %S

‎devtools/server/actors/resources/jstracer-state.js

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ class TracingStateWatcher {
5959
onTracingToggled(enabled, reason) {
6060
const tracerActor = this.targetActor.getTargetScopedActor("tracer");
6161
const logMethod = tracerActor?.getLogMethod();
62+
63+
// JavascriptTracer only supports recording once in the same process/thread.
64+
// If we open another DevTools, on the same process, we would receive notification
65+
// about a JavascriptTracer controlled by another toolbox's tracer actor.
66+
// Ignore them as our current tracer actor didn't start tracing.
67+
if (!logMethod) {
68+
return;
69+
}
70+
6271
this.onAvailable([
6372
{
6473
resourceType: JSTRACER_STATE,

‎devtools/server/actors/targets/base-target-actor.js

+39
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
"use strict";
66

77
const { Actor } = require("resource://devtools/shared/protocol.js");
8+
const {
9+
TYPES,
10+
getResourceWatcher,
11+
} = require("resource://devtools/server/actors/resources/index.js");
812

913
loader.lazyRequireGetter(
1014
this,
@@ -146,10 +150,45 @@ class BaseTargetActor extends Actor {
146150
*
147151
* This will be called by the watcher when the DevTools target-configuration
148152
* is updated, or when a target is created via JSWindowActors.
153+
*
154+
* @param {JSON} options
155+
* Configuration object provided by the client.
156+
* See target-configuration actor.
157+
* @param {Boolean} calledFromDocumentCreate
158+
* True, when this is called with initial configuration when the related target
159+
* actor is instantiated.
149160
*/
150161
updateTargetConfiguration(options = {}, calledFromDocumentCreation = false) {
151162
// If there is some tracer options, we should start tracing, otherwise we should stop (if we were)
152163
if (options.tracerOptions) {
164+
// Ignore the SessionData update if the user requested to start the tracer on next page load and:
165+
// - we apply it to an already loaded WindowGlobal,
166+
// - the target isn't the top level one.
167+
if (
168+
options.tracerOptions.traceOnNextLoad &&
169+
(!calledFromDocumentCreation || !this.isTopLevelTarget)
170+
) {
171+
if (this.isTopLevelTarget) {
172+
const consoleMessageWatcher = getResourceWatcher(
173+
this,
174+
TYPES.CONSOLE_MESSAGE
175+
);
176+
if (consoleMessageWatcher) {
177+
consoleMessageWatcher.emitMessages([
178+
{
179+
arguments: [
180+
"Waiting for next navigation or page reload before starting tracing",
181+
],
182+
styles: [],
183+
level: "jstracer",
184+
chromeContext: false,
185+
timeStamp: ChromeUtils.dateNow(),
186+
},
187+
]);
188+
}
189+
}
190+
return;
191+
}
153192
const tracerActor = this.getTargetScopedActor("tracer");
154193
tracerActor.startTracing(options.tracerOptions);
155194
} else if (this.hasTargetScopedActor("tracer")) {

0 commit comments

Comments
 (0)
Failed to load comments.