diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/client/performance-new/shared/browser.js | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/devtools/client/performance-new/shared/browser.js b/devtools/client/performance-new/shared/browser.js new file mode 100644 index 0000000000..1f3b91e278 --- /dev/null +++ b/devtools/client/performance-new/shared/browser.js @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +// @ts-check +"use strict"; + +/** + * @typedef {import("../@types/perf").Action} Action + * @typedef {import("../@types/perf").Library} Library + * @typedef {import("../@types/perf").PerfFront} PerfFront + * @typedef {import("../@types/perf").SymbolTableAsTuple} SymbolTableAsTuple + * @typedef {import("../@types/perf").RecordingState} RecordingState + * @typedef {import("../@types/perf").SymbolicationService} SymbolicationService + * @typedef {import("../@types/perf").PreferenceFront} PreferenceFront + * @typedef {import("../@types/perf").PerformancePref} PerformancePref + * @typedef {import("../@types/perf").RecordingSettings} RecordingSettings + * @typedef {import("../@types/perf").RestartBrowserWithEnvironmentVariable} RestartBrowserWithEnvironmentVariable + * @typedef {import("../@types/perf").GetActiveBrowserID} GetActiveBrowserID + * @typedef {import("../@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile + * * @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode + */ + +/** @type {PerformancePref["UIBaseUrl"]} */ +const UI_BASE_URL_PREF = "devtools.performance.recording.ui-base-url"; +/** @type {PerformancePref["UIBaseUrlPathPref"]} */ +const UI_BASE_URL_PATH_PREF = "devtools.performance.recording.ui-base-url-path"; + +/** @type {PerformancePref["UIEnableActiveTabView"]} */ +const UI_ENABLE_ACTIVE_TAB_PREF = + "devtools.performance.recording.active-tab-view.enabled"; + +const UI_BASE_URL_DEFAULT = "https://profiler.firefox.com"; +const UI_BASE_URL_PATH_DEFAULT = "/from-browser"; + +/** + * This file contains all of the privileged browser-specific functionality. This helps + * keep a clear separation between the privileged and non-privileged client code. It + * is also helpful in being able to mock out browser behavior for tests, without + * worrying about polluting the browser environment. + */ + +/** + * Once a profile is received from the actor, it needs to be opened up in + * profiler.firefox.com to be analyzed. This function opens up profiler.firefox.com + * into a new browser tab. + * @param {ProfilerViewMode | undefined} profilerViewMode - View mode for the Firefox Profiler + * front-end timeline. While opening the url, we should append a query string + * if a view other than "full" needs to be displayed. + * @returns {Promise<MockedExports.Browser>} The browser for the opened tab. + */ +async function openProfilerTab(profilerViewMode) { + // Allow the user to point to something other than profiler.firefox.com. + const baseUrl = Services.prefs.getStringPref( + UI_BASE_URL_PREF, + UI_BASE_URL_DEFAULT + ); + // Allow tests to override the path. + const baseUrlPath = Services.prefs.getStringPref( + UI_BASE_URL_PATH_PREF, + UI_BASE_URL_PATH_DEFAULT + ); + // This controls whether we enable the active tab view when capturing in web + // developer preset. + const enableActiveTab = Services.prefs.getBoolPref( + UI_ENABLE_ACTIVE_TAB_PREF, + false + ); + + // We automatically open up the "full" mode if no query string is present. + // `undefined` also means nothing is specified, and it should open the "full" + // timeline view in that case. + let viewModeQueryString = ""; + if (profilerViewMode === "active-tab") { + // We're not enabling the active-tab view in all environments until we + // iron out all its issues. + if (enableActiveTab) { + viewModeQueryString = "?view=active-tab&implementation=js"; + } else { + viewModeQueryString = "?implementation=js"; + } + } else if (profilerViewMode !== undefined && profilerViewMode !== "full") { + viewModeQueryString = `?view=${profilerViewMode}`; + } + + const urlToLoad = `${baseUrl}${baseUrlPath}${viewModeQueryString}`; + + // Find the most recently used window, as the DevTools client could be in a variety + // of hosts. + // Note that when running from the browser toolbox, there won't be the browser window, + // but only the browser toolbox document. + const win = + Services.wm.getMostRecentWindow("navigator:browser") || + Services.wm.getMostRecentWindow("devtools:toolbox"); + if (!win) { + throw new Error("No browser window"); + } + win.focus(); + + // The profiler frontend currently doesn't support being loaded in a private + // window, because it does some storage writes in IndexedDB. That's why we + // force the opening of the tab in a non-private window. This might open a new + // non-private window if the only currently opened window is a private window. + const contentBrowser = await new Promise(resolveOnContentBrowserCreated => + win.openWebLinkIn(urlToLoad, "tab", { + forceNonPrivate: true, + resolveOnContentBrowserCreated, + userContextId: win.gBrowser?.contentPrincipal.userContextId, + }) + ); + return contentBrowser; +} + +/** + * Flatten all the sharedLibraries of the different processes in the profile + * into one list of libraries. + * @param {MinimallyTypedGeckoProfile} profile - The profile JSON object + * @returns {Library[]} + */ +function sharedLibrariesFromProfile(profile) { + /** + * @param {MinimallyTypedGeckoProfile} processProfile + * @returns {Library[]} + */ + function getLibsRecursive(processProfile) { + return processProfile.libs.concat( + ...processProfile.processes.map(getLibsRecursive) + ); + } + + return getLibsRecursive(profile); +} + +/** + * Restarts the browser with a given environment variable set to a value. + * + * @type {RestartBrowserWithEnvironmentVariable} + */ +function restartBrowserWithEnvironmentVariable(envName, value) { + Services.env.set(envName, value); + + Services.startup.quit( + Services.startup.eForceQuit | Services.startup.eRestart + ); +} + +/** + * @param {Window} window + * @param {string[]} objdirs + * @param {(objdirs: string[]) => unknown} changeObjdirs + */ +function openFilePickerForObjdir(window, objdirs, changeObjdirs) { + const FilePicker = Cc["@mozilla.org/filepicker;1"].createInstance( + Ci.nsIFilePicker + ); + FilePicker.init(window, "Pick build directory", FilePicker.modeGetFolder); + FilePicker.open(rv => { + if (rv == FilePicker.returnOK) { + const path = FilePicker.file.path; + if (path && !objdirs.includes(path)) { + const newObjdirs = [...objdirs, path]; + changeObjdirs(newObjdirs); + } + } + }); +} + +module.exports = { + openProfilerTab, + sharedLibrariesFromProfile, + restartBrowserWithEnvironmentVariable, + openFilePickerForObjdir, +}; |