tor-browser

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

BrowserContentHandler.sys.mjs (59369B)


      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 
      5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const lazy = {};
      9 
     10 ChromeUtils.defineESModuleGetters(lazy, {
     11  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
     12  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     13  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     14  FirstStartup: "resource://gre/modules/FirstStartup.sys.mjs",
     15  HeadlessShell: "moz-src:///browser/components/shell/HeadlessShell.sys.mjs",
     16  HomePage: "resource:///modules/HomePage.sys.mjs",
     17  LaterRun: "resource:///modules/LaterRun.sys.mjs",
     18  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     19  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     20  SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
     21  SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
     22  ShellService: "moz-src:///browser/components/shell/ShellService.sys.mjs",
     23  SpecialMessageActions:
     24    "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
     25  UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
     26 });
     27 
     28 XPCOMUtils.defineLazyServiceGetters(lazy, {
     29  UpdateManager: ["@mozilla.org/updates/update-manager;1", Ci.nsIUpdateManager],
     30  WinTaskbar: ["@mozilla.org/windows-taskbar;1", Ci.nsIWinTaskbar],
     31  WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", Ci.nsIWindowsUIUtils],
     32 });
     33 
     34 ChromeUtils.defineLazyGetter(lazy, "gSystemPrincipal", () =>
     35  Services.scriptSecurityManager.getSystemPrincipal()
     36 );
     37 
     38 ChromeUtils.defineLazyGetter(lazy, "gWindowsAlertsService", () => {
     39  // We might not have the Windows alerts service: e.g., on Windows 7 and Windows 8.
     40  if (!("nsIWindowsAlertsService" in Ci)) {
     41    return null;
     42  }
     43  return Cc["@mozilla.org/system-alerts-service;1"]
     44    ?.getService(Ci.nsIAlertsService)
     45    ?.QueryInterface(Ci.nsIWindowsAlertsService);
     46 });
     47 
     48 const FORK_VERSION_PREF =
     49  "browser.startup.homepage_override.torbrowser.version";
     50 
     51 // One-time startup homepage override configurations
     52 const ONCE_DOMAINS = new Set(["mozilla.org", "firefox.com"]);
     53 const ONCE_PREF = "browser.startup.homepage_override.once";
     54 
     55 // Index of Private Browsing icon in firefox.exe
     56 // Must line up with the one in nsNativeAppSupportWin.h.
     57 const PRIVATE_BROWSING_ICON_INDEX = 5;
     58 
     59 function shouldLoadURI(aURI) {
     60  if (aURI && !aURI.schemeIs("chrome")) {
     61    return true;
     62  }
     63 
     64  dump("*** Preventing external load of chrome: URI into browser window\n");
     65  dump("    Use --chrome <uri> instead\n");
     66  return false;
     67 }
     68 
     69 function resolveURIInternal(aCmdLine, aArgument) {
     70  let principal = lazy.gSystemPrincipal;
     71  var uri = aCmdLine.resolveURI(aArgument);
     72  var uriFixup = Services.uriFixup;
     73 
     74  if (!(uri instanceof Ci.nsIFileURL)) {
     75    let prefURI = Services.uriFixup.getFixupURIInfo(
     76      aArgument,
     77      uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
     78    ).preferredURI;
     79    return { uri: prefURI, principal };
     80  }
     81 
     82  try {
     83    if (uri.file.exists()) {
     84      return { uri, principal };
     85    }
     86  } catch (e) {
     87    console.error(e);
     88  }
     89 
     90  // We have interpreted the argument as a relative file URI, but the file
     91  // doesn't exist. Try URI fixup heuristics: see bug 290782.
     92 
     93  try {
     94    uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
     95  } catch (e) {
     96    console.error(e);
     97  }
     98 
     99  return { uri, principal };
    100 }
    101 
    102 let gKiosk = false;
    103 let gMajorUpgrade = false;
    104 let gFirstRunProfile = false;
    105 var gFirstWindow = false;
    106 
    107 const OVERRIDE_NONE = 0;
    108 const OVERRIDE_NEW_PROFILE = 1;
    109 const OVERRIDE_NEW_MSTONE = 2;
    110 const OVERRIDE_NEW_BUILD_ID = 3;
    111 /**
    112 * Determines whether a home page override is needed.
    113 *
    114 * @param {boolean} [updateMilestones=true]
    115 *   True if we should update the milestone prefs after comparing those prefs
    116 *   with the current platform version and build ID.
    117 *
    118 *   If updateMilestones is false, then this function has no side-effects.
    119 *
    120 * @returns {number}
    121 *   One of the following constants:
    122 *     OVERRIDE_NEW_PROFILE
    123 *       if this is the first run with a new profile.
    124 *     OVERRIDE_NEW_MSTONE
    125 *       if this is the first run with a build with a different Gecko milestone
    126 *       or fork version (i.e. right after an upgrade).
    127 *     OVERRIDE_NEW_BUILD_ID
    128 *       if this is the first run with a new build ID of the same Gecko
    129 *       milestone (i.e. after a nightly upgrade).
    130 *     OVERRIDE_NONE
    131 *       otherwise.
    132 */
    133 function needHomepageOverride(updateMilestones = true) {
    134  var savedmstone = Services.prefs.getCharPref(
    135    "browser.startup.homepage_override.mstone",
    136    ""
    137  );
    138 
    139  if (savedmstone == "ignore") {
    140    return OVERRIDE_NONE;
    141  }
    142 
    143  var mstone = Services.appinfo.platformVersion;
    144 
    145  var savedForkVersion = Services.prefs.getCharPref(FORK_VERSION_PREF, null);
    146 
    147  var savedBuildID = Services.prefs.getCharPref(
    148    "browser.startup.homepage_override.buildID",
    149    ""
    150  );
    151 
    152  var buildID = Services.appinfo.platformBuildID;
    153 
    154  if (mstone != savedmstone) {
    155    // Bug 462254. Previous releases had a default pref to suppress the EULA
    156    // agreement if the platform's installer had already shown one. Now with
    157    // about:rights we've removed the EULA stuff and default pref, but we need
    158    // a way to make existing profiles retain the default that we removed.
    159    if (savedmstone) {
    160      Services.prefs.setBoolPref("browser.rights.3.shown", true);
    161 
    162      // Remember that we saw a major version change.
    163      gMajorUpgrade = true;
    164    }
    165 
    166    if (updateMilestones) {
    167      Services.prefs.setCharPref(
    168        "browser.startup.homepage_override.mstone",
    169        mstone
    170      );
    171      Services.prefs.setCharPref(
    172        "browser.startup.homepage_override.buildID",
    173        buildID
    174      );
    175      Services.prefs.setCharPref(
    176        FORK_VERSION_PREF,
    177        AppConstants.BASE_BROWSER_VERSION
    178      );
    179    }
    180    return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
    181  }
    182 
    183  if (AppConstants.BASE_BROWSER_VERSION != savedForkVersion) {
    184    if (updateMilestones) {
    185      Services.prefs.setCharPref(
    186        "browser.startup.homepage_override.buildID",
    187        buildID
    188      );
    189      Services.prefs.setCharPref(
    190        FORK_VERSION_PREF,
    191        AppConstants.BASE_BROWSER_VERSION
    192      );
    193    }
    194    return OVERRIDE_NEW_MSTONE;
    195  }
    196 
    197  if (buildID != savedBuildID) {
    198    if (updateMilestones) {
    199      Services.prefs.setCharPref(
    200        "browser.startup.homepage_override.buildID",
    201        buildID
    202      );
    203    }
    204    return OVERRIDE_NEW_BUILD_ID;
    205  }
    206 
    207  return OVERRIDE_NONE;
    208 }
    209 
    210 /**
    211 * Gets the override page for the first run after the application has been
    212 * updated.
    213 *
    214 * @param  update
    215 *         The nsIUpdate for the update that has been applied.
    216 * @param  defaultOverridePage
    217 *         The default override page
    218 * @param  nimbusOverridePage
    219 *         Nimbus provided URL
    220 * @param  disableWnp
    221 *         Boolean, disables all WNPs if true
    222 * @return The override page.
    223 */
    224 function getPostUpdateOverridePage(
    225  update,
    226  defaultOverridePage,
    227  nimbusOverridePage,
    228  disableWnp
    229 ) {
    230  if (disableWnp) {
    231    return "";
    232  }
    233 
    234  update = update.QueryInterface(Ci.nsIWritablePropertyBag);
    235  let actions = update.getProperty("actions");
    236  // When the update doesn't specify actions fallback to the original behavior
    237  // of displaying the default override page.
    238  if (!actions) {
    239    return defaultOverridePage;
    240  }
    241 
    242  // The existence of silent or the non-existence of showURL in the actions both
    243  // mean that an override page should not be displayed.
    244  if (actions.includes("silent") || !actions.includes("showURL")) {
    245    return "";
    246  }
    247 
    248  // If a policy was set to not allow the update.xml-provided URL to be used,
    249  // use the default fallback (which will also be provided by the policy).
    250  if (!Services.policies.isAllowed("postUpdateCustomPage")) {
    251    return defaultOverridePage;
    252  }
    253 
    254  if (nimbusOverridePage) {
    255    return nimbusOverridePage;
    256  }
    257 
    258  return update.getProperty("openURL") || defaultOverridePage;
    259 }
    260 
    261 /**
    262 * Open a browser window. If this is the initial launch, this function will
    263 * attempt to use the navigator:blank window opened by BrowserGlue.sys.mjs during
    264 * early startup.
    265 *
    266 * @param cmdLine
    267 *        The nsICommandLine object given to nsICommandLineHandler's handle
    268 *        method.
    269 *        Used to check if we are processing the command line for the initial launch.
    270 * @param triggeringPrincipal
    271 *        The nsIPrincipal to use as triggering principal for the page load(s).
    272 * @param urlOrUrlList (optional)
    273 *        When omitted, the browser window will be opened with the default
    274 *        arguments, which will usually load the homepage.
    275 *        This can be a JS array of urls provided as strings, each url will be
    276 *        loaded in a tab. postData will be ignored in this case.
    277 *        This can be a single url to load in the new window, provided as a string.
    278 *        postData will be used in this case if provided.
    279 * @param postData (optional)
    280 *        An nsIInputStream object to use as POST data when loading the provided
    281 *        url, or null.
    282 * @param forcePrivate (optional)
    283 *        Boolean. If set to true, the new window will be a private browsing one.
    284 *
    285 * @returns {ChromeWindow}
    286 *          Returns the top level window opened.
    287 */
    288 function openBrowserWindow(
    289  cmdLine,
    290  triggeringPrincipal,
    291  urlOrUrlList,
    292  postData = null,
    293  forcePrivate = false
    294 ) {
    295  const isStartup =
    296    cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
    297 
    298  let args;
    299  if (!urlOrUrlList) {
    300    // Just pass in the defaultArgs directly. We'll use system principal on the other end.
    301    if (isStartup) {
    302      args = [gBrowserContentHandler.getFirstWindowArgs()];
    303    } else {
    304      args = [gBrowserContentHandler.getNewWindowArgs()];
    305    }
    306  } else if (Array.isArray(urlOrUrlList)) {
    307    // There isn't an explicit way to pass a principal here, so we load multiple URLs
    308    // with system principal when we get to actually loading them.
    309    if (
    310      !triggeringPrincipal ||
    311      !triggeringPrincipal.equals(lazy.gSystemPrincipal)
    312    ) {
    313      throw new Error(
    314        "Can't open multiple URLs with something other than system principal."
    315      );
    316    }
    317    // Passing an nsIArray for the url disables the "|"-splitting behavior.
    318    let uriArray = Cc["@mozilla.org/array;1"].createInstance(
    319      Ci.nsIMutableArray
    320    );
    321    urlOrUrlList.forEach(function (uri) {
    322      var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
    323        Ci.nsISupportsString
    324      );
    325      sstring.data = uri;
    326      uriArray.appendElement(sstring);
    327    });
    328    args = [uriArray];
    329  } else {
    330    let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
    331      Ci.nsIWritablePropertyBag2
    332    );
    333    extraOptions.setPropertyAsBool("fromExternal", true);
    334 
    335    // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
    336    // ie. avoid the loadOneOrMoreURIs function.
    337    // Also, we need to pass the triggering principal.
    338    args = [
    339      urlOrUrlList,
    340      extraOptions,
    341      null, // refererInfo
    342      postData,
    343      undefined, // allowThirdPartyFixup; this would be `false` but that
    344      // needs a conversion. Hopefully bug 1485961 will fix.
    345      undefined, // user context id
    346      null, // origin principal
    347      null, // origin storage principal
    348      triggeringPrincipal,
    349    ];
    350  }
    351 
    352  if (isStartup) {
    353    let win = gBrowserContentHandler.replaceStartupWindow(args, forcePrivate);
    354    if (win) {
    355      return win;
    356    }
    357  }
    358 
    359  // We can't provide arguments to openWindow as a JS array.
    360  if (!urlOrUrlList) {
    361    // If we have a single string guaranteed to not contain '|' we can simply
    362    // wrap it in an nsISupportsString object.
    363    let [url] = args;
    364    args = Cc["@mozilla.org/supports-string;1"].createInstance(
    365      Ci.nsISupportsString
    366    );
    367    args.data = url;
    368  } else {
    369    // Otherwise, pass an nsIArray.
    370    if (args.length > 1) {
    371      let string = Cc["@mozilla.org/supports-string;1"].createInstance(
    372        Ci.nsISupportsString
    373      );
    374      string.data = args[0];
    375      args[0] = string;
    376    }
    377    let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
    378    args.forEach(a => {
    379      array.appendElement(a);
    380    });
    381    args = array;
    382  }
    383 
    384  return lazy.BrowserWindowTracker.openWindow({
    385    args,
    386    features: gBrowserContentHandler.getFeatures(cmdLine),
    387    private: forcePrivate,
    388  });
    389 }
    390 
    391 function openPreferences(cmdLine) {
    392  openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
    393 }
    394 
    395 async function doSearch(searchText, cmdLine) {
    396  // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
    397  // preferences, but need nsIBrowserDOMWindow extensions
    398  // Open the window immediately as BrowserContentHandler needs to
    399  // be handled synchronously. Then load the search URI when the
    400  // SearchService has loaded.
    401  let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
    402  await lazy.BrowserUtils.promiseObserved(
    403    "browser-delayed-startup-finished",
    404    subject => subject == win
    405  );
    406 
    407  lazy.SearchUIUtils.loadSearch({
    408    window: win,
    409    searchText,
    410    usePrivateWindow:
    411      lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
    412      lazy.PrivateBrowsingUtils.isWindowPrivate(win),
    413    triggeringPrincipal: lazy.gSystemPrincipal,
    414    policyContainer: win.gBrowser.selectedBrowser.policyContainer,
    415    sapSource: "system",
    416  }).catch(console.error);
    417 }
    418 
    419 function spinForLastUpdateInstalled() {
    420  return spinResolve(lazy.UpdateManager.lastUpdateInstalled());
    421 }
    422 
    423 function spinForUpdateInstalledAtStartup() {
    424  return spinResolve(lazy.UpdateManager.updateInstalledAtStartup());
    425 }
    426 
    427 function spinResolve(promise) {
    428  if (!(promise instanceof Promise)) {
    429    return promise;
    430  }
    431  let done = false;
    432  let result = null;
    433  let error = null;
    434  promise
    435    .catch(e => {
    436      error = e;
    437    })
    438    .then(r => {
    439      result = r;
    440      done = true;
    441    });
    442 
    443  Services.tm.spinEventLoopUntil(
    444    "BrowserContentHandler.sys.mjs:BCH_spinResolve",
    445    () => done
    446  );
    447  if (!done) {
    448    throw new Error("Forcefully exited event loop.");
    449  } else if (error) {
    450    throw error;
    451  } else {
    452    return result;
    453  }
    454 }
    455 
    456 export function nsBrowserContentHandler() {
    457  if (!gBrowserContentHandler) {
    458    gBrowserContentHandler = this;
    459  }
    460  return gBrowserContentHandler;
    461 }
    462 
    463 nsBrowserContentHandler.prototype = {
    464  /* nsISupports */
    465  QueryInterface: ChromeUtils.generateQI([
    466    "nsICommandLineHandler",
    467    "nsIBrowserHandler",
    468    "nsIContentHandler",
    469    "nsICommandLineValidator",
    470  ]),
    471 
    472  /* nsICommandLineHandler */
    473  handle: function bch_handle(cmdLine) {
    474    if (
    475      cmdLine.handleFlag("kiosk", false) ||
    476      cmdLine.handleFlagWithParam("kiosk-monitor", false)
    477    ) {
    478      gKiosk = true;
    479      Glean.browserStartup.kioskMode.set(true);
    480    }
    481    if (cmdLine.handleFlag("disable-pinch", false)) {
    482      let defaults = Services.prefs.getDefaultBranch(null);
    483      defaults.setBoolPref("apz.allow_zooming", false);
    484      Services.prefs.lockPref("apz.allow_zooming");
    485      defaults.setCharPref("browser.gesture.pinch.in", "");
    486      Services.prefs.lockPref("browser.gesture.pinch.in");
    487      defaults.setCharPref("browser.gesture.pinch.in.shift", "");
    488      Services.prefs.lockPref("browser.gesture.pinch.in.shift");
    489      defaults.setCharPref("browser.gesture.pinch.out", "");
    490      Services.prefs.lockPref("browser.gesture.pinch.out");
    491      defaults.setCharPref("browser.gesture.pinch.out.shift", "");
    492      Services.prefs.lockPref("browser.gesture.pinch.out.shift");
    493    }
    494    if (cmdLine.handleFlag("browser", false)) {
    495      openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
    496      cmdLine.preventDefault = true;
    497    }
    498 
    499    var uriparam;
    500    try {
    501      while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
    502        let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
    503        if (!shouldLoadURI(uri)) {
    504          continue;
    505        }
    506        openBrowserWindow(cmdLine, principal, uri.spec);
    507        cmdLine.preventDefault = true;
    508      }
    509    } catch (e) {
    510      console.error(e);
    511    }
    512 
    513    try {
    514      while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
    515        let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
    516        handURIToExistingBrowser(
    517          uri,
    518          Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
    519          cmdLine,
    520          false,
    521          principal
    522        );
    523        cmdLine.preventDefault = true;
    524      }
    525    } catch (e) {
    526      console.error(e);
    527    }
    528 
    529    var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
    530    if (chromeParam) {
    531      // Handle old preference dialog URLs.
    532      if (
    533        chromeParam == "chrome://browser/content/pref/pref.xul" ||
    534        chromeParam == "chrome://browser/content/preferences/preferences.xul"
    535      ) {
    536        openPreferences(cmdLine);
    537        cmdLine.preventDefault = true;
    538      } else {
    539        try {
    540          let { uri: resolvedURI } = resolveURIInternal(cmdLine, chromeParam);
    541          let isLocal = uri => {
    542            let localSchemes = new Set(["chrome", "file", "resource"]);
    543            if (uri instanceof Ci.nsINestedURI) {
    544              uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
    545            }
    546            return localSchemes.has(uri.scheme);
    547          };
    548          if (isLocal(resolvedURI)) {
    549            // If the URI is local, we are sure it won't wrongly inherit chrome privs
    550            let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
    551            // Provide 1 null argument, as openWindow has a different behavior
    552            // when the arg count is 0.
    553            let argArray = Cc["@mozilla.org/array;1"].createInstance(
    554              Ci.nsIMutableArray
    555            );
    556            argArray.appendElement(null);
    557            Services.ww.openWindow(
    558              null,
    559              resolvedURI.spec,
    560              "_blank",
    561              features,
    562              argArray
    563            );
    564            cmdLine.preventDefault = true;
    565          } else {
    566            dump("*** Preventing load of web URI as chrome\n");
    567            dump(
    568              "    If you're trying to load a webpage, do not pass --chrome.\n"
    569            );
    570          }
    571        } catch (e) {
    572          console.error(e);
    573        }
    574      }
    575    }
    576    if (cmdLine.handleFlag("preferences", false)) {
    577      openPreferences(cmdLine);
    578      cmdLine.preventDefault = true;
    579    }
    580    if (cmdLine.handleFlag("silent", false)) {
    581      cmdLine.preventDefault = true;
    582    }
    583 
    584    try {
    585      var privateWindowParam = cmdLine.handleFlagWithParam(
    586        "private-window",
    587        false
    588      );
    589      if (privateWindowParam) {
    590        let forcePrivate = true;
    591        let resolvedInfo;
    592        if (!lazy.PrivateBrowsingUtils.enabled) {
    593          // Load about:privatebrowsing in a normal tab, which will display an error indicating
    594          // access to private browsing has been disabled.
    595          forcePrivate = false;
    596          resolvedInfo = {
    597            uri: Services.io.newURI("about:privatebrowsing"),
    598            principal: lazy.gSystemPrincipal,
    599          };
    600        } else {
    601          resolvedInfo = resolveURIInternal(cmdLine, privateWindowParam);
    602        }
    603        handURIToExistingBrowser(
    604          resolvedInfo.uri,
    605          Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
    606          cmdLine,
    607          forcePrivate,
    608          resolvedInfo.principal
    609        );
    610        cmdLine.preventDefault = true;
    611      }
    612    } catch (e) {
    613      if (e.result != Cr.NS_ERROR_INVALID_ARG) {
    614        throw e;
    615      }
    616      // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
    617      if (cmdLine.handleFlag("private-window", false)) {
    618        openBrowserWindow(
    619          cmdLine,
    620          lazy.gSystemPrincipal,
    621          "about:privatebrowsing",
    622          null,
    623          lazy.PrivateBrowsingUtils.enabled
    624        );
    625        cmdLine.preventDefault = true;
    626      }
    627    }
    628 
    629    var searchParam = cmdLine.handleFlagWithParam("search", false);
    630    if (searchParam) {
    631      doSearch(searchParam, cmdLine);
    632      cmdLine.preventDefault = true;
    633    }
    634 
    635    // The global PB Service consumes this flag, so only eat it in per-window
    636    // PB builds.
    637    if (
    638      cmdLine.handleFlag("private", false) &&
    639      lazy.PrivateBrowsingUtils.enabled
    640    ) {
    641      lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
    642      if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
    643        let win = Services.wm.getMostRecentWindow("navigator:blank");
    644        if (win) {
    645          win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing =
    646            true;
    647        }
    648      }
    649    }
    650    if (cmdLine.handleFlag("setDefaultBrowser", false)) {
    651      // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
    652      // is an implementation of an interface method and changing it to be async would be complicated
    653      // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
    654      // an await.
    655      lazy.ShellService.setDefaultBrowser(true).catch(e => {
    656        console.error("setDefaultBrowser failed:", e);
    657      });
    658    }
    659 
    660    if (cmdLine.handleFlag("first-startup", false)) {
    661      // We don't want subsequent calls to needHompageOverride to have different
    662      // values because the milestones in prefs got updated, so we intentionally
    663      // tell needHomepageOverride to leave the milestone prefs alone when doing
    664      // this check.
    665      let override = needHomepageOverride(false /* updateMilestones */);
    666      lazy.FirstStartup.init(override == OVERRIDE_NEW_PROFILE /* newProfile */);
    667    }
    668 
    669    var fileParam = cmdLine.handleFlagWithParam("file", false);
    670    if (fileParam) {
    671      var file = cmdLine.resolveFile(fileParam);
    672      var fileURI = Services.io.newFileURI(file);
    673      openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
    674      cmdLine.preventDefault = true;
    675    }
    676 
    677    if (AppConstants.platform == "win") {
    678      // Handle "? searchterm" for Windows Vista start menu integration
    679      for (var i = cmdLine.length - 1; i >= 0; --i) {
    680        var param = cmdLine.getArgument(i);
    681        if (param.match(/^\? /)) {
    682          cmdLine.removeArguments(i, i);
    683          cmdLine.preventDefault = true;
    684 
    685          searchParam = param.substr(2);
    686          doSearch(searchParam, cmdLine);
    687        }
    688      }
    689    }
    690  },
    691 
    692  get helpInfo() {
    693    let info =
    694      "  --browser          Open a browser window.\n" +
    695      "  --new-window <url> Open <url> in a new window.\n" +
    696      "  --new-tab <url>    Open <url> in a new tab.\n" +
    697      "  --private-window [<url>] Open <url> in a new private window.\n";
    698    if (AppConstants.platform == "win") {
    699      info += "  --preferences      Open Options dialog.\n";
    700    } else {
    701      info += "  --preferences      Open Preferences dialog.\n";
    702    }
    703    info +=
    704      "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
    705    info +=
    706      "  --window-size width[,height] Width and optionally height of screenshot.\n";
    707    info +=
    708      "  --search <term>    Search <term> with your default search engine.\n";
    709    info += "  --setDefaultBrowser Set this app as the default browser.\n";
    710    info +=
    711      "  --first-startup    Run post-install actions before opening a new window.\n";
    712    info += "  --kiosk            Start the browser in kiosk mode.\n";
    713    info +=
    714      "  --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
    715    info +=
    716      "  --disable-pinch    Disable touch-screen and touch-pad pinch gestures.\n";
    717    return info;
    718  },
    719 
    720  /* nsIBrowserHandler */
    721 
    722  get defaultArgs() {
    723    return this.getNewWindowArgs();
    724  },
    725 
    726  // This function is expected to be called in non-startup cases,
    727  // a WNP will not be retrieved within this function, but it will retrieve
    728  // any new profile override page(s) or regular startup page(s).
    729  // For the startup version of this function, please use getFirstWindowArgs().
    730  // See Bug 1642039 for more information.
    731  getNewWindowArgs(skipStartPage = false) {
    732    var page = lazy.LaterRun.getURL();
    733    if (page == "about:blank") {
    734      page = "";
    735    }
    736    var startPage = "";
    737    var prefb = Services.prefs;
    738    try {
    739      var choice = prefb.getIntPref("browser.startup.page");
    740      if (choice == 1 || choice == 3) {
    741        startPage = lazy.HomePage.get();
    742      }
    743    } catch (e) {
    744      console.error(e);
    745    }
    746 
    747    if (startPage == "about:blank") {
    748      startPage = "";
    749    }
    750 
    751    if (!skipStartPage && startPage) {
    752      if (page) {
    753        page += "|" + startPage;
    754      } else {
    755        page = startPage;
    756      }
    757    } else if (!page) {
    758      page = startPage;
    759    }
    760 
    761    return page || "about:blank";
    762  },
    763 
    764  // This function is expected to be called very early during Firefox startup,
    765  // It will retrieve a WNP if avaliable, before calling getNewWindowsArg()
    766  // to retrieve any other startup pages that needs to be displayed.
    767  // See Bug 1642039 for more information.
    768  getFirstWindowArgs() {
    769    var prefb = Services.prefs;
    770 
    771    if (!gFirstWindow) {
    772      gFirstWindow = true;
    773      if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
    774        return "about:privatebrowsing";
    775      }
    776    }
    777 
    778    var override;
    779    var overridePage = "";
    780    var additionalPage = "";
    781    var willRestoreSession = false;
    782    let openAboutTor = false;
    783    try {
    784      // Read the old value of homepage_override.mstone before
    785      // needHomepageOverride updates it, so that we can later add it to the
    786      // URL if we do end up showing an overridePage. This makes it possible
    787      // to have the overridePage's content vary depending on the version we're
    788      // upgrading from.
    789      let old_mstone = Services.prefs.getCharPref(
    790        "browser.startup.homepage_override.mstone",
    791        "unknown"
    792      );
    793      let old_buildId = Services.prefs.getCharPref(
    794        "browser.startup.homepage_override.buildID",
    795        "unknown"
    796      );
    797 
    798      // We do the same for the fork version.
    799      let old_forkVersion = Services.prefs.getCharPref(FORK_VERSION_PREF, null);
    800 
    801      override = needHomepageOverride();
    802      // An object to measure the progress of handleUpdateSuccess
    803      let progress = {
    804        updateFetched: false,
    805        payloadCreated: false,
    806        pingFailed: false,
    807      };
    808 
    809      if (override != OVERRIDE_NONE) {
    810        switch (override) {
    811          case OVERRIDE_NEW_PROFILE:
    812            // New profile.
    813            gFirstRunProfile = true;
    814            overridePage = Services.urlFormatter.formatURLPref(
    815              "startup.homepage_welcome_url"
    816            );
    817            additionalPage = Services.urlFormatter.formatURLPref(
    818              "startup.homepage_welcome_url.additional"
    819            );
    820            // Turn on 'later run' pages for new profiles.
    821            lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
    822            break;
    823          case OVERRIDE_NEW_MSTONE: {
    824            // Check whether we will restore a session. If we will, we assume
    825            // that this is an "update" session. This does not take crashes
    826            // into account because that requires waiting for the session file
    827            // to be read. If a crash occurs after updating, before restarting,
    828            // we may open the startPage in addition to restoring the session.
    829            willRestoreSession =
    830              lazy.SessionStartup.isAutomaticRestoreEnabled();
    831 
    832            overridePage = Services.urlFormatter.formatURLPref(
    833              "startup.homepage_override_url"
    834            );
    835 
    836            /*
    837            The update manager loads its data asynchronously, off of the main thread.
    838            However, making this function asynchronous would be very difficult and
    839            wouldn't provide any benefit. This code is part of the sequence of operations
    840            that must run before the first tab and its contents can be displayed.
    841            The user has to wait for this to complete regardless of the method or thread of execution,
    842            and the browser will be practically unusable until it finishes.
    843            Therefore, asynchronous execution does not offer any real advantages in this context.
    844            */
    845            let update = spinForLastUpdateInstalled();
    846 
    847            // Make sure the update is newer than the last WNP version
    848            // and the update is not newer than the current Firefox version.
    849            if (
    850              update &&
    851              (Services.vc.compare(update.platformVersion, old_mstone) <= 0 ||
    852                Services.vc.compare(
    853                  update.appVersion,
    854                  Services.appinfo.version
    855                ) > 0)
    856            ) {
    857              update = null;
    858              overridePage = null;
    859            }
    860 
    861            /**
    862             * If the override URL is provided by an experiment, is a valid
    863             * Firefox What's New Page URL, and the update version is less than
    864             * or equal to the maxVersion set by the experiment, we'll try to use
    865             * the experiment override URL instead of the default or the
    866             * update-provided URL. Additional policy checks are done in
    867             *
    868             * @see getPostUpdateOverridePage
    869             */
    870            const nimbusOverrideUrl = Services.urlFormatter.formatURLPref(
    871              "startup.homepage_override_url_nimbus"
    872            );
    873            // This defines the maximum allowed Fx update version to see the
    874            // nimbus WNP. For ex, if maxVersion is set to 127 but user updates
    875            // to 128, they will not qualify.
    876            const maxVersion = Services.prefs.getCharPref(
    877              "startup.homepage_override_nimbus_maxVersion",
    878              ""
    879            );
    880            // This defines the minimum allowed Fx update version to see the
    881            // nimbus WNP. For ex, if minVersion is set to 126 but user updates
    882            // to 124, they will not qualify.
    883            const minVersion = Services.prefs.getCharPref(
    884              "startup.homepage_override_nimbus_minVersion",
    885              ""
    886            );
    887            // Pref used to disable all WNPs
    888            const disableWNP = Services.prefs.getBoolPref(
    889              "startup.homepage_override_nimbus_disable_wnp",
    890              false
    891            );
    892            let nimbusWNP;
    893            // minVersion and maxVersion optional variables
    894            const versionMatch =
    895              (!maxVersion ||
    896                Services.vc.compare(update.appVersion, maxVersion) <= 0) &&
    897              (!minVersion ||
    898                Services.vc.compare(update.appVersion, minVersion) >= 0);
    899 
    900            // The update version should be less than or equal to maxVersion and
    901            // greater or equal to minVersion set by the experiment.
    902            if (nimbusOverrideUrl && versionMatch) {
    903              try {
    904                let uri = Services.io.newURI(nimbusOverrideUrl);
    905                // Only allow https://www.mozilla.org and https://www.mozilla.com
    906                if (
    907                  uri.scheme === "https" &&
    908                  ["www.mozilla.org", "www.mozilla.com"].includes(uri.host)
    909                ) {
    910                  nimbusWNP = uri.spec;
    911                } else {
    912                  throw new Error("Bad URL");
    913                }
    914              } catch {
    915                console.error("Invalid WNP URL: ", nimbusOverrideUrl);
    916              }
    917            }
    918 
    919            let old_version = old_forkVersion ? old_forkVersion : old_mstone;
    920            if (
    921              update &&
    922              Services.vc.compare(update.appVersion, old_version) > 0
    923            ) {
    924              overridePage = getPostUpdateOverridePage(
    925                update,
    926                overridePage,
    927                nimbusWNP,
    928                disableWNP
    929              );
    930              // Record a Nimbus exposure event for the whatsNewPage feature.
    931              // The override page could be set in 3 ways: 1. set by Nimbus; 2.
    932              // set by the update file (openURL); 3. defaulting to the
    933              // evergreen page (set by the startup.homepage_override_url pref,
    934              // value depends on the Fx channel). This is done to record that
    935              // the control cohort could have seen the experimental What's New
    936              // Page (and will instead see the default What's New Page, or
    937              // won't see a WNP if the experiment disabled it by setting
    938              // disable_wnp). `recordExposureEvent` only records an event if
    939              // the user is enrolled in an experiment or rollout on the
    940              // whatsNewPage feature, so it's safe to call it unconditionally.
    941              if (overridePage || (versionMatch && disableWNP)) {
    942                let nimbusWNPFeature = lazy.NimbusFeatures.whatsNewPage;
    943                nimbusWNPFeature
    944                  .ready()
    945                  .then(() => nimbusWNPFeature.recordExposureEvent());
    946              }
    947 
    948              lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
    949            }
    950 
    951            // Send the update ping to signal that the update was successful.
    952            // Only do this if the update is installed right now.
    953            // The following code is ran asynchronously, but we won't await on it
    954            // since the user may be still waiting for the browser to start up at this point.
    955            let handleUpdateSuccessTask =
    956              lazy.UpdateManager.updateInstalledAtStartup().then(
    957                async updateInstalledAtStartup => {
    958                  if (updateInstalledAtStartup) {
    959                    await lazy.UpdatePing.handleUpdateSuccess(
    960                      old_mstone,
    961                      old_buildId,
    962                      progress
    963                    );
    964                  }
    965                }
    966              );
    967 
    968            // Adding a shutdown blocker to ensure the
    969            // update ping will be sent before Firefox exits.
    970            lazy.AsyncShutdown.profileBeforeChange.addBlocker(
    971              "BrowserContentHandler: running handleUpdateSuccess",
    972              handleUpdateSuccessTask,
    973              { fetchState: () => ({ progress }) }
    974            );
    975 
    976            overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
    977            overridePage = overridePage.replace(
    978              "%OLD_BASE_BROWSER_VERSION%",
    979              old_forkVersion
    980            );
    981            if (AppConstants.BASE_BROWSER_UPDATE) {
    982              // Tor Browser: Instead of opening the post-update "override page"
    983              // directly, we ensure that about:tor will be opened, which should
    984              // notify the user that their browser was updated.
    985              // NOTE: We ignore any existing overridePage value, which can come
    986              // from the openURL attribute within the updates.xml file.
    987              Services.prefs.setBoolPref(
    988                "torbrowser.post_update.shouldNotify",
    989                true
    990              );
    991              openAboutTor = true;
    992              overridePage = "about:tor";
    993            }
    994            break;
    995          }
    996          case OVERRIDE_NEW_BUILD_ID: {
    997            // We must spin the events loop because `getFirstWindowArgs` cannot be
    998            // easily made asynchronous, having too many synchronous callers. Additionally
    999            // we must know the value of `updateInstalledAtStartup` immediately,
   1000            // in order to properly enable `lazy.LaterRun`, that will be invoked shortly after this.
   1001            let updateInstalledAtStartup = spinForUpdateInstalledAtStartup();
   1002 
   1003            if (updateInstalledAtStartup) {
   1004              let handleUpdateSuccessTask = lazy.UpdatePing.handleUpdateSuccess(
   1005                old_mstone,
   1006                old_buildId,
   1007                progress
   1008              );
   1009 
   1010              lazy.AsyncShutdown.profileBeforeChange.addBlocker(
   1011                "BrowserContentHandler: running handleUpdateSuccess",
   1012                handleUpdateSuccessTask,
   1013                { fetchState: () => ({ progress }) }
   1014              );
   1015 
   1016              lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
   1017            }
   1018            break;
   1019          }
   1020        }
   1021      }
   1022    } catch (ex) {}
   1023 
   1024    // formatURLPref might return "about:blank" if getting the pref fails
   1025    if (overridePage == "about:blank") {
   1026      overridePage = "";
   1027    }
   1028 
   1029    // Allow showing a one-time startup override if we're not showing one
   1030    if (overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
   1031      try {
   1032        // Show if we haven't passed the expiration or there's no expiration
   1033        const { expire, url } = JSON.parse(
   1034          Services.urlFormatter.formatURLPref(ONCE_PREF)
   1035        );
   1036        if (!(Date.now() > expire)) {
   1037          // Only set allowed urls as override pages
   1038          overridePage = url
   1039            .split("|")
   1040            .map(val => {
   1041              let parsed = URL.parse(val);
   1042              if (!parsed) {
   1043                // Invalid URL, so filter out below
   1044                console.error(`Invalid once url: ${val}`);
   1045              }
   1046              return parsed;
   1047            })
   1048            .filter(
   1049              parsed =>
   1050                parsed?.protocol == "https:" &&
   1051                // Only accept exact hostname or subdomain; without port
   1052                ONCE_DOMAINS.has(
   1053                  Services.eTLD.getBaseDomainFromHost(parsed.host)
   1054                )
   1055            )
   1056            .join("|");
   1057 
   1058          // Be noisy as properly configured urls should be unchanged
   1059          if (overridePage != url) {
   1060            console.error(`Mismatched once urls: ${url}`);
   1061          }
   1062        }
   1063      } catch (ex) {
   1064        // Invalid json pref, so ignore (and clear below)
   1065        console.error("Invalid once pref:", ex);
   1066      } finally {
   1067        prefb.clearUserPref(ONCE_PREF);
   1068      }
   1069    }
   1070 
   1071    if (!additionalPage) {
   1072      additionalPage = lazy.LaterRun.getURL() || "";
   1073    }
   1074 
   1075    if (additionalPage && additionalPage != "about:blank") {
   1076      if (overridePage) {
   1077        overridePage += "|" + additionalPage;
   1078      } else {
   1079        overridePage = additionalPage;
   1080      }
   1081    }
   1082 
   1083    let skipStartPage =
   1084      override == OVERRIDE_NEW_PROFILE &&
   1085      prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
   1086 
   1087    var startPage = this.getNewWindowArgs(skipStartPage && !willRestoreSession);
   1088 
   1089    if (startPage == "about:blank") {
   1090      startPage = "";
   1091    }
   1092 
   1093    // If the user's homepage is about:tor, we do not want to open it twice with
   1094    // the override.
   1095    if (
   1096      openAboutTor &&
   1097      startPage === "about:tor" &&
   1098      overridePage?.split("|").includes("about:tor")
   1099    ) {
   1100      startPage = "";
   1101    }
   1102 
   1103    // Only show the startPage if we're not restoring an update session and are
   1104    // not set to skip the start page on this profile
   1105    if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
   1106      return overridePage + "|" + startPage;
   1107    }
   1108 
   1109    return overridePage || startPage || "about:blank";
   1110  },
   1111 
   1112  mFeatures: null,
   1113 
   1114  getFeatures: function bch_features(cmdLine) {
   1115    if (this.mFeatures === null) {
   1116      this.mFeatures = "";
   1117 
   1118      if (cmdLine) {
   1119        try {
   1120          var width = cmdLine.handleFlagWithParam("width", false);
   1121          var height = cmdLine.handleFlagWithParam("height", false);
   1122          var left = cmdLine.handleFlagWithParam("left", false);
   1123          var top = cmdLine.handleFlagWithParam("top", false);
   1124 
   1125          if (width) {
   1126            this.mFeatures += ",width=" + width;
   1127          }
   1128          if (height) {
   1129            this.mFeatures += ",height=" + height;
   1130          }
   1131          if (left) {
   1132            this.mFeatures += ",left=" + left;
   1133          }
   1134          if (top) {
   1135            this.mFeatures += ",top=" + top;
   1136          }
   1137        } catch (e) {}
   1138      }
   1139 
   1140      // The global PB Service consumes this flag, so only eat it in per-window
   1141      // PB builds.
   1142      if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
   1143        this.mFeatures += ",private";
   1144      }
   1145 
   1146      if (
   1147        Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
   1148        !Services.wm.getMostRecentWindow("navigator:browser")
   1149      ) {
   1150        this.mFeatures += ",suppressanimation";
   1151      }
   1152    }
   1153 
   1154    return this.mFeatures;
   1155  },
   1156 
   1157  get kiosk() {
   1158    return gKiosk;
   1159  },
   1160 
   1161  get majorUpgrade() {
   1162    return gMajorUpgrade;
   1163  },
   1164 
   1165  set majorUpgrade(val) {
   1166    gMajorUpgrade = val;
   1167  },
   1168 
   1169  get firstRunProfile() {
   1170    return gFirstRunProfile;
   1171  },
   1172 
   1173  set firstRunProfile(val) {
   1174    gFirstRunProfile = val;
   1175  },
   1176 
   1177  /* nsIContentHandler */
   1178 
   1179  handleContent: function bch_handleContent(contentType, context, request) {
   1180    const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
   1181 
   1182    try {
   1183      var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
   1184        Ci.nsIWebNavigationInfo
   1185      );
   1186      if (!webNavInfo.isTypeSupported(contentType)) {
   1187        throw NS_ERROR_WONT_HANDLE_CONTENT;
   1188      }
   1189    } catch (e) {
   1190      throw NS_ERROR_WONT_HANDLE_CONTENT;
   1191    }
   1192 
   1193    request.QueryInterface(Ci.nsIChannel);
   1194    handURIToExistingBrowser(
   1195      request.URI,
   1196      Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
   1197      null,
   1198      false,
   1199      request.loadInfo.triggeringPrincipal
   1200    );
   1201    request.cancel(Cr.NS_BINDING_ABORTED);
   1202  },
   1203 
   1204  /**
   1205   * Replace the startup UI window created in BrowserGlue with an actual window
   1206   */
   1207  replaceStartupWindow(args, forcePrivate) {
   1208    let win = Services.wm.getMostRecentWindow("navigator:blank");
   1209    if (win) {
   1210      // Remove the windowtype of our blank window so that we don't close it
   1211      // later on when seeing cmdLine.preventDefault is true.
   1212      win.document.documentElement.removeAttribute("windowtype");
   1213 
   1214      if (forcePrivate) {
   1215        win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing =
   1216          true;
   1217 
   1218        if (
   1219          AppConstants.platform == "win" &&
   1220          Services.prefs.getBoolPref(
   1221            "browser.privateWindowSeparation.enabled",
   1222            true
   1223          )
   1224        ) {
   1225          lazy.WinTaskbar.setGroupIdForWindow(
   1226            win,
   1227            lazy.WinTaskbar.defaultPrivateGroupId
   1228          );
   1229          lazy.WindowsUIUtils.setWindowIconFromExe(
   1230            win,
   1231            Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
   1232            // This corresponds to the definitions in
   1233            // nsNativeAppSupportWin.h
   1234            PRIVATE_BROWSING_ICON_INDEX
   1235          );
   1236        }
   1237      }
   1238 
   1239      let openTime = win.openTime;
   1240      win.location = AppConstants.BROWSER_CHROME_URL;
   1241      win.arguments = args; // <-- needs to be a plain JS array here.
   1242 
   1243      ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
   1244      lazy.BrowserWindowTracker.registerOpeningWindow(win, forcePrivate);
   1245      return win;
   1246    }
   1247    return null;
   1248  },
   1249 
   1250  /* nsICommandLineValidator */
   1251  validate: function bch_validate(cmdLine) {
   1252    var urlFlagIdx = cmdLine.findFlag("url", false);
   1253    if (
   1254      urlFlagIdx > -1 &&
   1255      cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
   1256    ) {
   1257      var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
   1258      if (
   1259        cmdLine.length != urlFlagIdx + 2 ||
   1260        /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
   1261      ) {
   1262        throw Components.Exception("", Cr.NS_ERROR_ABORT);
   1263      }
   1264    }
   1265  },
   1266 };
   1267 var gBrowserContentHandler = new nsBrowserContentHandler();
   1268 
   1269 function handURIToExistingBrowser(
   1270  uri,
   1271  location,
   1272  cmdLine,
   1273  forcePrivate,
   1274  triggeringPrincipal
   1275 ) {
   1276  if (!shouldLoadURI(uri)) {
   1277    return;
   1278  }
   1279 
   1280  let openInWindow = ({ browserDOMWindow }) => {
   1281    browserDOMWindow.openURI(
   1282      uri,
   1283      null,
   1284      location,
   1285      Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
   1286      triggeringPrincipal
   1287    );
   1288  };
   1289 
   1290  // Unless using a private window is forced, open external links in private
   1291  // windows only if we're in perma-private mode.
   1292  let allowPrivate =
   1293    forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
   1294  let navWin = lazy.BrowserWindowTracker.getTopWindow({
   1295    private: allowPrivate,
   1296  });
   1297 
   1298  if (navWin) {
   1299    openInWindow(navWin);
   1300    return;
   1301  }
   1302 
   1303  let pending = lazy.BrowserWindowTracker.getPendingWindow({
   1304    private: allowPrivate,
   1305  });
   1306  if (pending) {
   1307    // Note that we cannot make this function async as some callers rely on
   1308    // catching exceptions it can throw in some cases and some of those callers
   1309    // cannot be made async.
   1310    pending.then(openInWindow);
   1311    return;
   1312  }
   1313 
   1314  // if we couldn't load it in an existing window, open a new one
   1315  openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
   1316 }
   1317 
   1318 /**
   1319 * If given URI is a file type or a protocol, record telemetry that
   1320 * Firefox was invoked or launched (if `isLaunch` is truth-y).  If the
   1321 * file type or protocol is not registered by default, record it as
   1322 * ".<other extension>" or "<other protocol>".
   1323 *
   1324 * @param uri
   1325 *        The URI Firefox was asked to handle.
   1326 * @param isLaunch
   1327 *        truth-y if Firefox was launched/started rather than running and invoked.
   1328 */
   1329 function maybeRecordToHandleTelemetry(uri, isLaunch) {
   1330  let counter = isLaunch
   1331    ? Glean.osEnvironment.launchedToHandle
   1332    : Glean.osEnvironment.invokedToHandle;
   1333 
   1334  if (uri instanceof Ci.nsIFileURL) {
   1335    let extension = "." + uri.fileExtension.toLowerCase();
   1336    // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
   1337    // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
   1338    let registeredExtensions = new Set([
   1339      ".avif",
   1340      ".htm",
   1341      ".html",
   1342      ".pdf",
   1343      ".shtml",
   1344      ".xht",
   1345      ".xhtml",
   1346      ".svg",
   1347      ".webp",
   1348    ]);
   1349    if (registeredExtensions.has(extension)) {
   1350      counter[extension].add(1);
   1351    } else {
   1352      counter[".<other extension>"].add(1);
   1353    }
   1354  } else if (uri) {
   1355    let scheme = uri.scheme.toLowerCase();
   1356    let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
   1357    if (registeredSchemes.has(scheme)) {
   1358      counter[scheme].add(1);
   1359    } else {
   1360      counter["<other protocol>"].add(1);
   1361    }
   1362  }
   1363 }
   1364 
   1365 export function nsDefaultCommandLineHandler() {}
   1366 
   1367 nsDefaultCommandLineHandler.prototype = {
   1368  /* nsISupports */
   1369  QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
   1370 
   1371  _haveProfile: false,
   1372 
   1373  /**
   1374   * @param {nsICommandLine} cmdLine
   1375   * @returns {boolean} true if the command is handled as notification, otherwise false.
   1376   */
   1377  handleNotification(cmdLine) {
   1378    if (AppConstants.platform !== "win") {
   1379      // Only for Windows for now
   1380      return false;
   1381    }
   1382 
   1383    // Windows itself does disk I/O when the notification service is
   1384    // initialized, so make sure that is lazy.
   1385    let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
   1386    if (!tag) {
   1387      return false;
   1388    }
   1389 
   1390    // All notifications will invoke Firefox with an action.  Prior to Bug 1805514,
   1391    // this data was extracted from the Windows toast object directly (keyed by the
   1392    // notification ID) and not passed over the command line.  This is acceptable
   1393    // because the data passed is chrome-controlled, but if we implement the `actions`
   1394    // part of the DOM Web Notifications API, this will no longer be true:
   1395    // content-controlled data might transit over the command line.  This could lead
   1396    // to escaping bugs and overflows.  In the future, we intend to avoid any such
   1397    // issue by once again extracting all such data from the Windows toast object.
   1398    let notificationData = cmdLine.handleFlagWithParam(
   1399      "notification-windowsAction",
   1400      false
   1401    );
   1402    if (!notificationData) {
   1403      return false;
   1404    }
   1405 
   1406    let alertService = lazy.gWindowsAlertsService;
   1407    if (!alertService) {
   1408      console.error("Windows alert service not available.");
   1409      return false;
   1410    }
   1411 
   1412    // Notification handling occurs asynchronously to prevent blocking the
   1413    // main thread. As a result we won't have the information we need to open
   1414    // a new tab in the case of notification fallback handling before
   1415    // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
   1416    // the browser from exiting in case early blank window is pref'd off.
   1417    if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
   1418      Services.startup.enterLastWindowClosingSurvivalArea();
   1419    }
   1420    this.handleNotificationImpl(cmdLine, tag, notificationData, alertService)
   1421      .catch(e => {
   1422        console.error(
   1423          `Error handling Windows notification with tag '${tag}':`,
   1424          e
   1425        );
   1426      })
   1427      .finally(() => {
   1428        if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
   1429          Services.startup.exitLastWindowClosingSurvivalArea();
   1430        }
   1431      });
   1432    return true;
   1433  },
   1434 
   1435  /**
   1436   * Returns when the signal is sent to the relevant notification handler, either:
   1437   * 1. The service worker via nsINotificationHandler for a web notification, or
   1438   * 2. SpecialMessageActions for a Messaging-System-invoked one
   1439   *
   1440   * @param {nsICommandLine} cmdLine
   1441   * @param {string} notificationId
   1442   * @param {string} notificationData
   1443   * @param {nsIWindowsAlertsService} alertService
   1444   */
   1445  async handleNotificationImpl(
   1446    cmdLine,
   1447    notificationId,
   1448    notificationData,
   1449    alertService
   1450  ) {
   1451    let { tagWasHandled } = await alertService.handleWindowsTag(notificationId);
   1452 
   1453    try {
   1454      notificationData = JSON.parse(notificationData);
   1455    } catch (e) {
   1456      console.error(
   1457        `Failed to parse (notificationData=${notificationData}) for Windows notification (id=${notificationId})`
   1458      );
   1459    }
   1460 
   1461    // This is awkward: the relaunch data set by the caller is _wrapped_
   1462    // into a compound object that includes additional notification data,
   1463    // and everything is exchanged as strings.  Unwrap and parse here.
   1464    let opaqueRelaunchData = null;
   1465    if (notificationData?.opaqueRelaunchData) {
   1466      try {
   1467        opaqueRelaunchData = JSON.parse(notificationData.opaqueRelaunchData);
   1468      } catch (e) {
   1469        console.error(
   1470          `Failed to parse (opaqueRelaunchData=${notificationData.opaqueRelaunchData}) for Windows notification (id=${notificationId})`
   1471        );
   1472      }
   1473    }
   1474 
   1475    if (notificationData?.privilegedName) {
   1476      Glean.browserLaunchedToHandle.systemNotification.record({
   1477        name: notificationData.privilegedName,
   1478      });
   1479    }
   1480 
   1481    // If we have an action in the notification data, this will be the
   1482    // window to perform the action in.
   1483    let winForAction;
   1484 
   1485    // Fall back to launchUrl to not break notifications opened from
   1486    // previous builds after browser updates, as such notification would
   1487    // still have the old field.
   1488    let origin = notificationData?.origin ?? notificationData?.launchUrl;
   1489    let action = notificationData?.action;
   1490 
   1491    if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
   1492      // Get a browser window where we can open a tab. Having a browser
   1493      // window here helps:
   1494      //
   1495      // 1. Earlier loading without having to wait for service worker
   1496      // 2. Makes sure a browser window loads even if no service worker exists
   1497      //    or the worker decides to not open anything
   1498      //
   1499      // instead of making user to wait on the initial navigator:blank.
   1500      winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
   1501      await lazy.BrowserUtils.promiseObserved(
   1502        "browser-delayed-startup-finished",
   1503        subject => subject == winForAction
   1504      );
   1505    }
   1506 
   1507    if (!tagWasHandled && origin && !opaqueRelaunchData) {
   1508      let originPrincipal =
   1509        Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
   1510 
   1511      const handler = Cc["@mozilla.org/notification-handler;1"].getService(
   1512        Ci.nsINotificationHandler
   1513      );
   1514 
   1515      await handler.respondOnClick(
   1516        originPrincipal,
   1517        notificationId,
   1518        action,
   1519        /* aAutoClosed */ true
   1520      );
   1521      return;
   1522    }
   1523 
   1524    // Relaunch in private windows only if we're in perma-private mode.
   1525    let allowPrivate = lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
   1526    winForAction = lazy.BrowserWindowTracker.getTopWindow({
   1527      private: allowPrivate,
   1528      allowFromInactiveWorkspace: true,
   1529    });
   1530 
   1531    // Note: at time of writing `opaqueRelaunchData` was only used by the
   1532    // Messaging System; if present it could be inferred that the message
   1533    // originated from the Messaging System. The Messaging System did not
   1534    // act on Windows 8 style notification callbacks, so there was no risk
   1535    // of duplicating behavior. If a non-Messaging System consumer is
   1536    // modified to populate `opaqueRelaunchData` or the Messaging System
   1537    // modified to use the callback directly, we will need to revisit
   1538    // this assumption.
   1539    if (opaqueRelaunchData && winForAction) {
   1540      // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
   1541      Services.tm.dispatchToMainThread(() => {
   1542        lazy.SpecialMessageActions.handleAction(
   1543          opaqueRelaunchData,
   1544          winForAction.gBrowser
   1545        );
   1546      });
   1547    }
   1548  },
   1549 
   1550  /* nsICommandLineHandler */
   1551  handle: function dch_handle(cmdLine) {
   1552    var urilist = [];
   1553    var principalList = [];
   1554 
   1555    if (
   1556      cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
   1557      cmdLine.findFlag("os-autostart", true) != -1
   1558    ) {
   1559      // Relaunching after reboot (or quickly opening the application on reboot) and launch-on-login interact.  If we see an after reboot command line while already running, ignore it.
   1560      return;
   1561    }
   1562 
   1563    if (this.handleNotification(cmdLine)) {
   1564      // This command is about notification and is handled already
   1565      return;
   1566    }
   1567 
   1568    if (
   1569      cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
   1570      Services.startup.wasSilentlyStarted
   1571    ) {
   1572      // If we are starting up in silent mode, don't open a window. We also need
   1573      // to make sure that the application doesn't immediately exit, so stay in
   1574      // a LastWindowClosingSurvivalArea until a window opens.
   1575      Services.startup.enterLastWindowClosingSurvivalArea();
   1576      Services.obs.addObserver(function windowOpenObserver() {
   1577        Services.startup.exitLastWindowClosingSurvivalArea();
   1578        Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
   1579      }, "domwindowopened");
   1580      return;
   1581    }
   1582 
   1583    if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
   1584      // Handle the case where we don't have a profile selected yet (e.g. the
   1585      // Profile Manager is displayed).
   1586      // On Windows, we will crash if we open an url and then select a profile.
   1587      // On macOS, if we open an url we don't experience a crash but a broken
   1588      // window is opened.
   1589      // To prevent this handle all url command line flags and set the
   1590      // command line's preventDefault to true to prevent the display of the ui.
   1591      // The initial command line will be retained when nsAppRunner calls
   1592      // LaunchChild though urls launched after the initial launch will be lost.
   1593      if (!this._haveProfile) {
   1594        try {
   1595          // This will throw when a profile has not been selected.
   1596          Services.dirsvc.get("ProfD", Ci.nsIFile);
   1597          this._haveProfile = true;
   1598        } catch (e) {
   1599          // eslint-disable-next-line no-empty
   1600          while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
   1601          cmdLine.preventDefault = true;
   1602        }
   1603      }
   1604    }
   1605 
   1606    // `-osint` and handling registered file types and protocols is Windows-only.
   1607    let launchedWithArg_osint =
   1608      AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
   1609    if (launchedWithArg_osint) {
   1610      cmdLine.handleFlag("osint", false);
   1611    }
   1612 
   1613    try {
   1614      var ar;
   1615      while ((ar = cmdLine.handleFlagWithParam("url", false))) {
   1616        let { uri, principal } = resolveURIInternal(cmdLine, ar);
   1617        urilist.push(uri);
   1618        principalList.push(principal);
   1619 
   1620        if (launchedWithArg_osint) {
   1621          launchedWithArg_osint = false;
   1622 
   1623          // We use the resolved URI here, even though it can produce
   1624          // surprising results where-by `-osint -url test.pdf` resolves to
   1625          // a query with search parameter "test.pdf".  But that shouldn't
   1626          // happen when Firefox is launched by Windows itself: files should
   1627          // exist and be resolved to file URLs.
   1628          const isLaunch =
   1629            cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
   1630 
   1631          maybeRecordToHandleTelemetry(uri, isLaunch);
   1632        }
   1633      }
   1634    } catch (e) {
   1635      console.error(e);
   1636    }
   1637 
   1638    if (cmdLine.findFlag("screenshot", true) != -1) {
   1639      // Shouldn't have to push principal here with the screenshot flag
   1640      lazy.HeadlessShell.handleCmdLineArgs(
   1641        cmdLine,
   1642        urilist.filter(shouldLoadURI).map(u => u.spec)
   1643      );
   1644      return;
   1645    }
   1646 
   1647    for (let i = 0; i < cmdLine.length; ++i) {
   1648      var curarg = cmdLine.getArgument(i);
   1649      if (curarg.match(/^-/)) {
   1650        console.error("Warning: unrecognized command line flag", curarg);
   1651        // To emulate the pre-nsICommandLine behavior, we ignore
   1652        // the argument after an unrecognized flag.
   1653        ++i;
   1654      } else {
   1655        try {
   1656          let { uri, principal } = resolveURIInternal(cmdLine, curarg);
   1657          urilist.push(uri);
   1658          principalList.push(principal);
   1659        } catch (e) {
   1660          console.error(
   1661            `Error opening URI ${curarg} from the command line:`,
   1662            e
   1663          );
   1664        }
   1665      }
   1666    }
   1667 
   1668    if (urilist.length) {
   1669      if (
   1670        cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
   1671        urilist.length == 1
   1672      ) {
   1673        // Try to find an existing window and load our URI into the
   1674        // current tab, new tab, or new window as prefs determine.
   1675        try {
   1676          handURIToExistingBrowser(
   1677            urilist[0],
   1678            Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
   1679            cmdLine,
   1680            false,
   1681            principalList[0] ?? lazy.gSystemPrincipal
   1682          );
   1683          return;
   1684        } catch (e) {}
   1685      }
   1686 
   1687      // Can't open multiple URLs without using system principal.
   1688      var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
   1689      if (URLlist.length) {
   1690        openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
   1691      }
   1692    } else if (!cmdLine.preventDefault) {
   1693      if (
   1694        AppConstants.platform == "win" &&
   1695        cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
   1696        lazy.WindowsUIUtils.inWin10TabletMode
   1697      ) {
   1698        // In Win10's tablet mode, do not create a new window, but reuse the
   1699        // existing one. (Win11's tablet mode is still windowed and has no need
   1700        // for this workaround.)
   1701        let win = lazy.BrowserWindowTracker.getTopWindow();
   1702        if (win) {
   1703          win.focus();
   1704          return;
   1705        }
   1706      }
   1707      openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
   1708    } else {
   1709      // Need a better solution in the future to avoid opening the blank window
   1710      // when command line parameters say we are not going to show a browser
   1711      // window, but for now the blank window getting closed quickly (and
   1712      // causing only a slight flicker) is better than leaving it open.
   1713      let win = Services.wm.getMostRecentWindow("navigator:blank");
   1714      if (win) {
   1715        win.close();
   1716      }
   1717    }
   1718  },
   1719 
   1720  helpInfo: "",
   1721 };