summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance-new/shared/browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/performance-new/shared/browser.js')
-rw-r--r--devtools/client/performance-new/shared/browser.js172
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,
+};