Source code
Revision control
Copy as Markdown
Other Tools
/* 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
// @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
* @typedef {import("../@types/perf").ProfilerPanel} ProfilerPanel
*/
/** @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_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.
*
* @typedef {Object} OpenProfilerOptions
* @property {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.
* @property {ProfilerPanel} [defaultPanel] Allows to change the default opened panel.
*
* @param {OpenProfilerOptions} options
* @returns {Promise<MockedExports.Browser>} The browser for the opened tab.
*/
async function openProfilerTab({ profilerViewMode, defaultPanel }) {
// 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
);
const additionalPath = defaultPanel ? `/${defaultPanel}/` : "";
// 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}${additionalPath}${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,
relatedToCurrent: true,
})
);
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.browsingContext,
"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,
};