diff --git a/devtools/client/aboutdebugging/src/actions/ui.js b/devtools/client/aboutdebugging/src/actions/ui.js index 269b944d22f0..7e816cd0419d 100644 --- a/devtools/client/aboutdebugging/src/actions/ui.js +++ b/devtools/client/aboutdebugging/src/actions/ui.js @@ -24,6 +24,7 @@ const { SELECT_PAGE_SUCCESS, SELECTED_RUNTIME_ID_UPDATED, SHOW_PROFILER_DIALOG, + SWITCH_PROFILER_CONTEXT, USB_RUNTIMES_SCAN_START, USB_RUNTIMES_SCAN_SUCCESS, } = require("devtools/client/aboutdebugging/src/constants"); @@ -109,6 +110,14 @@ function showProfilerDialog() { return { type: SHOW_PROFILER_DIALOG }; } +/** + * The profiler can switch between "devtools-remote" and "aboutprofiling-remote" + * page contexts. + */ +function switchProfilerContext(profilerContext) { + return { type: SWITCH_PROFILER_CONTEXT, profilerContext }; +} + function hideProfilerDialog() { return { type: HIDE_PROFILER_DIALOG }; } @@ -182,6 +191,7 @@ module.exports = { scanUSBRuntimes, selectPage, showProfilerDialog, + switchProfilerContext, uninstallAdbAddon, updateAdbAddonStatus, updateAdbReady, diff --git a/devtools/client/aboutdebugging/src/components/ProfilerDialog.css b/devtools/client/aboutdebugging/src/components/ProfilerDialog.css index 84330bb933fb..0c79c9b2ceae 100644 --- a/devtools/client/aboutdebugging/src/components/ProfilerDialog.css +++ b/devtools/client/aboutdebugging/src/components/ProfilerDialog.css @@ -34,11 +34,19 @@ .profiler-dialog__inner { background-color: var(--white-100); display: grid; - height: calc(var(--base-unit) * 150); /* 600px */ grid-template-rows: max-content auto; max-height: calc(100% - calc(var(--base-unit) * 25)); /* 100% - 100px */ position: fixed; +} + +.profiler-dialog__inner--medium { width: calc(var(--base-unit) * 150); /* 600px */ + height: calc(var(--base-unit) * 150); /* 600px */ +} + +.profiler-dialog__inner--large { + width: calc(var(--base-unit) * 200); /* 800px */ + height: calc(var(--base-unit) * 175); /* 700px */ } .profiler-dialog__mask { diff --git a/devtools/client/aboutdebugging/src/components/ProfilerDialog.js b/devtools/client/aboutdebugging/src/components/ProfilerDialog.js index d1e87ccbe964..6a6b837bfc17 100644 --- a/devtools/client/aboutdebugging/src/components/ProfilerDialog.js +++ b/devtools/client/aboutdebugging/src/components/ProfilerDialog.js @@ -4,6 +4,7 @@ "use strict"; +const { connect } = require("devtools/client/shared/vendor/react-redux"); const { createFactory, PureComponent, @@ -16,24 +17,80 @@ const Localized = createFactory(FluentReact.Localized); const Actions = require("devtools/client/aboutdebugging/src/actions/index"); const Types = require("devtools/client/aboutdebugging/src/types/index"); +const { + PROFILER_PAGE_CONTEXT, +} = require("devtools/client/aboutdebugging/src/constants"); /** - * This component a modal dialog containing the performance profiler UI. + * This component is a modal dialog containing the performance profiler UI. It uses + * the simplified DevTools panel located in devtools/client/performance-new. When + * using a custom preset, and editing the settings, the page context switches + * to about:profiling, which receives the PerfFront of the remote debuggee. */ class ProfilerDialog extends PureComponent { static get propTypes() { return { runtimeDetails: Types.runtimeDetails.isRequired, - dispatch: PropTypes.func.isRequired, + profilerContext: PropTypes.string.isRequired, + hideProfilerDialog: PropTypes.func.isRequired, + switchProfilerContext: PropTypes.func.isRequired, }; } hide() { - this.props.dispatch(Actions.hideProfilerDialog()); + this.props.hideProfilerDialog(); + } + + /** + * The profiler iframe can either be the simplified devtools recording panel, + * or the more detailed about:profiling settings page. + */ + renderProfilerIframe() { + const { + runtimeDetails: { clientWrapper }, + switchProfilerContext, + profilerContext, + } = this.props; + + let src, onLoad; + + switch (profilerContext) { + case PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE: + src = clientWrapper.getPerformancePanelUrl(); + onLoad = e => { + clientWrapper.loadPerformanceProfiler(e.target.contentWindow, () => { + switchProfilerContext(PROFILER_PAGE_CONTEXT.ABOUTPROFILING_REMOTE); + }); + }; + break; + + case PROFILER_PAGE_CONTEXT.ABOUTPROFILING_REMOTE: + src = "about:profiling#remote"; + onLoad = e => { + clientWrapper.loadAboutProfiling(e.target.contentWindow, () => { + switchProfilerContext(PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE); + }); + }; + break; + + default: + throw new Error(`Unhandled profiler context: "${profilerContext}"`); + } + + return dom.iframe({ + key: profilerContext, + className: "profiler-dialog__frame", + src, + onLoad, + }); } render() { - const { clientWrapper } = this.props.runtimeDetails; + const { profilerContext, switchProfilerContext } = this.props; + const dialogSizeClassName = + profilerContext === PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE + ? "profiler-dialog__inner--medium" + : "profiler-dialog__inner--large"; return dom.div( { @@ -42,7 +99,7 @@ class ProfilerDialog extends PureComponent { }, dom.article( { - className: "profiler-dialog__inner qa-profiler-dialog", + className: `profiler-dialog__inner ${dialogSizeClassName} qa-profiler-dialog`, onClick: e => e.stopPropagation(), }, dom.header( @@ -63,23 +120,37 @@ class ProfilerDialog extends PureComponent { dom.button( { className: "ghost-button qa-profiler-dialog-close", - onClick: () => this.hide(), + onClick: () => { + if (profilerContext === PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE) { + this.hide(); + } else { + switchProfilerContext(PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE); + } + }, }, dom.img({ src: "chrome://devtools/skin/images/close.svg", }) ) ), - dom.iframe({ - className: "profiler-dialog__frame", - src: clientWrapper.getPerformancePanelUrl(), - onLoad: e => { - clientWrapper.loadPerformanceProfiler(e.target.contentWindow); - }, - }) + this.renderProfilerIframe() ) ); } } -module.exports = ProfilerDialog; +const mapStateToProps = state => { + return { + profilerContext: state.ui.profilerContext, + }; +}; + +const mapDispatchToProps = { + hideProfilerDialog: Actions.hideProfilerDialog, + switchProfilerContext: Actions.switchProfilerContext, +}; + +module.exports = connect( + mapStateToProps, + mapDispatchToProps +)(ProfilerDialog); diff --git a/devtools/client/aboutdebugging/src/constants.js b/devtools/client/aboutdebugging/src/constants.js index 1d1abb6701d2..c9a85affbd27 100644 --- a/devtools/client/aboutdebugging/src/constants.js +++ b/devtools/client/aboutdebugging/src/constants.js @@ -28,6 +28,7 @@ const actionTypes = { DISCONNECT_RUNTIME_START: "DISCONNECT_RUNTIME_START", DISCONNECT_RUNTIME_SUCCESS: "DISCONNECT_RUNTIME_SUCCESS", HIDE_PROFILER_DIALOG: "HIDE_PROFILER_DIALOG", + SWITCH_PROFILER_CONTEXT: "SWITCH_PROFILER_CONTEXT", NETWORK_LOCATIONS_UPDATE_FAILURE: "NETWORK_LOCATIONS_UPDATE_FAILURE", NETWORK_LOCATIONS_UPDATE_START: "NETWORK_LOCATIONS_UPDATE_START", NETWORK_LOCATIONS_UPDATE_SUCCESS: "NETWORK_LOCATIONS_UPDATE_SUCCESS", @@ -150,6 +151,16 @@ const USB_STATES = { UPDATING_USB: "UPDATING_USB", }; +/** + * These constants reference the performance-new's concept of a PageContext. + * These are defined in devtools/client/performance-new/@types/perf.d.ts + * about:debugging only uses the remote variants of the PageContexts. + */ +const PROFILER_PAGE_CONTEXT = { + DEVTOOLS_REMOTE: "devtools-remote", + ABOUTPROFILING_REMOTE: "aboutprofiling-remote", +}; + // flatten constants module.exports = Object.assign( {}, @@ -165,6 +176,7 @@ module.exports = Object.assign( SERVICE_WORKER_FETCH_STATES, SERVICE_WORKER_STATUSES, USB_STATES, + PROFILER_PAGE_CONTEXT, }, actionTypes ); diff --git a/devtools/client/aboutdebugging/src/modules/client-wrapper.js b/devtools/client/aboutdebugging/src/modules/client-wrapper.js index 5478f1097d09..b0d21f14ce33 100644 --- a/devtools/client/aboutdebugging/src/modules/client-wrapper.js +++ b/devtools/client/aboutdebugging/src/modules/client-wrapper.js @@ -174,10 +174,22 @@ class ClientWrapper { return "chrome://devtools/content/performance-new/index.xhtml"; } - async loadPerformanceProfiler(win) { + /** + * @param {Window} win - The window of the dialog window. + * @param {Function} openAboutProfiling + */ + async loadPerformanceProfiler(win, openAboutProfiling) { const perfFront = await this.getFront("perf"); - const perfActorVersion = this.client.mainRoot.traits.perfActorVersion; - win.gInit(perfFront, "devtools-remote", perfActorVersion); + win.gInit(perfFront, "devtools-remote", openAboutProfiling); + } + + /** + * @param {Window} win - The window of the dialog window. + * @param {Function} openRemoteDevTools + */ + async loadAboutProfiling(win, openRemoteDevTools) { + const perfFront = await this.getFront("perf"); + win.gInit(perfFront, "aboutprofiling-remote", openRemoteDevTools); } } diff --git a/devtools/client/aboutdebugging/src/reducers/ui-state.js b/devtools/client/aboutdebugging/src/reducers/ui-state.js index 9196da5755f2..bb7ca6f0ec08 100644 --- a/devtools/client/aboutdebugging/src/reducers/ui-state.js +++ b/devtools/client/aboutdebugging/src/reducers/ui-state.js @@ -10,8 +10,10 @@ const { DEBUG_TARGET_COLLAPSIBILITY_UPDATED, HIDE_PROFILER_DIALOG, NETWORK_LOCATIONS_UPDATE_SUCCESS, + PROFILER_PAGE_CONTEXT, SELECT_PAGE_SUCCESS, SHOW_PROFILER_DIALOG, + SWITCH_PROFILER_CONTEXT, TEMPORARY_EXTENSION_INSTALL_FAILURE, TEMPORARY_EXTENSION_INSTALL_SUCCESS, USB_RUNTIMES_SCAN_START, @@ -29,6 +31,7 @@ function UiState( isAdbReady: false, isScanningUsb: false, networkLocations: locations, + profilerContext: PROFILER_PAGE_CONTEXT.DEVTOOLS_REMOTE, selectedPage: null, showProfilerDialog: false, showHiddenAddons, @@ -68,13 +71,22 @@ function uiReducer(state = UiState(), action) { } case SHOW_PROFILER_DIALOG: { - return Object.assign({}, state, { showProfilerDialog: true }); + return Object.assign({}, state, { + showProfilerDialog: true, + // Always start in the devtools-remote view. + profilerContext: "devtools-remote", + }); } case HIDE_PROFILER_DIALOG: { return Object.assign({}, state, { showProfilerDialog: false }); } + case SWITCH_PROFILER_CONTEXT: { + const { profilerContext } = action; + return Object.assign({}, state, { profilerContext }); + } + case USB_RUNTIMES_SCAN_START: { return Object.assign({}, state, { isScanningUsb: true }); } diff --git a/devtools/client/performance-new/@types/perf.d.ts b/devtools/client/performance-new/@types/perf.d.ts index 78bf9ec59ca5..5b9ffd7b39dc 100644 --- a/devtools/client/performance-new/@types/perf.d.ts +++ b/devtools/client/performance-new/@types/perf.d.ts @@ -58,6 +58,7 @@ export interface PerfFront { isLockedForPrivateBrowsing: () => MaybePromise; on: (type: string, listener: () => void) => void; off: (type: string, listener: () => void) => void; + destroy: () => void, /** * This method was was added in Firefox 72. */ @@ -97,7 +98,11 @@ export type RecordingState = // We are currently migrating to a new UX workflow with about:profiling. // This type provides an easy way to change the implementation based // on context. -export type PageContext = "devtools" | "devtools-remote" | "aboutprofiling"; +export type PageContext = + | "devtools" + | "devtools-remote" + | "aboutprofiling" + | "aboutprofiling-remote"; export interface State { recordingState: RecordingState; @@ -221,6 +226,11 @@ export interface InitializedValues { // by the actor system. This compatibility can be required when the ESR version // is running at least Firefox 72. supportedFeatures: string[] | null + // Allow different devtools contexts to open about:profiling with different methods. + // e.g. via a new tab, or page navigation. + openAboutProfiling?: () => void, + // Allow about:profiling to switch back to the remote devtools panel. + openRemoteDevTools?: () => void, } /** @@ -268,6 +278,8 @@ export type Action = setRecordingPreferences: SetRecordingPreferences; presets: Presets; pageContext: PageContext; + openAboutProfiling?: () => void, + openRemoteDevTools?: () => void, recordingSettingsFromPreferences: RecordingStateFromPreferences; getSymbolTableGetter: (profile: object) => GetSymbolTableCallback; supportedFeatures: string[] | null; @@ -287,6 +299,8 @@ export interface InitializeStoreValues { recordingPreferences: RecordingStateFromPreferences; supportedFeatures: string[] | null; getSymbolTableGetter: (profile: object) => GetSymbolTableCallback; + openAboutProfiling?: () => void; + openRemoteDevTools?: () => void; } export type PopupBackgroundFeatures = { [feature: string]: boolean }; diff --git a/devtools/client/performance-new/aboutprofiling/initializer.js b/devtools/client/performance-new/aboutprofiling/initializer.js index a7c723dd1fb7..738713538dd0 100644 --- a/devtools/client/performance-new/aboutprofiling/initializer.js +++ b/devtools/client/performance-new/aboutprofiling/initializer.js @@ -6,6 +6,8 @@ * @typedef {import("../@types/perf").InitializeStoreValues} InitializeStoreValues * @typedef {import("../@types/perf").PopupWindow} PopupWindow * @typedef {import("../@types/perf").RecordingStateFromPreferences} RecordingStateFromPreferences + * @typedef {import("../@types/perf").PerfFront} PerfFront + * @typedef {import("../@types/perf").PageContext} PageContext */ "use strict"; @@ -71,33 +73,37 @@ const { /** * Initialize the panel by creating a redux store, and render the root component. + * + * @param {PerfFront} perfFront - The Perf actor's front. Used to start and stop recordings. + * @param {PageContext} pageContext - The context that the UI is being loaded in under. + * @param {(() => void)} [openRemoteDevTools] Optionally provide a way to go back to + * the remote devtools page. */ - -document.addEventListener("DOMContentLoaded", async () => { +async function gInit(perfFront, pageContext, openRemoteDevTools) { const store = createStore(reducers); - const perfFrontInterface = new ActorReadyGeckoProfilerInterface(); - const supportedFeatures = await perfFrontInterface.getSupportedFeatures(); + const supportedFeatures = await perfFront.getSupportedFeatures(); // Do some initialization, especially with privileged things that are part of the // the browser. store.dispatch( actions.initializeStore({ - perfFront: perfFrontInterface, + perfFront, receiveProfile, supportedFeatures, presets, // Get the preferences from the current browser - recordingPreferences: getRecordingPreferences("aboutprofiling"), + recordingPreferences: getRecordingPreferences(pageContext), /** * @param {RecordingStateFromPreferences} newRecordingPreferences */ setRecordingPreferences: newRecordingPreferences => - setRecordingPreferences("aboutprofiling", newRecordingPreferences), + setRecordingPreferences(pageContext, newRecordingPreferences), // The popup doesn't need to support remote symbol tables from the debuggee. // Only get the symbols from this browser. getSymbolTableGetter: () => getSymbolsFromThisBrowser, - pageContext: "aboutprofiling", + pageContext, + openRemoteDevTools, }) ); @@ -116,8 +122,20 @@ document.addEventListener("DOMContentLoaded", async () => { ); window.addEventListener("unload", function() { - // The perf front interface needs to be unloaded in order to remove event handlers. - // Not doing so leads to leaks. - perfFrontInterface.destroy(); + // Do not destroy the perf front if working remotely, about:debugging will do + // this for us. + if (pageContext !== "aboutprofiling-remote") { + // The perf front interface needs to be unloaded in order to remove event handlers. + // Not doing so leads to leaks. + perfFront.destroy(); + } }); -}); +} + +// Automatically initialize the page if it's not a remote connection, otherwise +// the page will be initialized by about:debugging. +if (window.location.hash !== "#remote") { + document.addEventListener("DOMContentLoaded", () => { + gInit(new ActorReadyGeckoProfilerInterface(), "aboutprofiling"); + }); +} diff --git a/devtools/client/performance-new/components/AboutProfiling.js b/devtools/client/performance-new/components/AboutProfiling.js index f4661796ea09..3c647314ed85 100644 --- a/devtools/client/performance-new/components/AboutProfiling.js +++ b/devtools/client/performance-new/components/AboutProfiling.js @@ -13,6 +13,7 @@ * @property {boolean?} isSupportedPlatform * @property {PageContext} pageContext * @property {string | null} promptEnvRestart + * @property {(() => void) | undefined} openRemoteDevTools */ /** @@ -70,7 +71,12 @@ class AboutProfiling extends PureComponent { } render() { - const { isSupportedPlatform, pageContext, promptEnvRestart } = this.props; + const { + isSupportedPlatform, + pageContext, + promptEnvRestart, + openRemoteDevTools, + } = this.props; if (isSupportedPlatform === null) { // We don't know yet if this is a supported platform, wait for a response. @@ -100,6 +106,21 @@ class AboutProfiling extends PureComponent { ) ) : null, + + openRemoteDevTools + ? div( + { className: "perf-back" }, + button( + { + className: "perf-back-button", + type: "button", + onClick: openRemoteDevTools, + }, + "Save settings and go back" + ) + ) + : null, + div( { className: "perf-intro" }, h1({ className: "perf-intro-title" }, "Profiler Settings"), @@ -130,6 +151,7 @@ function mapStateToProps(state) { isSupportedPlatform: selectors.getIsSupportedPlatform(state), pageContext: selectors.getPageContext(state), promptEnvRestart: selectors.getPromptEnvRestart(state), + openRemoteDevTools: selectors.getOpenRemoteDevTools(state), }; } diff --git a/devtools/client/performance-new/components/DevToolsPresetSelection.js b/devtools/client/performance-new/components/DevToolsPresetSelection.js index 55107a18ee4c..94abc90b4a09 100644 --- a/devtools/client/performance-new/components/DevToolsPresetSelection.js +++ b/devtools/client/performance-new/components/DevToolsPresetSelection.js @@ -14,6 +14,7 @@ * @property {number} interval * @property {string[]} threads * @property {string[]} features + * @property {() => void} openAboutProfiling * @property {import("../@types/perf").Presets} presets */ @@ -61,7 +62,6 @@ class DevToolsPresetSelection extends PureComponent { constructor(props) { super(props); this.onPresetChange = this.onPresetChange.bind(this); - this.handleLinkClick = this.handleLinkClick.bind(this); /** * Create an object map to easily look up feature description. @@ -82,13 +82,8 @@ class DevToolsPresetSelection extends PureComponent { this.props.changePreset(presets, event.target.value); } - handleLinkClick() { - const { openTrustedLink } = require("devtools/client/shared/link"); - openTrustedLink("about:profiling", {}); - } - render() { - const { presetName, presets } = this.props; + const { presetName, presets, openAboutProfiling } = this.props; let presetDescription; const currentPreset = presets[presetName]; @@ -124,7 +119,7 @@ class DevToolsPresetSelection extends PureComponent { }) ), button( - { className: "perf-external-link", onClick: this.handleLinkClick }, + { className: "perf-external-link", onClick: openAboutProfiling }, "Edit Settingsā€¦" ) ); @@ -170,6 +165,7 @@ function mapStateToProps(state) { interval: selectors.getInterval(state), threads: selectors.getThreads(state), features: selectors.getFeatures(state), + openAboutProfiling: selectors.getOpenAboutProfiling(state), }; } diff --git a/devtools/client/performance-new/initializer.js b/devtools/client/performance-new/initializer.js index f0fba59e19ca..5fc37277228d 100644 --- a/devtools/client/performance-new/initializer.js +++ b/devtools/client/performance-new/initializer.js @@ -71,8 +71,9 @@ const { * * @param {PerfFront} perfFront - The Perf actor's front. Used to start and stop recordings. * @param {PageContext} pageContext - The context that the UI is being loaded in under. + * @param {(() => void)?} openAboutProfiling - Optional call to open about:profiling */ -async function gInit(perfFront, pageContext) { +async function gInit(perfFront, pageContext, openAboutProfiling) { const store = createStore(reducers); // Get the supported features from the debuggee. If the debuggee is before // Firefox 72, then return null, as the actor does not support that feature. @@ -86,6 +87,13 @@ async function gInit(perfFront, pageContext) { perfFront.getSupportedFeatures() ).catch(() => null); + if (!openAboutProfiling) { + openAboutProfiling = () => { + const { openTrustedLink } = require("devtools/client/shared/link"); + openTrustedLink("about:profiling", {}); + }; + } + // Do some initialization, especially with privileged things that are part of the // the browser. store.dispatch( @@ -95,6 +103,7 @@ async function gInit(perfFront, pageContext) { recordingPreferences: getRecordingPreferences(pageContext), presets, supportedFeatures, + openAboutProfiling, pageContext: "devtools", // Go ahead and hide the implementation details for the component on how the diff --git a/devtools/client/performance-new/popup/background.jsm.js b/devtools/client/performance-new/popup/background.jsm.js index e995c3a1e102..253b3f87e80d 100644 --- a/devtools/client/performance-new/popup/background.jsm.js +++ b/devtools/client/performance-new/popup/background.jsm.js @@ -330,6 +330,7 @@ function getPrefPostfix(pageContext) { // Don't use any postfix on the prefs. return ""; case "devtools-remote": + case "aboutprofiling-remote": return ".remote"; default: { const { UnhandledCaseError } = lazyUtils(); diff --git a/devtools/client/performance-new/store/reducers.js b/devtools/client/performance-new/store/reducers.js index 8d2a010bdd56..40a07ecc8650 100644 --- a/devtools/client/performance-new/store/reducers.js +++ b/devtools/client/performance-new/store/reducers.js @@ -185,6 +185,8 @@ function initializedValues(state = null, action) { pageContext: action.pageContext, getSymbolTableGetter: action.getSymbolTableGetter, supportedFeatures: action.supportedFeatures, + openAboutProfiling: action.openAboutProfiling, + openRemoteDevTools: action.openRemoteDevTools, }; default: return state; diff --git a/devtools/client/performance-new/store/selectors.js b/devtools/client/performance-new/store/selectors.js index cce12a976eb9..3f7b15211282 100644 --- a/devtools/client/performance-new/store/selectors.js +++ b/devtools/client/performance-new/store/selectors.js @@ -56,6 +56,28 @@ const getPresets = state => getInitializedValues(state).presets; /** @type {Selector} */ const getPresetName = state => state.presetName; +/** + * When remote profiling, there will be a back button to the settings. + * + * @type {Selector<(() => void) | undefined>} + */ +const getOpenRemoteDevTools = state => + getInitializedValues(state).openRemoteDevTools; + +/** + * Get the functon to open about:profiling. This assumes that the function exists, + * otherwise it will throw an error. + * + * @type {Selector<() => void>} + */ +const getOpenAboutProfiling = state => { + const { openAboutProfiling } = getInitializedValues(state); + if (!openAboutProfiling) { + throw new Error("Expected to get an openAboutProfiling function."); + } + return openAboutProfiling; +}; + /** * Warning! This function returns a new object on every run, and so should not * be used directly as a React prop. @@ -138,6 +160,8 @@ module.exports = { getObjdirs, getPresets, getPresetName, + getOpenRemoteDevTools, + getOpenAboutProfiling, getRecordingSettings, getInitializedValues, getPerfFront, diff --git a/devtools/client/themes/aboutprofiling.css b/devtools/client/themes/aboutprofiling.css index 74409676209e..e7c00080741d 100644 --- a/devtools/client/themes/aboutprofiling.css +++ b/devtools/client/themes/aboutprofiling.css @@ -196,3 +196,13 @@ h2 { display: flex; align-items: start; } + +.perf-back { + margin-block-end: 13px; +} + +.perf-back-button { + /* Remove the inherited margins */ + margin-left: 0; + margin-right: 0; +}