tor-browser

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

browser_startup.js (9190B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /* This test records at which phase of startup the JS modules are first
      5 * loaded.
      6 * If you made changes that cause this test to fail, it's likely because you
      7 * are loading more JS code during startup.
      8 * Most code has no reason to run off of the app-startup notification
      9 * (this is very early, before we have selected the user profile, so
     10 *  preferences aren't accessible yet).
     11 * If your code isn't strictly required to show the first browser window,
     12 * it shouldn't be loaded before we are done with first paint.
     13 * Finally, if your code isn't really needed during startup, it should not be
     14 * loaded before we have started handling user events.
     15 */
     16 
     17 "use strict";
     18 
     19 /* Set this to true only for debugging purpose; it makes the output noisy. */
     20 const kDumpAllStacks = false;
     21 
     22 const startupPhases = {
     23  // For app-startup, we have an allowlist of acceptable JS files.
     24  // Anything loaded during app-startup must have a compelling reason
     25  // to run before we have even selected the user profile.
     26  // Consider loading your code after first paint instead,
     27  // eg. from BrowserGlue.sys.mjs' _onFirstWindowLoaded method).
     28  "before profile selection": {
     29    allowlist: {
     30      modules: new Set([
     31        "resource:///modules/BrowserGlue.sys.mjs",
     32        "moz-src:///browser/components/DesktopActorRegistry.sys.mjs",
     33        "resource:///modules/StartupRecorder.sys.mjs",
     34        "resource://gre/modules/AppConstants.sys.mjs",
     35        "resource://gre/modules/ActorManagerParent.sys.mjs",
     36        "resource://gre/modules/CustomElementsListener.sys.mjs",
     37        "resource://gre/modules/MainProcessSingleton.sys.mjs",
     38        "resource://gre/modules/XPCOMUtils.sys.mjs",
     39      ]),
     40    },
     41  },
     42 
     43  // For the following phases of startup we have only a list of files that
     44  // are **not** allowed to load in this phase, as too many other scripts
     45  // load during this time.
     46 
     47  // We are at this phase after creating the first browser window (ie. after final-ui-startup).
     48  "before opening first browser window": {
     49    denylist: {
     50      modules: new Set([]),
     51    },
     52  },
     53 
     54  // We reach this phase right after showing the first browser window.
     55  // This means that anything already loaded at this point has been loaded
     56  // before first paint and delayed it.
     57  "before first paint": {
     58    denylist: {
     59      modules: new Set([
     60        "resource:///modules/AboutNewTab.sys.mjs",
     61        "resource:///modules/BrowserUsageTelemetry.sys.mjs",
     62        "resource:///modules/ContentCrashHandlers.sys.mjs",
     63        "moz-src:///browser/components/shell/ShellService.sys.mjs",
     64        "resource://gre/modules/NewTabUtils.sys.mjs",
     65        "resource://gre/modules/PageThumbs.sys.mjs",
     66        "resource://gre/modules/PlacesUtils.sys.mjs",
     67        "resource://gre/modules/Preferences.sys.mjs",
     68        "resource://gre/modules/SearchService.sys.mjs",
     69        // Sqlite.sys.mjs commented out because of bug 1828735.
     70        // "resource://gre/modules/Sqlite.sys.mjs"
     71      ]),
     72      services: new Set(["@mozilla.org/browser/search-service;1"]),
     73    },
     74  },
     75 
     76  // We are at this phase once we are ready to handle user events.
     77  // Anything loaded at this phase or before gets in the way of the user
     78  // interacting with the first browser window.
     79  "before handling user events": {
     80    denylist: {
     81      modules: new Set([
     82        "resource://gre/modules/Blocklist.sys.mjs",
     83        // Bug 1391495 - BrowserWindowTracker.sys.mjs is intermittently used.
     84        // "resource:///modules/BrowserWindowTracker.sys.mjs",
     85        "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
     86        "resource://gre/modules/Bookmarks.sys.mjs",
     87        "resource://gre/modules/ContextualIdentityService.sys.mjs",
     88        "resource://gre/modules/FxAccounts.sys.mjs",
     89        "resource://gre/modules/FxAccountsStorage.sys.mjs",
     90        "resource://gre/modules/PlacesSyncUtils.sys.mjs",
     91        "resource://gre/modules/PushComponents.sys.mjs",
     92      ]),
     93      services: new Set(["@mozilla.org/browser/nav-bookmarks-service;1"]),
     94    },
     95  },
     96 
     97  // Things that are expected to be completely out of the startup path
     98  // and loaded lazily when used for the first time by the user should
     99  // be listed here.
    100  "before becoming idle": {
    101    denylist: {
    102      modules: new Set([
    103        "resource://gre/modules/AsyncPrefs.sys.mjs",
    104        "resource://gre/modules/LoginManagerContextMenu.sys.mjs",
    105        "resource://pdf.js/PdfStreamConverter.sys.mjs",
    106      ]),
    107    },
    108  },
    109 };
    110 
    111 if (AppConstants.platform == "win") {
    112  // On Windows we call checkForLaunchOnLogin early in startup.
    113  startupPhases["before profile selection"].allowlist.modules.add(
    114    "moz-src:///browser/components/shell/StartupOSIntegration.sys.mjs"
    115  );
    116 }
    117 
    118 if (
    119  Services.prefs.getBoolPref("browser.startup.blankWindow") &&
    120  (Services.prefs.getCharPref(
    121    "extensions.activeThemeID",
    122    "default-theme@mozilla.org"
    123  ) == "default-theme@mozilla.org" ||
    124    AppConstants.MOZ_DEV_EDITION) // See bug 1979209.
    125 ) {
    126  startupPhases["before profile selection"].allowlist.modules.add(
    127    "resource://gre/modules/XULStore.sys.mjs"
    128  );
    129 }
    130 
    131 if (AppConstants.MOZ_CRASHREPORTER) {
    132  startupPhases["before handling user events"].denylist.modules.add(
    133    "resource://gre/modules/CrashSubmit.sys.mjs"
    134  );
    135 }
    136 // Bug 1798750
    137 if (AppConstants.platform != "linux") {
    138  startupPhases["before handling user events"].denylist.modules.add(
    139    "resource://gre/modules/PlacesBackups.sys.mjs",
    140    "resource://gre/modules/PlacesExpiration.sys.mjs"
    141  );
    142 }
    143 
    144 add_task(async function () {
    145  if (
    146    !AppConstants.NIGHTLY_BUILD &&
    147    !AppConstants.MOZ_DEV_EDITION &&
    148    !AppConstants.DEBUG
    149  ) {
    150    ok(
    151      !("@mozilla.org/test/startuprecorder;1" in Cc),
    152      "the startup recorder component shouldn't exist in this non-nightly/non-devedition/" +
    153        "non-debug build."
    154    );
    155    return;
    156  }
    157 
    158  let startupRecorder =
    159    Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
    160  await startupRecorder.done;
    161 
    162  let data = Cu.cloneInto(startupRecorder.data.code, {});
    163  function getStack(scriptType, name) {
    164    if (scriptType == "modules") {
    165      return Cu.getModuleImportStack(name);
    166    }
    167    return "";
    168  }
    169 
    170  // This block only adds debug output to help find the next bugs to file,
    171  // it doesn't contribute to the actual test.
    172  SimpleTest.requestCompleteLog();
    173  let previous;
    174  for (let phase in data) {
    175    for (let scriptType in data[phase]) {
    176      for (let f of data[phase][scriptType]) {
    177        // phases are ordered, so if a script wasn't loaded yet at the immediate
    178        // previous phase, it wasn't loaded during any of the previous phases
    179        // either, and is new in the current phase.
    180        if (!previous || !data[previous][scriptType].includes(f)) {
    181          info(`${scriptType} loaded ${phase}: ${f}`);
    182          if (kDumpAllStacks) {
    183            info(getStack(scriptType, f));
    184          }
    185        }
    186      }
    187    }
    188    previous = phase;
    189  }
    190 
    191  for (let phase in startupPhases) {
    192    let loadedList = data[phase];
    193    let allowlist = startupPhases[phase].allowlist || null;
    194    if (allowlist) {
    195      for (let scriptType in allowlist) {
    196        loadedList[scriptType] = loadedList[scriptType].filter(c => {
    197          if (!allowlist[scriptType].has(c)) {
    198            return true;
    199          }
    200          allowlist[scriptType].delete(c);
    201          return false;
    202        });
    203        is(
    204          loadedList[scriptType].length,
    205          0,
    206          `should have no unexpected ${scriptType} loaded ${phase}`
    207        );
    208        for (let script of loadedList[scriptType]) {
    209          let message = `unexpected ${scriptType}: ${script}`;
    210          record(false, message, undefined, getStack(scriptType, script));
    211        }
    212        is(
    213          allowlist[scriptType].size,
    214          0,
    215          `all ${scriptType} allowlist entries should have been used`
    216        );
    217        for (let script of allowlist[scriptType]) {
    218          ok(false, `unused ${scriptType} allowlist entry: ${script}`);
    219        }
    220      }
    221    }
    222    let denylist = startupPhases[phase].denylist || null;
    223    if (denylist) {
    224      for (let scriptType in denylist) {
    225        for (let file of denylist[scriptType]) {
    226          let loaded = loadedList[scriptType].includes(file);
    227          let message = `${file} is not allowed ${phase}`;
    228          if (!loaded) {
    229            ok(true, message);
    230          } else {
    231            record(false, message, undefined, getStack(scriptType, file));
    232          }
    233        }
    234      }
    235 
    236      if (denylist.modules) {
    237        let results = await PerfTestHelpers.throttledMapPromises(
    238          denylist.modules,
    239          async uri => ({
    240            uri,
    241            exists: await PerfTestHelpers.checkURIExists(uri),
    242          })
    243        );
    244 
    245        for (let { uri, exists } of results) {
    246          ok(exists, `denylist entry ${uri} for phase "${phase}" must exist`);
    247        }
    248      }
    249 
    250      if (denylist.services) {
    251        for (let contract of denylist.services) {
    252          ok(
    253            contract in Cc,
    254            `denylist entry ${contract} for phase "${phase}" must exist`
    255          );
    256        }
    257      }
    258    }
    259  }
    260 });