summaryrefslogtreecommitdiffstats
path: root/devtools/client/performance-new/shared/browser.js
blob: c97bb0a0ab908a9553d8a799814f4e02c84db02b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* 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,
      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, "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,
};