tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser.js (7547B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 // @ts-check
      5 "use strict";
      6 
      7 /**
      8 * @typedef {import("../@types/perf").Action} Action
      9 * @typedef {import("../@types/perf").Library} Library
     10 * @typedef {import("../@types/perf").PerfFront} PerfFront
     11 * @typedef {import("../@types/perf").SymbolTableAsTuple} SymbolTableAsTuple
     12 * @typedef {import("../@types/perf").RecordingState} RecordingState
     13 * @typedef {import("../@types/perf").SymbolicationService} SymbolicationService
     14 * @typedef {import("../@types/perf").PreferenceFront} PreferenceFront
     15 * @typedef {import("../@types/perf").PerformancePref} PerformancePref
     16 * @typedef {import("../@types/perf").RecordingSettings} RecordingSettings
     17 * @typedef {import("../@types/perf").GetActiveBrowserID} GetActiveBrowserID
     18 * @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode
     19 * @typedef {import("../@types/perf").ProfilerPanel} ProfilerPanel
     20 */
     21 
     22 const {
     23  gDevTools,
     24 } = require("resource://devtools/client/framework/devtools.js");
     25 
     26 /** @type {PerformancePref["UIBaseUrl"]} */
     27 const UI_BASE_URL_PREF = "devtools.performance.recording.ui-base-url";
     28 /** @type {PerformancePref["UIBaseUrlPathPref"]} */
     29 const UI_BASE_URL_PATH_PREF = "devtools.performance.recording.ui-base-url-path";
     30 
     31 /** @type {PerformancePref["UIEnableActiveTabView"]} */
     32 const UI_ENABLE_ACTIVE_TAB_PREF =
     33  "devtools.performance.recording.active-tab-view.enabled";
     34 
     35 const UI_BASE_URL_DEFAULT = "https://profiler.firefox.com";
     36 const UI_BASE_URL_PATH_DEFAULT = "/from-browser";
     37 
     38 /**
     39 * This file contains all of the privileged browser-specific functionality. This helps
     40 * keep a clear separation between the privileged and non-privileged client code. It
     41 * is also helpful in being able to mock out browser behavior for tests, without
     42 * worrying about polluting the browser environment.
     43 */
     44 
     45 /**
     46 * Once a profile is received from the actor, it needs to be opened up in
     47 * profiler.firefox.com to be analyzed. This function opens up profiler.firefox.com
     48 * into a new browser tab.
     49 *
     50 * @typedef {object} OpenProfilerOptions
     51 * @property {ProfilerViewMode | undefined} [profilerViewMode] - View mode for the Firefox Profiler
     52 *   front-end timeline. While opening the url, we should append a query string
     53 *   if a view other than "full" needs to be displayed.
     54 * @property {ProfilerPanel} [defaultPanel] Allows to change the default opened panel.
     55 *
     56 * @param {OpenProfilerOptions} options
     57 * @returns {Promise<MockedExports.Browser>} The browser for the opened tab.
     58 */
     59 async function openProfilerTab({ profilerViewMode, defaultPanel }) {
     60  // Allow the user to point to something other than profiler.firefox.com.
     61  const baseUrl = Services.prefs.getStringPref(
     62    UI_BASE_URL_PREF,
     63    UI_BASE_URL_DEFAULT
     64  );
     65  // Allow tests to override the path.
     66  const baseUrlPath = Services.prefs.getStringPref(
     67    UI_BASE_URL_PATH_PREF,
     68    UI_BASE_URL_PATH_DEFAULT
     69  );
     70  const additionalPath = defaultPanel ? `/${defaultPanel}/` : "";
     71  // This controls whether we enable the active tab view when capturing in web
     72  // developer preset.
     73  const enableActiveTab = Services.prefs.getBoolPref(
     74    UI_ENABLE_ACTIVE_TAB_PREF,
     75    false
     76  );
     77 
     78  // We automatically open up the "full" mode if no query string is present.
     79  // `undefined` also means nothing is specified, and it should open the "full"
     80  // timeline view in that case.
     81  let viewModeQueryString = "";
     82  if (profilerViewMode === "active-tab") {
     83    // We're not enabling the active-tab view in all environments until we
     84    // iron out all its issues.
     85    if (enableActiveTab) {
     86      viewModeQueryString = "?view=active-tab&implementation=js";
     87    } else {
     88      viewModeQueryString = "?implementation=js";
     89    }
     90  } else if (profilerViewMode !== undefined && profilerViewMode !== "full") {
     91    viewModeQueryString = `?view=${profilerViewMode}`;
     92  }
     93 
     94  const urlToLoad = `${baseUrl}${baseUrlPath}${additionalPath}${viewModeQueryString}`;
     95 
     96  // Find the most recently used window, as the DevTools client could be in a variety
     97  // of hosts.
     98  // Note that when running from the browser toolbox, there won't be the browser window,
     99  // but only the browser toolbox document.
    100  const win =
    101    Services.wm.getMostRecentBrowserWindow() ||
    102    Services.wm.getMostRecentWindow("devtools:toolbox");
    103  if (!win) {
    104    throw new Error("No browser window");
    105  }
    106  win.focus();
    107 
    108  // The profiler frontend currently doesn't support being loaded in a private
    109  // window, because it does some storage writes in IndexedDB. That's why we
    110  // force the opening of the tab in a non-private window. This might open a new
    111  // non-private window if the only currently opened window is a private window.
    112  const contentBrowser = await new Promise(resolveOnContentBrowserCreated =>
    113    win.openWebLinkIn(urlToLoad, "tab", {
    114      forceNonPrivate: true,
    115      resolveOnContentBrowserCreated,
    116      userContextId: win.gBrowser?.contentPrincipal.userContextId,
    117      relatedToCurrent: true,
    118    })
    119  );
    120  return contentBrowser;
    121 }
    122 
    123 /**
    124 * Restarts the browser with a given environment variable set to a value.
    125 *
    126 * @param {Record<string, string>} env
    127 */
    128 function restartBrowserWithEnvironmentVariable(env) {
    129  for (const [envName, envValue] of Object.entries(env)) {
    130    Services.env.set(envName, envValue);
    131  }
    132 
    133  Services.startup.quit(
    134    Services.startup.eForceQuit | Services.startup.eRestart
    135  );
    136 }
    137 
    138 /**
    139 * @param {Window} window
    140 * @param {string[]} objdirs
    141 * @param {(objdirs: string[]) => unknown} changeObjdirs
    142 */
    143 function openFilePickerForObjdir(window, objdirs, changeObjdirs) {
    144  const FilePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
    145    Ci.nsIFilePicker
    146  );
    147  FilePicker.init(
    148    window.browsingContext,
    149    "Pick build directory",
    150    FilePicker.modeGetFolder
    151  );
    152  FilePicker.open(rv => {
    153    if (rv == FilePicker.returnOK) {
    154      const path = FilePicker.file.path;
    155      if (path && !objdirs.includes(path)) {
    156        const newObjdirs = [...objdirs, path];
    157        changeObjdirs(newObjdirs);
    158      }
    159    }
    160  });
    161 }
    162 
    163 /**
    164 * Try to open the given script with line and column in the tab.
    165 *
    166 * If the profiled tab is not alive anymore, returns without doing anything.
    167 *
    168 * @param {number} tabId
    169 * @param {string} scriptUrl
    170 * @param {number} line
    171 * @param {number} columnOneBased
    172 */
    173 async function openScriptInDebugger(tabId, scriptUrl, line, columnOneBased) {
    174  const win = Services.wm.getMostRecentBrowserWindow();
    175 
    176  // Iterate through all tabs in the current window and find the tab that we want.
    177  const foundTab = win.gBrowser.tabs.find(
    178    tab => tab.linkedBrowser.browserId === tabId
    179  );
    180 
    181  if (!foundTab) {
    182    console.log(`No tab found with the tab id: ${tabId}`);
    183    return;
    184  }
    185 
    186  // If a matching tab was found, switch to it.
    187  win.gBrowser.selectedTab = foundTab;
    188 
    189  // And open the devtools debugger with script.
    190  const toolbox = await gDevTools.showToolboxForTab(foundTab, {
    191    toolId: "jsdebugger",
    192  });
    193 
    194  toolbox.win.focus();
    195 
    196  // In case profiler backend can't retrieve the column number, it can return zero.
    197  const columnZeroBased = columnOneBased > 0 ? columnOneBased - 1 : 0;
    198  await toolbox.viewSourceInDebugger(
    199    scriptUrl,
    200    line,
    201    columnZeroBased,
    202    /* sourceId = */ null,
    203    "ProfilerOpenScript"
    204  );
    205 }
    206 
    207 module.exports = {
    208  openProfilerTab,
    209  restartBrowserWithEnvironmentVariable,
    210  openFilePickerForObjdir,
    211  openScriptInDebugger,
    212 };