mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
8b439f0bce
Differential Revision: https://phabricator.services.mozilla.com/D120182
230 lines
8.1 KiB
JavaScript
230 lines
8.1 KiB
JavaScript
/* 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").GetEnvironmentVariable} GetEnvironmentVariable
|
|
* @typedef {import("./@types/perf").GetActiveBrowserID} GetActiveBrowserID
|
|
* @typedef {import("./@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
|
|
* * @typedef {import("./@types/perf").ProfilerViewMode} ProfilerViewMode
|
|
*/
|
|
|
|
const ChromeUtils = require("ChromeUtils");
|
|
const { createLazyLoaders } = ChromeUtils.import(
|
|
"resource://devtools/client/performance-new/typescript-lazy-load.jsm.js"
|
|
);
|
|
|
|
const lazy = createLazyLoaders({
|
|
Chrome: () => require("chrome"),
|
|
Services: () => require("Services"),
|
|
OS: () => ChromeUtils.import("resource://gre/modules/osfile.jsm"),
|
|
PerfSymbolication: () =>
|
|
ChromeUtils.import(
|
|
"resource://devtools/client/performance-new/symbolication.jsm.js"
|
|
),
|
|
});
|
|
|
|
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
|
const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
|
|
const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
|
|
|
|
/** @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";
|
|
|
|
const UI_BASE_URL_DEFAULT = "https://profiler.firefox.com";
|
|
const UI_BASE_URL_PATH_DEFAULT = "/from-addon";
|
|
|
|
/**
|
|
* 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, and injects the profile via a frame script.
|
|
*
|
|
* @param {MinimallyTypedGeckoProfile | ArrayBuffer | {}} profile - The Gecko profile.
|
|
* @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.
|
|
* @param {SymbolicationService} symbolicationService - An object which implements the
|
|
* SymbolicationService interface, whose getSymbolTable method will be invoked
|
|
* when profiler.firefox.com sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This
|
|
* method should obtain a symbol table for the requested binary and resolve the
|
|
* returned promise with it.
|
|
*/
|
|
function openProfilerAndDisplayProfile(
|
|
profile,
|
|
profilerViewMode,
|
|
symbolicationService
|
|
) {
|
|
const Services = lazy.Services();
|
|
// Find the most recently used window, as the DevTools client could be in a variety
|
|
// of hosts.
|
|
const win = Services.wm.getMostRecentWindow("navigator:browser");
|
|
if (!win) {
|
|
throw new Error("No browser window");
|
|
}
|
|
const browser = win.gBrowser;
|
|
win.focus();
|
|
|
|
// 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
|
|
);
|
|
|
|
// 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") {
|
|
viewModeQueryString = "?view=active-tab&implementation=js";
|
|
} else if (profilerViewMode !== undefined && profilerViewMode !== "full") {
|
|
viewModeQueryString = `?view=${profilerViewMode}`;
|
|
}
|
|
|
|
const tab = browser.addWebTab(
|
|
`${baseUrl}${baseUrlPath}${viewModeQueryString}`,
|
|
{
|
|
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
|
|
userContextId: browser.contentPrincipal.userContextId,
|
|
}),
|
|
}
|
|
);
|
|
browser.selectedTab = tab;
|
|
const mm = tab.linkedBrowser.messageManager;
|
|
mm.loadFrameScript(
|
|
"chrome://devtools/content/performance-new/frame-script.js",
|
|
false
|
|
);
|
|
mm.sendAsyncMessage(TRANSFER_EVENT, profile);
|
|
mm.addMessageListener(SYMBOL_TABLE_REQUEST_EVENT, e => {
|
|
const { debugName, breakpadId } = e.data;
|
|
symbolicationService.getSymbolTable(debugName, breakpadId).then(
|
|
result => {
|
|
const [addr, index, buffer] = result;
|
|
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
|
status: "success",
|
|
debugName,
|
|
breakpadId,
|
|
result: [addr, index, buffer],
|
|
});
|
|
},
|
|
error => {
|
|
// Re-wrap the error object into an object that is Structured Clone-able.
|
|
const { name, message, lineNumber, fileName } = error;
|
|
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
|
status: "error",
|
|
debugName,
|
|
breakpadId,
|
|
error: { name, message, lineNumber, fileName },
|
|
});
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
const Services = lazy.Services();
|
|
const { Cc, Ci } = lazy.Chrome();
|
|
const env = Cc["@mozilla.org/process/environment;1"].getService(
|
|
Ci.nsIEnvironment
|
|
);
|
|
env.set(envName, value);
|
|
|
|
Services.startup.quit(
|
|
Services.startup.eForceQuit | Services.startup.eRestart
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets an environment variable from the browser.
|
|
*
|
|
* @type {GetEnvironmentVariable}
|
|
*/
|
|
function getEnvironmentVariable(envName) {
|
|
const { Cc, Ci } = lazy.Chrome();
|
|
const env = Cc["@mozilla.org/process/environment;1"].getService(
|
|
Ci.nsIEnvironment
|
|
);
|
|
return env.get(envName);
|
|
}
|
|
|
|
/**
|
|
* @param {Window} window
|
|
* @param {string[]} objdirs
|
|
* @param {(objdirs: string[]) => unknown} changeObjdirs
|
|
*/
|
|
function openFilePickerForObjdir(window, objdirs, changeObjdirs) {
|
|
const { Cc, Ci } = lazy.Chrome();
|
|
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 = {
|
|
openProfilerAndDisplayProfile,
|
|
sharedLibrariesFromProfile,
|
|
restartBrowserWithEnvironmentVariable,
|
|
getEnvironmentVariable,
|
|
openFilePickerForObjdir,
|
|
};
|