forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1408 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1408 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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, {
 | |
|   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
 | |
|   FirstStartup: "resource://gre/modules/FirstStartup.sys.mjs",
 | |
|   HeadlessShell: "resource:///modules/HeadlessShell.sys.mjs",
 | |
|   HomePage: "resource:///modules/HomePage.sys.mjs",
 | |
|   LaterRun: "resource:///modules/LaterRun.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",
 | |
|   SpecialMessageActions:
 | |
|     "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
 | |
|   UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
 | |
| });
 | |
| 
 | |
| 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"],
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineLazyGetter(lazy, "gSystemPrincipal", () =>
 | |
|   Services.scriptSecurityManager.getSystemPrincipal()
 | |
| );
 | |
| 
 | |
| ChromeUtils.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) {
 | |
|   // If using Firefox protocol handler remove it from URI
 | |
|   // at this stage. This is before we would otherwise
 | |
|   // record telemetry so do that here.
 | |
|   if (aArgument.startsWith("firefox:")) {
 | |
|     aArgument = aArgument.substring("firefox:".length);
 | |
|     Services.telemetry.keyedScalarAdd(
 | |
|       "os.environment.launched_to_handle",
 | |
|       "firefox",
 | |
|       1
 | |
|     );
 | |
|   }
 | |
|   if (aArgument.startsWith("firefox-private:")) {
 | |
|     aArgument = aArgument.substring("firefox-private:".length);
 | |
|     Services.telemetry.keyedScalarAdd(
 | |
|       "os.environment.launched_to_handle",
 | |
|       "firefox-private",
 | |
|       1
 | |
|     );
 | |
|   }
 | |
|   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) ||
 | |
|       cmdLine.handleFlagWithParam("kiosk-monitor", 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;
 | |
|     }
 | |
| 
 | |
|     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
 | |
|       );
 | |
|       // Check for Firefox private browsing protocol handler here.
 | |
|       let url = null;
 | |
|       let urlFlagIdx = cmdLine.findFlag("url", false);
 | |
|       if (urlFlagIdx > -1 && cmdLine.length > 1) {
 | |
|         url = cmdLine.getArgument(urlFlagIdx + 1);
 | |
|       }
 | |
|       if (privateWindowParam || url?.startsWith("firefox-private:")) {
 | |
|         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 if (url?.startsWith("firefox-private:")) {
 | |
|           // We can safely remove the flag and parameter now.
 | |
|           cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
 | |
|           resolvedURI = resolveURIInternal(cmdLine, url);
 | |
|         } 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)) {
 | |
|       // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
 | |
|       // is an implementation of an interface method and changing it to be async would be complicated
 | |
|       // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
 | |
|       // an await.
 | |
|       lazy.ShellService.setDefaultBrowser(true).catch(e => {
 | |
|         console.error("setDefaultBrowser failed:", e);
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     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 +=
 | |
|       "  --kiosk-monitor <num> Place kiosk browser window on given monitor.\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.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
 | |
|             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);
 | |
|               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
 | |
|             }
 | |
| 
 | |
|             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);
 | |
|               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
 | |
|             }
 | |
|             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 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 (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
 | |
|       // Since the purpose of this is to record early in startup,
 | |
|       // only record on launches, not already-running invocations.
 | |
|       Services.telemetry.setEventRecordingEnabled("telemetry", true);
 | |
|       Glean.fogValidation.validateEarlyEvent.record();
 | |
|     }
 | |
| 
 | |
|     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;
 | |
|         }
 | |
| 
 | |
|         // All notifications will invoke Firefox with an action.  Prior to Bug 1805514,
 | |
|         // this data was extracted from the Windows toast object directly (keyed by the
 | |
|         // notification ID) and not passed over the command line.  This is acceptable
 | |
|         // because the data passed is chrome-controlled, but if we implement the `actions`
 | |
|         // part of the DOM Web Notifications API, this will no longer be true:
 | |
|         // content-controlled data might transit over the command line.  This could lead
 | |
|         // to escaping bugs and overflows.  In the future, we intend to avoid any such
 | |
|         // issue by once again extracting all such data from the Windows toast object.
 | |
|         let notificationData = cmdLine.handleFlagWithParam(
 | |
|           "notification-windowsAction",
 | |
|           false
 | |
|         );
 | |
