Bug 1866818 - [devtools] Enable the JS tracer on remote debugging. r=devtools-reviewers,devtools-backward-compat-reviewers,nchevobbe

Display a warning message in the tracer sidebar when there could be a backward compat issue.

Differential Revision: https://phabricator.services.mozilla.com/D217745
This commit is contained in:
Alexandre Poirot 2024-07-31 12:48:21 +00:00
parent 5238055398
commit e985b5cc50
17 changed files with 219 additions and 11 deletions

View File

@ -108,6 +108,8 @@ fail-if = ["a11y_checks"] # Bug 1849028 clicked element may not be focusable and
["browser_aboutdebugging_devtoolstoolbox_focus.js"]
["browser_aboutdebugging_devtoolstoolbox_jstracer.js"]
["browser_aboutdebugging_devtoolstoolbox_menubar.js"]
["browser_aboutdebugging_devtoolstoolbox_navigate_back_forward.js"]

View File

@ -0,0 +1,79 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/shared-head.js",
this
);
const TAB_URL =
"data:text/html,<script>function foo() { bar(); }; function bar() {}</script>";
/* import-globals-from helper-collapsibilities.js */
Services.scriptloader.loadSubScript(
CHROME_URL_ROOT + "helper-collapsibilities.js",
this
);
/**
* Test JavaScript Tracer in about:devtools-toolbox tabs (ie non localTab tab target).
*/
add_task(async function () {
// This is preffed off for now, so ensure turning it on
await pushPref("devtools.debugger.features.javascript-tracing", true);
const testTab = await addTab(TAB_URL);
info("Force all debug target panes to be expanded");
prepareCollapsibilitiesTest();
const { document, tab, window } = await openAboutDebugging();
await selectThisFirefoxPage(document, window.AboutDebugging.store);
const { devtoolsTab, devtoolsWindow } = await openAboutDevtoolsToolbox(
document,
tab,
window,
TAB_URL
);
info("Select performance panel");
const toolbox = getToolbox(devtoolsWindow);
await toolbox.selectTool("jsdebugger");
info("Add a breakpoint at line 10 in the test script");
const debuggerContext = createDebuggerContext(toolbox);
await toggleJsTracerMenuItem(
debuggerContext,
"#jstracer-menu-item-debugger-sidebar"
);
await toggleJsTracer(toolbox);
info("Invoke some code that will be traced");
await ContentTask.spawn(testTab.linkedBrowser, {}, function () {
content.wrappedJSObject.foo();
});
info("Wait for the expected traces to appear in the call tree");
const tree = await waitForElementWithSelector(
debuggerContext,
"#tracer-tab-panel .tree"
);
const traces = await waitFor(() => {
const elements = tree.querySelectorAll(
".trace-line .frame-link-function-display-name"
);
if (elements.length == 2) {
return elements;
}
return false;
});
is(traces[0].textContent, "λ foo");
is(traces[1].textContent, "λ bar");
await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
await removeTab(testTab);
await removeTab(tab);
});

View File

@ -67,3 +67,14 @@ export function selectTrace(traceIndex) {
);
};
}
export function setLocalAndRemoteRuntimeVersion(
localPlatformVersion,
remotePlatformVersion
) {
return {
type: "SET_RUNTIME_VERSIONS",
localPlatformVersion,
remotePlatformVersion,
};
}

View File

@ -11,6 +11,9 @@ import sourceQueue from "../utils/source-queue";
const {
TRACER_LOG_METHODS,
} = require("resource://devtools/shared/specs/tracer.js");
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
let actions;
let commands;
@ -94,6 +97,15 @@ export async function onConnect(_commands, _resourceCommand, _actions, store) {
// to be able to clear the tracer data on tracing start, that, even if the
// tracer is waiting for next interation/load.
commands.tracerCommand.on("toggle", onTracingToggled);
if (!commands.client.isLocalClient) {
const localPlatformVersion = AppConstants.MOZ_APP_VERSION;
const remotePlatformVersion = await getRemotePlatformVersion();
actions.setLocalAndRemoteRuntimeVersion(
localPlatformVersion,
remotePlatformVersion
);
}
}
export function onDisconnect() {
@ -238,4 +250,10 @@ function onDocumentEventAvailable(events) {
}
}
async function getRemotePlatformVersion() {
const deviceFront = await commands.client.mainRoot.getFront("device");
const description = await deviceFront.getDescription();
return description.platformversion;
}
export { clientCommands };

