summaryrefslogtreecommitdiffstats
path: root/browser/components/BrowserContentHandler.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/BrowserContentHandler.sys.mjs1309
1 files changed, 1309 insertions, 0 deletions
diff --git a/browser/components/BrowserContentHandler.sys.mjs b/browser/components/BrowserContentHandler.sys.mjs
new file mode 100644
index 0000000000..60ba0a7d32
--- /dev/null
+++ b/browser/components/BrowserContentHandler.sys.mjs
@@ -0,0 +1,1309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ FirstStartup: "resource://gre/modules/FirstStartup.sys.mjs",
+ HeadlessShell: "resource:///modules/HeadlessShell.sys.mjs",
+ NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
+ ShellService: "resource:///modules/ShellService.sys.mjs",
+ UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+ HomePage: "resource:///modules/HomePage.jsm",
+ LaterRun: "resource:///modules/LaterRun.jsm",
+});
+XPCOMUtils.defineLazyServiceGetters(lazy, {
+ UpdateManager: ["@mozilla.org/updates/update-manager;1", "nsIUpdateManager"],
+ WinTaskbar: ["@mozilla.org/windows-taskbar;1", "nsIWinTaskbar"],
+ WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "gSystemPrincipal", () =>
+ Services.scriptSecurityManager.getSystemPrincipal()
+);
+
+XPCOMUtils.defineLazyGetter(lazy, "gWindowsAlertsService", () => {
+ // We might not have the Windows alerts service: e.g., on Windows 7 and Windows 8.
+ if (!("nsIWindowsAlertsService" in Ci)) {
+ return null;
+ }
+ return Cc["@mozilla.org/system-alerts-service;1"]
+ ?.getService(Ci.nsIAlertsService)
+ ?.QueryInterface(Ci.nsIWindowsAlertsService);
+});
+
+// One-time startup homepage override configurations
+const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
+const ONCE_PREF = "browser.startup.homepage_override.once";
+
+// Index of Private Browsing icon in firefox.exe
+// Must line up with the one in nsNativeAppSupportWin.h.
+const PRIVATE_BROWSING_ICON_INDEX = 5;
+
+function shouldLoadURI(aURI) {
+ if (aURI && !aURI.schemeIs("chrome")) {
+ return true;
+ }
+
+ dump("*** Preventing external load of chrome: URI into browser window\n");
+ dump(" Use --chrome <uri> instead\n");
+ return false;
+}
+
+function resolveURIInternal(aCmdLine, aArgument) {
+ var uri = aCmdLine.resolveURI(aArgument);
+ var uriFixup = Services.uriFixup;
+
+ if (!(uri instanceof Ci.nsIFileURL)) {
+ return Services.uriFixup.getFixupURIInfo(
+ aArgument,
+ uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ ).preferredURI;
+ }
+
+ try {
+ if (uri.file.exists()) {
+ return uri;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ // We have interpreted the argument as a relative file URI, but the file
+ // doesn't exist. Try URI fixup heuristics: see bug 290782.
+
+ try {
+ uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
+ } catch (e) {
+ console.error(e);
+ }
+
+ return uri;
+}
+
+let gKiosk = false;
+let gMajorUpgrade = false;
+let gFirstRunProfile = false;
+var gFirstWindow = false;
+
+const OVERRIDE_NONE = 0;
+const OVERRIDE_NEW_PROFILE = 1;
+const OVERRIDE_NEW_MSTONE = 2;
+const OVERRIDE_NEW_BUILD_ID = 3;
+/**
+ * Determines whether a home page override is needed.
+ * Returns:
+ * OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
+ * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
+ * Gecko milestone (i.e. right after an upgrade).
+ * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
+ * same Gecko milestone (i.e. after a nightly upgrade).
+ * OVERRIDE_NONE otherwise.
+ */
+function needHomepageOverride(prefb) {
+ var savedmstone = prefb.getCharPref(
+ "browser.startup.homepage_override.mstone",
+ ""
+ );
+
+ if (savedmstone == "ignore") {
+ return OVERRIDE_NONE;
+ }
+
+ var mstone = Services.appinfo.platformVersion;
+
+ var savedBuildID = prefb.getCharPref(
+ "browser.startup.homepage_override.buildID",
+ ""
+ );
+
+ var buildID = Services.appinfo.platformBuildID;
+
+ if (mstone != savedmstone) {
+ // Bug 462254. Previous releases had a default pref to suppress the EULA
+ // agreement if the platform's installer had already shown one. Now with
+ // about:rights we've removed the EULA stuff and default pref, but we need
+ // a way to make existing profiles retain the default that we removed.
+ if (savedmstone) {
+ prefb.setBoolPref("browser.rights.3.shown", true);
+
+ // Remember that we saw a major version change.
+ gMajorUpgrade = true;
+ }
+
+ prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
+ }
+
+ if (buildID != savedBuildID) {
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return OVERRIDE_NEW_BUILD_ID;
+ }
+
+ return OVERRIDE_NONE;
+}
+
+/**
+ * Gets the override page for the first run after the application has been
+ * updated.
+ * @param update
+ * The nsIUpdate for the update that has been applied.
+ * @param defaultOverridePage
+ * The default override page.
+ * @return The override page.
+ */
+function getPostUpdateOverridePage(update, defaultOverridePage) {
+ update = update.QueryInterface(Ci.nsIWritablePropertyBag);
+ let actions = update.getProperty("actions");
+ // When the update doesn't specify actions fallback to the original behavior
+ // of displaying the default override page.
+ if (!actions) {
+ return defaultOverridePage;
+ }
+
+ // The existence of silent or the non-existence of showURL in the actions both
+ // mean that an override page should not be displayed.
+ if (actions.includes("silent") || !actions.includes("showURL")) {
+ return "";
+ }
+
+ // If a policy was set to not allow the update.xml-provided
+ // URL to be used, use the default fallback (which will also
+ // be provided by the policy).
+ if (!Services.policies.isAllowed("postUpdateCustomPage")) {
+ return defaultOverridePage;
+ }
+
+ return update.getProperty("openURL") || defaultOverridePage;
+}
+
+/**
+ * Open a browser window. If this is the initial launch, this function will
+ * attempt to use the navigator:blank window opened by BrowserGlue.sys.mjs during
+ * early startup.
+ *
+ * @param cmdLine
+ * The nsICommandLine object given to nsICommandLineHandler's handle
+ * method.
+ * Used to check if we are processing the command line for the initial launch.
+ * @param triggeringPrincipal
+ * The nsIPrincipal to use as triggering principal for the page load(s).
+ * @param urlOrUrlList (optional)
+ * When omitted, the browser window will be opened with the default
+ * arguments, which will usually load the homepage.
+ * This can be a JS array of urls provided as strings, each url will be
+ * loaded in a tab. postData will be ignored in this case.
+ * This can be a single url to load in the new window, provided as a string.
+ * postData will be used in this case if provided.
+ * @param postData (optional)
+ * An nsIInputStream object to use as POST data when loading the provided
+ * url, or null.
+ * @param forcePrivate (optional)
+ * Boolean. If set to true, the new window will be a private browsing one.
+ *
+ * @returns {ChromeWindow}
+ * Returns the top level window opened.
+ */
+function openBrowserWindow(
+ cmdLine,
+ triggeringPrincipal,
+ urlOrUrlList,
+ postData = null,
+ forcePrivate = false
+) {
+ const isStartup =
+ cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
+
+ let args;
+ if (!urlOrUrlList) {
+ // Just pass in the defaultArgs directly. We'll use system principal on the other end.
+ args = [gBrowserContentHandler.getArgs(isStartup)];
+ } else if (Array.isArray(urlOrUrlList)) {
+ // There isn't an explicit way to pass a principal here, so we load multiple URLs
+ // with system principal when we get to actually loading them.
+ if (
+ !triggeringPrincipal ||
+ !triggeringPrincipal.equals(lazy.gSystemPrincipal)
+ ) {
+ throw new Error(
+ "Can't open multiple URLs with something other than system principal."
+ );
+ }
+ // Passing an nsIArray for the url disables the "|"-splitting behavior.
+ let uriArray = Cc["@mozilla.org/array;1"].createInstance(
+ Ci.nsIMutableArray
+ );
+ urlOrUrlList.forEach(function (uri) {
+ var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ sstring.data = uri;
+ uriArray.appendElement(sstring);
+ });
+ args = [uriArray];
+ } else {
+ let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ extraOptions.setPropertyAsBool("fromExternal", true);
+
+ // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
+ // ie. avoid the loadOneOrMoreURIs function.
+ // Also, we need to pass the triggering principal.
+ args = [
+ urlOrUrlList,
+ extraOptions,
+ null, // refererInfo
+ postData,
+ undefined, // allowThirdPartyFixup; this would be `false` but that
+ // needs a conversion. Hopefully bug 1485961 will fix.
+ undefined, // user context id
+ null, // origin principal
+ null, // origin storage principal
+ triggeringPrincipal,
+ ];
+ }
+
+ if (isStartup) {
+ let win = Services.wm.getMostRecentWindow("navigator:blank");
+ if (win) {
+ // Remove the windowtype of our blank window so that we don't close it
+ // later on when seeing cmdLine.preventDefault is true.
+ win.document.documentElement.removeAttribute("windowtype");
+
+ if (forcePrivate) {
+ win.docShell.QueryInterface(
+ Ci.nsILoadContext
+ ).usePrivateBrowsing = true;
+
+ if (
+ AppConstants.platform == "win" &&
+ lazy.NimbusFeatures.majorRelease2022.getVariable(
+ "feltPrivacyWindowSeparation"
+ )
+ ) {
+ lazy.WinTaskbar.setGroupIdForWindow(
+ win,
+ lazy.WinTaskbar.defaultPrivateGroupId
+ );
+ lazy.WindowsUIUtils.setWindowIconFromExe(
+ win,
+ Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
+ // This corresponds to the definitions in
+ // nsNativeAppSupportWin.h
+ PRIVATE_BROWSING_ICON_INDEX
+ );
+ }
+ }
+
+ let openTime = win.openTime;
+ win.location = AppConstants.BROWSER_CHROME_URL;
+ win.arguments = args; // <-- needs to be a plain JS array here.
+
+ ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
+ lazy.BrowserWindowTracker.registerOpeningWindow(win, forcePrivate);
+ return win;
+ }
+ }
+
+ // We can't provide arguments to openWindow as a JS array.
+ if (!urlOrUrlList) {
+ // If we have a single string guaranteed to not contain '|' we can simply
+ // wrap it in an nsISupportsString object.
+ let [url] = args;
+ args = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ args.data = url;
+ } else {
+ // Otherwise, pass an nsIArray.
+ if (args.length > 1) {
+ let string = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ string.data = args[0];
+ args[0] = string;
+ }
+ let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ args.forEach(a => {
+ array.appendElement(a);
+ });
+ args = array;
+ }
+
+ return lazy.BrowserWindowTracker.openWindow({
+ args,
+ features: gBrowserContentHandler.getFeatures(cmdLine),
+ private: forcePrivate,
+ });
+}
+
+function openPreferences(cmdLine, extraArgs) {
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
+}
+
+async function doSearch(searchTerm, cmdLine) {
+ // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
+ // preferences, but need nsIBrowserDOMWindow extensions
+ // Open the window immediately as BrowserContentHandler needs to
+ // be handled synchronously. Then load the search URI when the
+ // SearchService has loaded.
+ let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
+ await new Promise(resolve => {
+ Services.obs.addObserver(function observe(subject) {
+ if (subject == win) {
+ Services.obs.removeObserver(
+ observe,
+ "browser-delayed-startup-finished"
+ );
+ resolve();
+ }
+ }, "browser-delayed-startup-finished");
+ });
+
+ win.BrowserSearch.loadSearchFromCommandLine(
+ searchTerm,
+ lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
+ lazy.PrivateBrowsingUtils.isWindowPrivate(win),
+ lazy.gSystemPrincipal,
+ win.gBrowser.selectedBrowser.csp
+ ).catch(console.error);
+}
+
+export function nsBrowserContentHandler() {
+ if (!gBrowserContentHandler) {
+ gBrowserContentHandler = this;
+ }
+ return gBrowserContentHandler;
+}
+
+nsBrowserContentHandler.prototype = {
+ /* nsISupports */
+ QueryInterface: ChromeUtils.generateQI([
+ "nsICommandLineHandler",
+ "nsIBrowserHandler",
+ "nsIContentHandler",
+ "nsICommandLineValidator",
+ ]),
+
+ /* nsICommandLineHandler */
+ handle: function bch_handle(cmdLine) {
+ if (cmdLine.handleFlag("kiosk", false)) {
+ gKiosk = true;
+ }
+ if (cmdLine.handleFlag("disable-pinch", false)) {
+ let defaults = Services.prefs.getDefaultBranch(null);
+ defaults.setBoolPref("apz.allow_zooming", false);
+ Services.prefs.lockPref("apz.allow_zooming");
+ defaults.setCharPref("browser.gesture.pinch.in", "");
+ Services.prefs.lockPref("browser.gesture.pinch.in");
+ defaults.setCharPref("browser.gesture.pinch.in.shift", "");
+ Services.prefs.lockPref("browser.gesture.pinch.in.shift");
+ defaults.setCharPref("browser.gesture.pinch.out", "");
+ Services.prefs.lockPref("browser.gesture.pinch.out");
+ defaults.setCharPref("browser.gesture.pinch.out.shift", "");
+ Services.prefs.lockPref("browser.gesture.pinch.out.shift");
+ }
+ if (cmdLine.handleFlag("browser", false)) {
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
+ cmdLine.preventDefault = true;
+ }
+
+ // In the past, when an instance was not already running, the -remote
+ // option returned an error code. Any script or application invoking the
+ // -remote option is expected to be handling this case, otherwise they
+ // wouldn't be doing anything when there is no Firefox already running.
+ // Making the -remote option always return an error code makes those
+ // scripts or applications handle the situation as if Firefox was not
+ // already running.
+ if (cmdLine.handleFlag("remote", true)) {
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ }
+
+ var uriparam;
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
+ let uri = resolveURIInternal(cmdLine, uriparam);
+ if (!shouldLoadURI(uri)) {
+ continue;
+ }
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal, uri.spec);
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
+ let uri = resolveURIInternal(cmdLine, uriparam);
+ handURIToExistingBrowser(
+ uri,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ cmdLine,
+ false,
+ lazy.gSystemPrincipal
+ );
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
+ if (chromeParam) {
+ // Handle old preference dialog URLs.
+ if (
+ chromeParam == "chrome://browser/content/pref/pref.xul" ||
+ chromeParam == "chrome://browser/content/preferences/preferences.xul"
+ ) {
+ openPreferences(cmdLine);
+ cmdLine.preventDefault = true;
+ } else {
+ try {
+ let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
+ let isLocal = uri => {
+ let localSchemes = new Set(["chrome", "file", "resource"]);
+ if (uri instanceof Ci.nsINestedURI) {
+ uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
+ }
+ return localSchemes.has(uri.scheme);
+ };
+ if (isLocal(resolvedURI)) {
+ // If the URI is local, we are sure it won't wrongly inherit chrome privs
+ let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
+ // Provide 1 null argument, as openWindow has a different behavior
+ // when the arg count is 0.
+ let argArray = Cc["@mozilla.org/array;1"].createInstance(
+ Ci.nsIMutableArray
+ );
+ argArray.appendElement(null);
+ Services.ww.openWindow(
+ null,
+ resolvedURI.spec,
+ "_blank",
+ features,
+ argArray
+ );
+ cmdLine.preventDefault = true;
+ } else {
+ dump("*** Preventing load of web URI as chrome\n");
+ dump(
+ " If you're trying to load a webpage, do not pass --chrome.\n"
+ );
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+ if (cmdLine.handleFlag("preferences", false)) {
+ openPreferences(cmdLine);
+ cmdLine.preventDefault = true;
+ }
+ if (cmdLine.handleFlag("silent", false)) {
+ cmdLine.preventDefault = true;
+ }
+
+ try {
+ var privateWindowParam = cmdLine.handleFlagWithParam(
+ "private-window",
+ false
+ );
+ if (privateWindowParam) {
+ let forcePrivate = true;
+ let resolvedURI;
+ if (!lazy.PrivateBrowsingUtils.enabled) {
+ // Load about:privatebrowsing in a normal tab, which will display an error indicating
+ // access to private browsing has been disabled.
+ forcePrivate = false;
+ resolvedURI = Services.io.newURI("about:privatebrowsing");
+ } else {
+ resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
+ }
+ handURIToExistingBrowser(
+ resolvedURI,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ cmdLine,
+ forcePrivate,
+ lazy.gSystemPrincipal
+ );
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_INVALID_ARG) {
+ throw e;
+ }
+ // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
+ if (cmdLine.handleFlag("private-window", false)) {
+ openBrowserWindow(
+ cmdLine,
+ lazy.gSystemPrincipal,
+ "about:privatebrowsing",
+ null,
+ lazy.PrivateBrowsingUtils.enabled
+ );
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ var searchParam = cmdLine.handleFlagWithParam("search", false);
+ if (searchParam) {
+ doSearch(searchParam, cmdLine);
+ cmdLine.preventDefault = true;
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (
+ cmdLine.handleFlag("private", false) &&
+ lazy.PrivateBrowsingUtils.enabled
+ ) {
+ lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
+ if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ let win = Services.wm.getMostRecentWindow("navigator:blank");
+ if (win) {
+ win.docShell.QueryInterface(
+ Ci.nsILoadContext
+ ).usePrivateBrowsing = true;
+ }
+ }
+ }
+ if (cmdLine.handleFlag("setDefaultBrowser", false)) {
+ lazy.ShellService.setDefaultBrowser(true, true);
+ }
+
+ if (cmdLine.handleFlag("first-startup", false)) {
+ lazy.FirstStartup.init();
+ }
+
+ var fileParam = cmdLine.handleFlagWithParam("file", false);
+ if (fileParam) {
+ var file = cmdLine.resolveFile(fileParam);
+ var fileURI = Services.io.newFileURI(file);
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
+ cmdLine.preventDefault = true;
+ }
+
+ if (AppConstants.platform == "win") {
+ // Handle "? searchterm" for Windows Vista start menu integration
+ for (var i = cmdLine.length - 1; i >= 0; --i) {
+ var param = cmdLine.getArgument(i);
+ if (param.match(/^\? /)) {
+ cmdLine.removeArguments(i, i);
+ cmdLine.preventDefault = true;
+
+ searchParam = param.substr(2);
+ doSearch(searchParam, cmdLine);
+ }
+ }
+ }
+ },
+
+ get helpInfo() {
+ let info =
+ " --browser Open a browser window.\n" +
+ " --new-window <url> Open <url> in a new window.\n" +
+ " --new-tab <url> Open <url> in a new tab.\n" +
+ " --private-window <url> Open <url> in a new private window.\n";
+ if (AppConstants.platform == "win") {
+ info += " --preferences Open Options dialog.\n";
+ } else {
+ info += " --preferences Open Preferences dialog.\n";
+ }
+ info +=
+ " --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
+ info +=
+ " --window-size width[,height] Width and optionally height of screenshot.\n";
+ info +=
+ " --search <term> Search <term> with your default search engine.\n";
+ info += " --setDefaultBrowser Set this app as the default browser.\n";
+ info +=
+ " --first-startup Run post-install actions before opening a new window.\n";
+ info += " --kiosk Start the browser in kiosk mode.\n";
+ info +=
+ " --disable-pinch Disable touch-screen and touch-pad pinch gestures.\n";
+ return info;
+ },
+
+ /* nsIBrowserHandler */
+
+ get defaultArgs() {
+ return this.getArgs();
+ },
+
+ getArgs(isStartup = false) {
+ var prefb = Services.prefs;
+
+ if (!gFirstWindow) {
+ gFirstWindow = true;
+ if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ return "about:privatebrowsing";
+ }
+ }
+
+ var override;
+ var overridePage = "";
+ var additionalPage = "";
+ var willRestoreSession = false;
+ try {
+ // Read the old value of homepage_override.mstone before
+ // needHomepageOverride updates it, so that we can later add it to the
+ // URL if we do end up showing an overridePage. This makes it possible
+ // to have the overridePage's content vary depending on the version we're
+ // upgrading from.
+ let old_mstone = Services.prefs.getCharPref(
+ "browser.startup.homepage_override.mstone",
+ "unknown"
+ );
+ let old_buildId = Services.prefs.getCharPref(
+ "browser.startup.homepage_override.buildID",
+ "unknown"
+ );
+ override = needHomepageOverride(prefb);
+ if (override != OVERRIDE_NONE) {
+ switch (override) {
+ case OVERRIDE_NEW_PROFILE:
+ // New profile.
+ gFirstRunProfile = true;
+ if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
+ break;
+ }
+ overridePage = Services.urlFormatter.formatURLPref(
+ "startup.homepage_welcome_url"
+ );
+ additionalPage = Services.urlFormatter.formatURLPref(
+ "startup.homepage_welcome_url.additional"
+ );
+ // Turn on 'later run' pages for new profiles.
+ lazy.LaterRun.enabled = true;
+ break;
+ case OVERRIDE_NEW_MSTONE:
+ // Check whether we will restore a session. If we will, we assume
+ // that this is an "update" session. This does not take crashes
+ // into account because that requires waiting for the session file
+ // to be read. If a crash occurs after updating, before restarting,
+ // we may open the startPage in addition to restoring the session.
+ willRestoreSession =
+ lazy.SessionStartup.isAutomaticRestoreEnabled();
+
+ overridePage = Services.urlFormatter.formatURLPref(
+ "startup.homepage_override_url"
+ );
+ let update = lazy.UpdateManager.readyUpdate;
+ if (
+ update &&
+ Services.vc.compare(update.appVersion, old_mstone) > 0
+ ) {
+ overridePage = getPostUpdateOverridePage(update, overridePage);
+ // Send the update ping to signal that the update was successful.
+ lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
+ }
+
+ overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
+ break;
+ case OVERRIDE_NEW_BUILD_ID:
+ if (lazy.UpdateManager.readyUpdate) {
+ // Send the update ping to signal that the update was successful.
+ lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
+ }
+ break;
+ }
+ }
+ } catch (ex) {}
+
+ // formatURLPref might return "about:blank" if getting the pref fails
+ if (overridePage == "about:blank") {
+ overridePage = "";
+ }
+
+ // Allow showing a one-time startup override if we're not showing one
+ if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
+ try {
+ // Show if we haven't passed the expiration or there's no expiration
+ const { expire, url } = JSON.parse(
+ Services.urlFormatter.formatURLPref(ONCE_PREF)
+ );
+ if (!(Date.now() > expire)) {
+ // Only set allowed urls as override pages
+ overridePage = url
+ .split("|")
+ .map(val => {
+ try {
+ return new URL(val);
+ } catch (ex) {
+ // Invalid URL, so filter out below
+ console.error(`Invalid once url: ${ex}`);
+ return null;
+ }
+ })
+ .filter(
+ parsed =>
+ parsed &&
+ parsed.protocol == "https:" &&
+ // Only accept exact hostname or subdomain; without port
+ ONCE_DOMAINS.includes(
+ Services.eTLD.getBaseDomainFromHost(parsed.host)
+ )
+ )
+ .join("|");
+
+ // Be noisy as properly configured urls should be unchanged
+ if (overridePage != url) {
+ console.error(`Mismatched once urls: ${url}`);
+ }
+ }
+ } catch (ex) {
+ // Invalid json pref, so ignore (and clear below)
+ console.error(`Invalid once pref: ${ex}`);
+ } finally {
+ prefb.clearUserPref(ONCE_PREF);
+ }
+ }
+
+ if (!additionalPage) {
+ additionalPage = lazy.LaterRun.getURL() || "";
+ }
+
+ if (additionalPage && additionalPage != "about:blank") {
+ if (overridePage) {
+ overridePage += "|" + additionalPage;
+ } else {
+ overridePage = additionalPage;
+ }
+ }
+
+ var startPage = "";
+ try {
+ var choice = prefb.getIntPref("browser.startup.page");
+ if (choice == 1 || choice == 3) {
+ startPage = lazy.HomePage.get();
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ if (startPage == "about:blank") {
+ startPage = "";
+ }
+
+ let skipStartPage =
+ override == OVERRIDE_NEW_PROFILE &&
+ prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
+ // Only show the startPage if we're not restoring an update session and are
+ // not set to skip the start page on this profile
+ if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
+ return overridePage + "|" + startPage;
+ }
+
+ return overridePage || startPage || "about:blank";
+ },
+
+ mFeatures: null,
+
+ getFeatures: function bch_features(cmdLine) {
+ if (this.mFeatures === null) {
+ this.mFeatures = "";
+
+ if (cmdLine) {
+ try {
+ var width = cmdLine.handleFlagWithParam("width", false);
+ var height = cmdLine.handleFlagWithParam("height", false);
+ var left = cmdLine.handleFlagWithParam("left", false);
+ var top = cmdLine.handleFlagWithParam("top", false);
+
+ if (width) {
+ this.mFeatures += ",width=" + width;
+ }
+ if (height) {
+ this.mFeatures += ",height=" + height;
+ }
+ if (left) {
+ this.mFeatures += ",left=" + left;
+ }
+ if (top) {
+ this.mFeatures += ",top=" + top;
+ }
+ } catch (e) {}
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ this.mFeatures += ",private";
+ }
+
+ if (
+ Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
+ !Services.wm.getMostRecentWindow("navigator:browser")
+ ) {
+ this.mFeatures += ",suppressanimation";
+ }
+ }
+
+ return this.mFeatures;
+ },
+
+ get kiosk() {
+ return gKiosk;
+ },
+
+ get majorUpgrade() {
+ return gMajorUpgrade;
+ },
+
+ set majorUpgrade(val) {
+ gMajorUpgrade = val;
+ },
+
+ get firstRunProfile() {
+ return gFirstRunProfile;
+ },
+
+ set firstRunProfile(val) {
+ gFirstRunProfile = val;
+ },
+
+ /* nsIContentHandler */
+
+ handleContent: function bch_handleContent(contentType, context, request) {
+ const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+
+ try {
+ var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
+ Ci.nsIWebNavigationInfo
+ );
+ if (!webNavInfo.isTypeSupported(contentType)) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+ } catch (e) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ request.QueryInterface(Ci.nsIChannel);
+ handURIToExistingBrowser(
+ request.URI,
+ Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ null,
+ false,
+ request.loadInfo.triggeringPrincipal
+ );
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ /* nsICommandLineValidator */
+ validate: function bch_validate(cmdLine) {
+ var urlFlagIdx = cmdLine.findFlag("url", false);
+ if (
+ urlFlagIdx > -1 &&
+ cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
+ ) {
+ var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
+ if (
+ cmdLine.length != urlFlagIdx + 2 ||
+ /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ }
+ var isDefault = false;
+ try {
+ var url =
+ Services.urlFormatter.formatURLPref("app.support.baseURL") +
+ "win10-default-browser";
+ if (urlParam == url) {
+ isDefault = lazy.ShellService.isDefaultBrowser(false, false);
+ }
+ } catch (ex) {}
+ if (isDefault) {
+ // Firefox is already the default HTTP handler.
+ // We don't have to show the instruction page.
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ }
+ }
+ },
+};
+var gBrowserContentHandler = new nsBrowserContentHandler();
+
+function handURIToExistingBrowser(
+ uri,
+ location,
+ cmdLine,
+ forcePrivate,
+ triggeringPrincipal
+) {
+ if (!shouldLoadURI(uri)) {
+ return;
+ }
+
+ let openInWindow = ({ browserDOMWindow }) => {
+ browserDOMWindow.openURI(
+ uri,
+ null,
+ location,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
+ triggeringPrincipal
+ );
+ };
+
+ // Unless using a private window is forced, open external links in private
+ // windows only if we're in perma-private mode.
+ let allowPrivate =
+ forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
+ let navWin = lazy.BrowserWindowTracker.getTopWindow({
+ private: allowPrivate,
+ });
+
+ if (navWin) {
+ openInWindow(navWin);
+ return;
+ }
+
+ let pending = lazy.BrowserWindowTracker.getPendingWindow({
+ private: allowPrivate,
+ });
+ if (pending) {
+ // Note that we cannot make this function async as some callers rely on
+ // catching exceptions it can throw in some cases and some of those callers
+ // cannot be made async.
+ pending.then(openInWindow);
+ return;
+ }
+
+ // if we couldn't load it in an existing window, open a new one
+ openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
+}
+
+/**
+ * If given URI is a file type or a protocol, record telemetry that
+ * Firefox was invoked or launched (if `isLaunch` is truth-y). If the
+ * file type or protocol is not registered by default, record it as
+ * ".<other extension>" or "<other protocol>".
+ *
+ * @param uri
+ * The URI Firefox was asked to handle.
+ * @param isLaunch
+ * truth-y if Firefox was launched/started rather than running and invoked.
+ */
+function maybeRecordToHandleTelemetry(uri, isLaunch) {
+ let scalar = isLaunch
+ ? "os.environment.launched_to_handle"
+ : "os.environment.invoked_to_handle";
+
+ if (uri instanceof Ci.nsIFileURL) {
+ let extension = "." + uri.fileExtension.toLowerCase();
+ // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
+ // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
+ let registeredExtensions = new Set([
+ ".avif",
+ ".htm",
+ ".html",
+ ".pdf",
+ ".shtml",
+ ".xht",
+ ".xhtml",
+ ".svg",
+ ".webp",
+ ]);
+ if (registeredExtensions.has(extension)) {
+ Services.telemetry.keyedScalarAdd(scalar, extension, 1);
+ } else {
+ Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
+ }
+ } else if (uri) {
+ let scheme = uri.scheme.toLowerCase();
+ let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
+ if (registeredSchemes.has(scheme)) {
+ Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
+ } else {
+ Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
+ }
+ }
+}
+
+export function nsDefaultCommandLineHandler() {}
+
+nsDefaultCommandLineHandler.prototype = {
+ /* nsISupports */
+ QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
+
+ _haveProfile: false,
+
+ /* nsICommandLineHandler */
+ handle: function dch_handle(cmdLine) {
+ var urilist = [];
+
+ if (AppConstants.platform == "win") {
+ // Windows itself does disk I/O when the notification service is
+ // initialized, so make sure that is lazy.
+ while (true) {
+ let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
+ if (!tag) {
+ break;
+ }
+
+ let alertService = lazy.gWindowsAlertsService;
+ if (!alertService) {
+ console.error("Windows alert service not available.");
+ break;
+ }
+
+ async function handleNotification() {
+ const { launchUrl, privilegedName } =
+ await alertService.handleWindowsTag(tag);
+
+ // If `launchUrl` or `privilegedName` are provided, then the
+ // notification was from a prior instance of the application and we
+ // need to handled fallback behavior.
+ if (launchUrl || privilegedName) {
+ console.info(
+ `Completing Windows notification (tag=${JSON.stringify(
+ tag
+ )}, launchUrl=${JSON.stringify(
+ launchUrl
+ )}, privilegedName=${JSON.stringify(privilegedName)}))`
+ );
+ }
+
+ if (privilegedName) {
+ Services.telemetry.setEventRecordingEnabled(
+ "browser.launched_to_handle",
+ true
+ );
+ Glean.browserLaunchedToHandle.systemNotification.record({
+ name: privilegedName,
+ });
+ }
+
+ if (launchUrl) {
+ let uri = resolveURIInternal(cmdLine, launchUrl);
+ if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ // Try to find an existing window and load our URI into the current
+ // tab, new tab, or new window as prefs determine.
+ try {
+ handURIToExistingBrowser(
+ uri,
+ Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ cmdLine,
+ false,
+ lazy.gSystemPrincipal
+ );
+ return;
+ } catch (e) {}
+ }
+
+ if (shouldLoadURI(uri)) {
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal, [uri.spec]);
+ }
+ } else if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ // No URL provided, but notification was interacted with while the
+ // application was closed. Fall back to opening the browser without url.
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
+ }
+ }
+
+ // Notification handling occurs asynchronously to prevent blocking the
+ // main thread. As a result we won't have the information we need to open
+ // a new tab in the case of notification fallback handling before
+ // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
+ // the browser from exiting in case early blank window is pref'd off.
+ if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ Services.startup.enterLastWindowClosingSurvivalArea();
+ }
+ handleNotification()
+ .catch(e => {
+ console.error(
+ `Error handling Windows notification with tag '${tag}': ${e}`
+ );
+ })
+ .finally(() => {
+ if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
+ Services.startup.exitLastWindowClosingSurvivalArea();
+ }
+ });
+
+ return;
+ }
+ }
+
+ if (
+ cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
+ Services.startup.wasSilentlyStarted
+ ) {
+ // If we are starting up in silent mode, don't open a window. We also need
+ // to make sure that the application doesn't immediately exit, so stay in
+ // a LastWindowClosingSurvivalArea until a window opens.
+ Services.startup.enterLastWindowClosingSurvivalArea();
+ Services.obs.addObserver(function windowOpenObserver() {
+ Services.startup.exitLastWindowClosingSurvivalArea();
+ Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
+ }, "domwindowopened");
+ return;
+ }
+
+ if (AppConstants.platform == "win") {
+ // If we don't have a profile selected yet (e.g. the Profile Manager is
+ // displayed) we will crash if we open an url and then select a profile. To
+ // prevent this handle all url command line flags and set the command line's
+ // preventDefault to true to prevent the display of the ui. The initial
+ // command line will be retained when nsAppRunner calls LaunchChild though
+ // urls launched after the initial launch will be lost.
+ if (!this._haveProfile) {
+ try {
+ // This will throw when a profile has not been selected.
+ Services.dirsvc.get("ProfD", Ci.nsIFile);
+ this._haveProfile = true;
+ } catch (e) {
+ // eslint-disable-next-line no-empty
+ while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
+ cmdLine.preventDefault = true;
+ }
+ }
+ }
+
+ // `-osint` and handling registered file types and protocols is Windows-only.
+ let launchedWithArg_osint =
+ AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
+ if (launchedWithArg_osint) {
+ cmdLine.handleFlag("osint", false);
+ }
+
+ try {
+ var ar;
+ while ((ar = cmdLine.handleFlagWithParam("url", false))) {
+ var uri = resolveURIInternal(cmdLine, ar);
+ urilist.push(uri);
+
+ if (launchedWithArg_osint) {
+ launchedWithArg_osint = false;
+
+ // We use the resolved URI here, even though it can produce
+ // surprising results where-by `-osint -url test.pdf` resolves to
+ // a query with search parameter "test.pdf". But that shouldn't
+ // happen when Firefox is launched by Windows itself: files should
+ // exist and be resolved to file URLs.
+ const isLaunch =
+ cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
+
+ maybeRecordToHandleTelemetry(uri, isLaunch);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+
+ if (
+ AppConstants.platform == "win" &&
+ cmdLine.handleFlag("to-handle-default-browser-agent", false)
+ ) {
+ // The Default Browser Agent launches Firefox in response to a Windows
+ // native notification, but it does so in a non-standard manner.
+ Services.telemetry.setEventRecordingEnabled(
+ "browser.launched_to_handle",
+ true
+ );
+ Glean.browserLaunchedToHandle.systemNotification.record({
+ name: "default-browser-agent",
+ });
+
+ let thanksURI = Services.io.newURI(
+ Services.urlFormatter.formatURLPref(
+ "browser.shell.defaultBrowserAgent.thanksURL"
+ )
+ );
+ urilist.push(thanksURI);
+ }
+
+ if (cmdLine.findFlag("screenshot", true) != -1) {
+ lazy.HeadlessShell.handleCmdLineArgs(
+ cmdLine,
+ urilist.filter(shouldLoadURI).map(u => u.spec)
+ );
+ return;
+ }
+
+ for (let i = 0; i < cmdLine.length; ++i) {
+ var curarg = cmdLine.getArgument(i);
+ if (curarg.match(/^-/)) {
+ console.error(
+ "Warning: unrecognized command line flag " + curarg + "\n"
+ );
+ // To emulate the pre-nsICommandLine behavior, we ignore
+ // the argument after an unrecognized flag.
+ ++i;
+ } else {
+ try {
+ urilist.push(resolveURIInternal(cmdLine, curarg));
+ } catch (e) {
+ console.error(
+ "Error opening URI '" +
+ curarg +
+ "' from the command line: " +
+ e +
+ "\n"
+ );
+ }
+ }
+ }
+
+ if (urilist.length) {
+ if (
+ cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
+ urilist.length == 1
+ ) {
+ // Try to find an existing window and load our URI into the
+ // current tab, new tab, or new window as prefs determine.
+ try {
+ handURIToExistingBrowser(
+ urilist[0],
+ Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+ cmdLine,
+ false,
+ lazy.gSystemPrincipal
+ );
+ return;
+ } catch (e) {}
+ }
+
+ var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
+ if (URLlist.length) {
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
+ }
+ } else if (!cmdLine.preventDefault) {
+ if (
+ AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
+ cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
+ lazy.WindowsUIUtils.inTabletMode
+ ) {
+ // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
+ let win = lazy.BrowserWindowTracker.getTopWindow();
+ if (win) {
+ win.focus();
+ return;
+ }
+ }
+ openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
+ } else {
+ // Need a better solution in the future to avoid opening the blank window
+ // when command line parameters say we are not going to show a browser
+ // window, but for now the blank window getting closed quickly (and
+ // causing only a slight flicker) is better than leaving it open.
+ let win = Services.wm.getMostRecentWindow("navigator:blank");
+ if (win) {
+ win.close();
+ }
+ }
+ },
+
+ helpInfo: "",
+};