|         if (!notificationData) {
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         let alertService = lazy.gWindowsAlertsService;
 | |
|         if (!alertService) {
 | |
|           console.error("Windows alert service not available.");
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         async function handleNotification() {
 | |
|           let { tagWasHandled } = await alertService.handleWindowsTag(tag);
 | |
| 
 | |
|           // If the tag was not handled via callback, then the notification was
 | |
|           // from a prior instance of the application and we need to handle
 | |
|           // fallback behavior.
 | |
|           if (!tagWasHandled) {
 | |
|             console.info(
 | |
|               `Completing Windows notification (tag=${JSON.stringify(
 | |
|                 tag
 | |
|               )}, notificationData=${notificationData})`
 | |
|             );
 | |
|             try {
 | |
|               notificationData = JSON.parse(notificationData);
 | |
|             } catch (e) {
 | |
|               console.error(
 | |
|                 `Completing Windows notification (tag=${JSON.stringify(
 | |
|                   tag
 | |
|                 )}, failed to parse (notificationData=${notificationData})`
 | |
|               );
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           // This is awkward: the relaunch data set by the caller is _wrapped_
 | |
|           // into a compound object that includes additional notification data,
 | |
|           // and everything is exchanged as strings.  Unwrap and parse here.
 | |
|           let opaqueRelaunchData = null;
 | |
|           if (notificationData?.opaqueRelaunchData) {
 | |
|             try {
 | |
|               opaqueRelaunchData = JSON.parse(
 | |
|                 notificationData.opaqueRelaunchData
 | |
|               );
 | |
|             } catch (e) {
 | |
|               console.error(
 | |
|                 `Completing Windows notification (tag=${JSON.stringify(
 | |
|                   tag
 | |
|                 )}, failed to parse (opaqueRelaunchData=${
 | |
|                   notificationData.opaqueRelaunchData
 | |
|                 })`
 | |
|               );
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           if (notificationData?.privilegedName) {
 | |
|             Services.telemetry.setEventRecordingEnabled(
 | |
|               "browser.launched_to_handle",
 | |
|               true
 | |
|             );
 | |
|             Glean.browserLaunchedToHandle.systemNotification.record({
 | |
|               name: notificationData.privilegedName,
 | |
|             });
 | |
|           }
 | |
| 
 | |
|           // If we have an action in the notification data, this will be the
 | |
|           // window to perform the action in.
 | |
|           let winForAction;
 | |
| 
 | |
|           if (notificationData?.launchUrl && !opaqueRelaunchData) {
 | |
|             // Unprivileged Web Notifications contain a launch URL and are handled
 | |
|             // slightly differently than privileged notifications with actions.
 | |
|             let uri = resolveURIInternal(cmdLine, notificationData.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.
 | |
|             winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
 | |
|             await new Promise(resolve => {
 | |
|               Services.obs.addObserver(function observe(subject) {
 | |
|                 if (subject == winForAction) {
 | |
|                   Services.obs.removeObserver(
 | |
|                     observe,
 | |
|                     "browser-delayed-startup-finished"
 | |
|                   );
 | |
|                   resolve();
 | |
|                 }
 | |
|               }, "browser-delayed-startup-finished");
 | |
|             });
 | |
|           } else {
 | |
|             // Relaunch in private windows only if we're in perma-private mode.
 | |
|             let allowPrivate =
 | |
|               lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
 | |
|             winForAction = lazy.BrowserWindowTracker.getTopWindow({
 | |
|               private: allowPrivate,
 | |
|             });
 | |
|           }
 | |
| 
 | |
|           if (opaqueRelaunchData && winForAction) {
 | |
|             // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
 | |
|             Services.tm.dispatchToMainThread(() => {
 | |
|               lazy.SpecialMessageActions.handleAction(
 | |
|                 opaqueRelaunchData,
 | |
|                 winForAction.gBrowser
 | |
|               );
 | |
|             });
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // 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" || AppConstants.platform == "macosx") {
 | |
|       // Handle the case where we don't have a profile selected yet (e.g. the
 | |
|       // Profile Manager is displayed).
 | |
|       // On Windows, we will crash if we open an url and then select a profile.
 | |
|       // On macOS, if we open an url we don't experience a crash but a broken
 | |
|       // window is opened.
 | |
|       // 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);
 | |
|         // 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
 | |
|           );
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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.platform == "win" &&
 | |
|         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: "",
 | |
| };
 | 