View File

@ -57,6 +57,24 @@
fill: var(--theme-icon-checked-color);
}
.tracer-toolbar .tracer-runtime-version-mismatch {
--icon-size: 16px;
--icon-inline-padding: 4px;
background-color: var(--theme-warning-background);
color: var(--theme-warning-color);
border-block-end: 1px solid var(--theme-splitter-color);
padding: 1em;
padding-inline-start: calc(var(--icon-inline-padding) * 2 + var(--icon-size));
background-image: url(resource://devtools-shared-images/alert-small.svg);
background-position-x: var(--icon-inline-padding);
background-position-y: 50%;
background-repeat: no-repeat;
background-size: var(--icon-size);
-moz-context-properties: fill;
fill: var(--theme-warning-color);
}
.tracer-tab .tracer-message {
display: flex;
justify-content: center;

View File

@ -20,6 +20,7 @@ import {
getAllMutationTraces,
getAllTraceCount,
getIsCurrentlyTracing,
getRuntimeVersions,
} from "../../selectors/index";
const VirtualizedTree = require("resource://devtools/client/shared/components/VirtualizedTree.js");
const FrameView = createFactory(
@ -721,6 +722,8 @@ export class Tracer extends Component {
render() {
const isZoomed = this.state.renderedTraceCount != this.props.traceCount;
const { runtimeVersions } = this.props;
return div(
{
className: "tracer-container",
@ -738,6 +741,16 @@ export class Tracer extends Component {
"This panel is experimental. It may change, regress, be dropped or replaced."
)
: null,
runtimeVersions &&
runtimeVersions.localPlatformVersion !=
runtimeVersions.remotePlatformVersion
? div(
{
className: "tracer-runtime-version-mismatch",
},
`Client and remote runtime have different versions (${runtimeVersions.localPlatformVersion} vs ${runtimeVersions.remotePlatformVersion}) . The Tracer may be broken because of protocol changes between these two versions. Please upgrade or downgrade one of the two to use the same major version.`
)
: null,
this.renderSearchInput()
),
isZoomed
@ -830,6 +843,7 @@ const mapStateToProps = state => {
mutationTraces: getAllMutationTraces(state),
traceCount: getAllTraceCount(state),
selectedTraceIndex: getSelectedTraceIndex(state),
runtimeVersions: getRuntimeVersions(state),
};
};

View File

@ -30,6 +30,10 @@ function initialState() {
// Index of the currently selected trace within `mutableTraces`.
selectedTraceIndex: null,
// Runtime versions to help show warning when there is a mismatch between frontend and backend versions
localPlatformVersion: null,
remotePlatformVersion: null,
};
}
@ -127,6 +131,13 @@ function update(state = initialState(), action) {
selectedTrace: null,
};
}
case "SET_RUNTIME_VERSIONS": {
return {
...state,
localPlatformVersion: action.localPlatformVersion,
remotePlatformVersion: action.remotePlatformVersion,
};
}
}
return state;
}

View File

@ -26,3 +26,9 @@ export function getAllMutationTraces(state) {
export function getAllTraceCount(state) {
return state.tracerFrames?.mutableTraces.length || 0;
}
export function getRuntimeVersions(state) {
return {
localPlatformVersion: state.tracerFrames?.localPlatformVersion,
remotePlatformVersion: state.tracerFrames?.remotePlatformVersion,
};
}

View File

@ -619,9 +619,7 @@ exports.ToolboxButtons = [
"toolbox.buttons.jstracer",
osString == "Darwin" ? "Cmd+Shift+5" : "Ctrl+Shift+5"
),
isToolSupported: toolbox =>
(toolbox.commands.descriptorFront.isLocalTab ||
toolbox.isBrowserToolbox) &&
isToolSupported: () =>
Services.prefs.getBoolPref(
"devtools.debugger.features.javascript-tracing",
false

View File

@ -792,6 +792,14 @@ DevToolsClient.prototype = {
return this._transport;
},
/**
* Boolean flag to help identify client connected to the current runtime,
* via a LocalDevToolsTransport pipe.
*/
get isLocalClient() {
return !!this._transport.isLocalTransport;
},
dumpPools() {
for (const pool of this._pools) {
console.log(`%c${pool.actorID}`, "font-weight: bold;", [

View File

@ -241,6 +241,9 @@ const BOOLEAN_CONFIGURATION_PREFS = {
name: "pauseOverlay",
thread: true,
},
"devtools.debugger.features.javascript-tracing": {
name: "isTracerFeatureEnabled",
},
};
/**

View File

@ -2392,12 +2392,36 @@ async function unregisterServiceWorker(workerUrl) {
* Toggle the JavavaScript tracer via its toolbox toolbar button.
*/
async function toggleJsTracer(toolbox) {
const { isTracingEnabled } = toolbox.commands.tracerCommand;
const { tracerCommand } = toolbox.commands;
const { isTracingEnabled } = tracerCommand;
const { logMethod, traceOnNextInteraction, traceOnNextLoad } =
toolbox.commands.tracerCommand.getTracingOptions();
// When the tracer is waiting for user interaction or page load, it won't be made active
// right away. The test should manually wait for its activation.
const shouldWaitForToggle = !traceOnNextInteraction && !traceOnNextLoad;
let onTracingToggled;
if (shouldWaitForToggle) {
onTracingToggled = new Promise(resolve => {
tracerCommand.on("toggle", async function listener() {
// Ignore the event, if we are still in the same state as before the click
if (tracerCommand.isTracingActive == isTracingEnabled) {
return;
}
tracerCommand.off("toggle", listener);
resolve();
});
});
}
const toolbarButton = toolbox.doc.getElementById("command-button-jstracer");
toolbarButton.click();
if (shouldWaitForToggle) {
info("Waiting for the tracer to be active");
await onTracingToggled;
}
const {
TRACER_LOG_METHODS,
} = require("resource://devtools/shared/specs/tracer.js");

View File

@ -31,6 +31,8 @@ const SUPPORTED_OPTIONS = {
customFormatters: true,
// Set a custom user agent
customUserAgent: true,
// Is the tracer experimental feature manually enabled by the user?
isTracerFeatureEnabled: true,
// Enable JavaScript
javascriptEnabled: true,
// Force a custom device pixel ratio (used in RDM). Set to null to restore origin ratio.

View File

@ -224,6 +224,14 @@ class BaseTargetActor extends Actor {
return this.#instantiatedTargetScopedActors.has(prefix);
}
/**
* Server side boolean to know if the tracer has been enabled by the user.
*
* By enabled, we mean the feature has been exposed to the user,
* not that the tracer is actively tracing executions.
*/
isTracerFeatureEnabled = false;
/**
* Apply target-specific options.
*
@ -238,6 +246,9 @@ class BaseTargetActor extends Actor {
* actor is instantiated.
*/
updateTargetConfiguration(options = {}, calledFromDocumentCreation = false) {
if (typeof options.isTracerFeatureEnabled === "boolean") {
this.isTracerFeatureEnabled = options.isTracerFeatureEnabled;
}
// If there is some tracer options, we should start tracing, otherwise we should stop (if we were)
if (options.tracerOptions) {
// Ignore the SessionData update if the user requested to start the tracer on next page load and:

View File

@ -866,16 +866,12 @@ WebConsoleCommandsManager.register({
name: "trace",
isSideEffectFree: false,
command(owner, args) {
// Disable :trace command on worker until this feature is enabled by default
if (isWorker) {
throw new Error(":trace command isn't supported in workers");
}
// Disable :trace command on worker until this feature is enabled by default
if (
!Services.prefs.getBoolPref(
"devtools.debugger.features.javascript-tracing",
false
)
) {
if (!owner.consoleActor.parentActor.isTracerFeatureEnabled) {
throw new Error(
":trace requires 'devtools.debugger.features.javascript-tracing' preference to be true"
);

View File

@ -25,6 +25,7 @@ types.addDictType("target-configuration.configuration", {
serviceWorkersTestingEnabled: "nullable:boolean",
setTabOffline: "nullable:boolean",
touchEventsOverride: "nullable:string",
isTracerFeatureEnabled: "nullable:boolean",
});
const targetConfigurationSpec = generateActorSpec({

View File

@ -35,6 +35,12 @@ function LocalDebuggerTransport(other) {
}
LocalDebuggerTransport.prototype = {
/**
* Boolean to help identify DevToolsClient instances connected to a LocalDevToolsTransport pipe
* and so connected to the same runtime as the frontend.
*/
isLocalTransport: true,
/**
* Transmit a message by directly calling the onPacket handler of the other
* endpoint.