Backed out changeset 496886cb30a5 (bug 1867152) for bc failures on browser_user_input...
[gecko.git] / browser / components / BrowserContentHandler.sys.mjs
blob18f3dd6ec9e05d21b9079d9ff90409ae9d3e54b8
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
8 const lazy = {};
10 ChromeUtils.defineESModuleGetters(lazy, {
11   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
12   FirstStartup: "resource://gre/modules/FirstStartup.sys.mjs",
13   HeadlessShell: "resource:///modules/HeadlessShell.sys.mjs",
14   HomePage: "resource:///modules/HomePage.sys.mjs",
15   LaterRun: "resource:///modules/LaterRun.sys.mjs",
16   NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
17   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
18   SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
19   ShellService: "resource:///modules/ShellService.sys.mjs",
20   SpecialMessageActions:
21     "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
22   UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
23 });
25 XPCOMUtils.defineLazyServiceGetters(lazy, {
26   UpdateManager: ["@mozilla.org/updates/update-manager;1", "nsIUpdateManager"],
27   WinTaskbar: ["@mozilla.org/windows-taskbar;1", "nsIWinTaskbar"],
28   WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
29 });
31 ChromeUtils.defineLazyGetter(lazy, "gSystemPrincipal", () =>
32   Services.scriptSecurityManager.getSystemPrincipal()
35 ChromeUtils.defineLazyGetter(lazy, "gWindowsAlertsService", () => {
36   // We might not have the Windows alerts service: e.g., on Windows 7 and Windows 8.
37   if (!("nsIWindowsAlertsService" in Ci)) {
38     return null;
39   }
40   return Cc["@mozilla.org/system-alerts-service;1"]
41     ?.getService(Ci.nsIAlertsService)
42     ?.QueryInterface(Ci.nsIWindowsAlertsService);
43 });
45 // One-time startup homepage override configurations
46 const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
47 const ONCE_PREF = "browser.startup.homepage_override.once";
49 // Index of Private Browsing icon in firefox.exe
50 // Must line up with the one in nsNativeAppSupportWin.h.
51 const PRIVATE_BROWSING_ICON_INDEX = 5;
53 function shouldLoadURI(aURI) {
54   if (aURI && !aURI.schemeIs("chrome")) {
55     return true;
56   }
58   dump("*** Preventing external load of chrome: URI into browser window\n");
59   dump("    Use --chrome <uri> instead\n");
60   return false;
63 function resolveURIInternal(aCmdLine, aArgument) {
64   // If using Firefox protocol handler remove it from URI
65   // at this stage. This is before we would otherwise
66   // record telemetry so do that here.
67   if (aArgument.startsWith("firefox:")) {
68     aArgument = aArgument.substring("firefox:".length);
69     Services.telemetry.keyedScalarAdd(
70       "os.environment.launched_to_handle",
71       "firefox",
72       1
73     );
74   }
75   if (aArgument.startsWith("firefox-private:")) {
76     aArgument = aArgument.substring("firefox-private:".length);
77     Services.telemetry.keyedScalarAdd(
78       "os.environment.launched_to_handle",
79       "firefox-private",
80       1
81     );
82   }
83   var uri = aCmdLine.resolveURI(aArgument);
84   var uriFixup = Services.uriFixup;
86   if (!(uri instanceof Ci.nsIFileURL)) {
87     return Services.uriFixup.getFixupURIInfo(
88       aArgument,
89       uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
90     ).preferredURI;
91   }
93   try {
94     if (uri.file.exists()) {
95       return uri;
96     }
97   } catch (e) {
98     console.error(e);
99   }
101   // We have interpreted the argument as a relative file URI, but the file
102   // doesn't exist. Try URI fixup heuristics: see bug 290782.
104   try {
105     uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
106   } catch (e) {
107     console.error(e);
108   }
110   return uri;
113 let gKiosk = false;
114 let gMajorUpgrade = false;
115 let gFirstRunProfile = false;
116 var gFirstWindow = false;
118 const OVERRIDE_NONE = 0;
119 const OVERRIDE_NEW_PROFILE = 1;
120 const OVERRIDE_NEW_MSTONE = 2;
121 const OVERRIDE_NEW_BUILD_ID = 3;
123  * Determines whether a home page override is needed.
124  * Returns:
125  *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
126  *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
127  *                      Gecko milestone (i.e. right after an upgrade).
128  *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
129  *                        same Gecko milestone (i.e. after a nightly upgrade).
130  *  OVERRIDE_NONE otherwise.
131  */
132 function needHomepageOverride(prefb) {
133   var savedmstone = prefb.getCharPref(
134     "browser.startup.homepage_override.mstone",
135     ""
136   );
138   if (savedmstone == "ignore") {
139     return OVERRIDE_NONE;
140   }
142   var mstone = Services.appinfo.platformVersion;
144   var savedBuildID = prefb.getCharPref(
145     "browser.startup.homepage_override.buildID",
146     ""
147   );
149   var buildID = Services.appinfo.platformBuildID;
151   if (mstone != savedmstone) {
152     // Bug 462254. Previous releases had a default pref to suppress the EULA
153     // agreement if the platform's installer had already shown one. Now with
154     // about:rights we've removed the EULA stuff and default pref, but we need
155     // a way to make existing profiles retain the default that we removed.
156     if (savedmstone) {
157       prefb.setBoolPref("browser.rights.3.shown", true);
159       // Remember that we saw a major version change.
160       gMajorUpgrade = true;
161     }
163     prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
164     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
165     return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
166   }
168   if (buildID != savedBuildID) {
169     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
170     return OVERRIDE_NEW_BUILD_ID;
171   }
173   return OVERRIDE_NONE;
177  * Gets the override page for the first run after the application has been
178  * updated.
179  * @param  update
180  *         The nsIUpdate for the update that has been applied.
181  * @param  defaultOverridePage
182  *         The default override page.
183  * @return The override page.
184  */
185 function getPostUpdateOverridePage(update, defaultOverridePage) {
186   update = update.QueryInterface(Ci.nsIWritablePropertyBag);
187   let actions = update.getProperty("actions");
188   // When the update doesn't specify actions fallback to the original behavior
189   // of displaying the default override page.
190   if (!actions) {
191     return defaultOverridePage;
192   }
194   // The existence of silent or the non-existence of showURL in the actions both
195   // mean that an override page should not be displayed.
196   if (actions.includes("silent") || !actions.includes("showURL")) {
197     return "";
198   }
200   // If a policy was set to not allow the update.xml-provided
201   // URL to be used, use the default fallback (which will also
202   // be provided by the policy).
203   if (!Services.policies.isAllowed("postUpdateCustomPage")) {
204     return defaultOverridePage;
205   }
207   return update.getProperty("openURL") || defaultOverridePage;
211  * Open a browser window. If this is the initial launch, this function will
212  * attempt to use the navigator:blank window opened by BrowserGlue.sys.mjs during
213  * early startup.
215  * @param cmdLine
216  *        The nsICommandLine object given to nsICommandLineHandler's handle
217  *        method.
218  *        Used to check if we are processing the command line for the initial launch.
219  * @param triggeringPrincipal
220  *        The nsIPrincipal to use as triggering principal for the page load(s).
221  * @param urlOrUrlList (optional)
222  *        When omitted, the browser window will be opened with the default
223  *        arguments, which will usually load the homepage.
224  *        This can be a JS array of urls provided as strings, each url will be
225  *        loaded in a tab. postData will be ignored in this case.
226  *        This can be a single url to load in the new window, provided as a string.
227  *        postData will be used in this case if provided.
228  * @param postData (optional)
229  *        An nsIInputStream object to use as POST data when loading the provided
230  *        url, or null.
231  * @param forcePrivate (optional)
232  *        Boolean. If set to true, the new window will be a private browsing one.
234  * @returns {ChromeWindow}
235  *          Returns the top level window opened.
236  */
237 function openBrowserWindow(
238   cmdLine,
239   triggeringPrincipal,
240   urlOrUrlList,
241   postData = null,
242   forcePrivate = false
243 ) {
244   const isStartup =
245     cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
247   let args;
248   if (!urlOrUrlList) {
249     // Just pass in the defaultArgs directly. We'll use system principal on the other end.
250     args = [gBrowserContentHandler.getArgs(isStartup)];
251   } else if (Array.isArray(urlOrUrlList)) {
252     // There isn't an explicit way to pass a principal here, so we load multiple URLs
253     // with system principal when we get to actually loading them.
254     if (
255       !triggeringPrincipal ||
256       !triggeringPrincipal.equals(lazy.gSystemPrincipal)
257     ) {
258       throw new Error(
259         "Can't open multiple URLs with something other than system principal."
260       );
261     }
262     // Passing an nsIArray for the url disables the "|"-splitting behavior.
263     let uriArray = Cc["@mozilla.org/array;1"].createInstance(
264       Ci.nsIMutableArray
265     );
266     urlOrUrlList.forEach(function (uri) {
267       var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
268         Ci.nsISupportsString
269       );
270       sstring.data = uri;
271       uriArray.appendElement(sstring);
272     });
273     args = [uriArray];
274   } else {
275     let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
276       Ci.nsIWritablePropertyBag2
277     );
278     extraOptions.setPropertyAsBool("fromExternal", true);
280     // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
281     // ie. avoid the loadOneOrMoreURIs function.
282     // Also, we need to pass the triggering principal.
283     args = [
284       urlOrUrlList,
285       extraOptions,
286       null, // refererInfo
287       postData,
288       undefined, // allowThirdPartyFixup; this would be `false` but that
289       // needs a conversion. Hopefully bug 1485961 will fix.
290       undefined, // user context id
291       null, // origin principal
292       null, // origin storage principal
293       triggeringPrincipal,
294     ];
295   }
297   if (isStartup) {
298     let win = Services.wm.getMostRecentWindow("navigator:blank");
299     if (win) {
300       // Remove the windowtype of our blank window so that we don't close it
301       // later on when seeing cmdLine.preventDefault is true.
302       win.document.documentElement.removeAttribute("windowtype");
304       if (forcePrivate) {
305         win.docShell.QueryInterface(
306           Ci.nsILoadContext
307         ).usePrivateBrowsing = true;
309         if (
310           AppConstants.platform == "win" &&
311           lazy.NimbusFeatures.majorRelease2022.getVariable(
312             "feltPrivacyWindowSeparation"
313           )
314         ) {
315           lazy.WinTaskbar.setGroupIdForWindow(
316             win,
317             lazy.WinTaskbar.defaultPrivateGroupId
318           );
319           lazy.WindowsUIUtils.setWindowIconFromExe(
320             win,
321             Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
322             // This corresponds to the definitions in
323             // nsNativeAppSupportWin.h
324             PRIVATE_BROWSING_ICON_INDEX
325           );
326         }
327       }
329       let openTime = win.openTime;
330       win.location = AppConstants.BROWSER_CHROME_URL;
331       win.arguments = args; // <-- needs to be a plain JS array here.
333       ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
334       lazy.BrowserWindowTracker.registerOpeningWindow(win, forcePrivate);
335       return win;
336     }
337   }
339   // We can't provide arguments to openWindow as a JS array.
340   if (!urlOrUrlList) {
341     // If we have a single string guaranteed to not contain '|' we can simply
342     // wrap it in an nsISupportsString object.
343     let [url] = args;
344     args = Cc["@mozilla.org/supports-string;1"].createInstance(
345       Ci.nsISupportsString
346     );
347     args.data = url;
348   } else {
349     // Otherwise, pass an nsIArray.
350     if (args.length > 1) {
351       let string = Cc["@mozilla.org/supports-string;1"].createInstance(
352         Ci.nsISupportsString
353       );
354       string.data = args[0];
355       args[0] = string;
356     }
357     let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
358     args.forEach(a => {
359       array.appendElement(a);
360     });
361     args = array;
362   }
364   return lazy.BrowserWindowTracker.openWindow({
365     args,
366     features: gBrowserContentHandler.getFeatures(cmdLine),
367     private: forcePrivate,
368   });
371 function openPreferences(cmdLine, extraArgs) {
372   openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
375 async function doSearch(searchTerm, cmdLine) {
376   // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
377   // preferences, but need nsIBrowserDOMWindow extensions
378   // Open the window immediately as BrowserContentHandler needs to
379   // be handled synchronously. Then load the search URI when the
380   // SearchService has loaded.
381   let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
382   await new Promise(resolve => {
383     Services.obs.addObserver(function observe(subject) {
384       if (subject == win) {
385         Services.obs.removeObserver(
386           observe,
387           "browser-delayed-startup-finished"
388         );
389         resolve();
390       }
391     }, "browser-delayed-startup-finished");
392   });
394   win.BrowserSearch.loadSearchFromCommandLine(
395     searchTerm,
396     lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
397       lazy.PrivateBrowsingUtils.isWindowPrivate(win),
398     lazy.gSystemPrincipal,
399     win.gBrowser.selectedBrowser.csp
400   ).catch(console.error);
403 export function nsBrowserContentHandler() {
404   if (!gBrowserContentHandler) {
405     gBrowserContentHandler = this;
406   }
407   return gBrowserContentHandler;
410 nsBrowserContentHandler.prototype = {
411   /* nsISupports */
412   QueryInterface: ChromeUtils.generateQI([
413     "nsICommandLineHandler",
414     "nsIBrowserHandler",
415     "nsIContentHandler",
416     "nsICommandLineValidator",
417   ]),
419   /* nsICommandLineHandler */
420   handle: function bch_handle(cmdLine) {
421     if (
422       cmdLine.handleFlag("kiosk", false) ||
423       cmdLine.handleFlagWithParam("kiosk-monitor", false)
424     ) {
425       gKiosk = true;
426     }
427     if (cmdLine.handleFlag("disable-pinch", false)) {
428       let defaults = Services.prefs.getDefaultBranch(null);
429       defaults.setBoolPref("apz.allow_zooming", false);
430       Services.prefs.lockPref("apz.allow_zooming");
431       defaults.setCharPref("browser.gesture.pinch.in", "");
432       Services.prefs.lockPref("browser.gesture.pinch.in");
433       defaults.setCharPref("browser.gesture.pinch.in.shift", "");
434       Services.prefs.lockPref("browser.gesture.pinch.in.shift");
435       defaults.setCharPref("browser.gesture.pinch.out", "");
436       Services.prefs.lockPref("browser.gesture.pinch.out");
437       defaults.setCharPref("browser.gesture.pinch.out.shift", "");
438       Services.prefs.lockPref("browser.gesture.pinch.out.shift");
439     }
440     if (cmdLine.handleFlag("browser", false)) {
441       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
442       cmdLine.preventDefault = true;
443     }
445     // In the past, when an instance was not already running, the -remote
446     // option returned an error code. Any script or application invoking the
447     // -remote option is expected to be handling this case, otherwise they
448     // wouldn't be doing anything when there is no Firefox already running.
449     // Making the -remote option always return an error code makes those
450     // scripts or applications handle the situation as if Firefox was not
451     // already running.
452     if (cmdLine.handleFlag("remote", true)) {
453       throw Components.Exception("", Cr.NS_ERROR_ABORT);
454     }
456     var uriparam;
457     try {
458       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
459         let uri = resolveURIInternal(cmdLine, uriparam);
460         if (!shouldLoadURI(uri)) {
461           continue;
462         }
463         openBrowserWindow(cmdLine, lazy.gSystemPrincipal, uri.spec);
464         cmdLine.preventDefault = true;
465       }
466     } catch (e) {
467       console.error(e);
468     }
470     try {
471       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
472         let uri = resolveURIInternal(cmdLine, uriparam);
473         handURIToExistingBrowser(
474           uri,
475           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
476           cmdLine,
477           false,
478           lazy.gSystemPrincipal
479         );
480         cmdLine.preventDefault = true;
481       }
482     } catch (e) {
483       console.error(e);
484     }
486     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
487     if (chromeParam) {
488       // Handle old preference dialog URLs.
489       if (
490         chromeParam == "chrome://browser/content/pref/pref.xul" ||
491         chromeParam == "chrome://browser/content/preferences/preferences.xul"
492       ) {
493         openPreferences(cmdLine);
494         cmdLine.preventDefault = true;
495       } else {
496         try {
497           let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
498           let isLocal = uri => {
499             let localSchemes = new Set(["chrome", "file", "resource"]);
500             if (uri instanceof Ci.nsINestedURI) {
501               uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
502             }
503             return localSchemes.has(uri.scheme);
504           };
505           if (isLocal(resolvedURI)) {
506             // If the URI is local, we are sure it won't wrongly inherit chrome privs
507             let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
508             // Provide 1 null argument, as openWindow has a different behavior
509             // when the arg count is 0.
510             let argArray = Cc["@mozilla.org/array;1"].createInstance(
511               Ci.nsIMutableArray
512             );
513             argArray.appendElement(null);
514             Services.ww.openWindow(
515               null,
516               resolvedURI.spec,
517               "_blank",
518               features,
519               argArray
520             );
521             cmdLine.preventDefault = true;
522           } else {
523             dump("*** Preventing load of web URI as chrome\n");
524             dump(
525               "    If you're trying to load a webpage, do not pass --chrome.\n"
526             );
527           }
528         } catch (e) {
529           console.error(e);
530         }
531       }
532     }
533     if (cmdLine.handleFlag("preferences", false)) {
534       openPreferences(cmdLine);
535       cmdLine.preventDefault = true;
536     }
537     if (cmdLine.handleFlag("silent", false)) {
538       cmdLine.preventDefault = true;
539     }
541     try {
542       var privateWindowParam = cmdLine.handleFlagWithParam(
543         "private-window",
544         false
545       );
546       // Check for Firefox private browsing protocol handler here.
547       let url = null;
548       let urlFlagIdx = cmdLine.findFlag("url", false);
549       if (urlFlagIdx > -1 && cmdLine.length > 1) {
550         url = cmdLine.getArgument(urlFlagIdx + 1);
551       }
552       if (privateWindowParam || url?.startsWith("firefox-private:")) {
553         let forcePrivate = true;
554         let resolvedURI;
555         if (!lazy.PrivateBrowsingUtils.enabled) {
556           // Load about:privatebrowsing in a normal tab, which will display an error indicating
557           // access to private browsing has been disabled.
558           forcePrivate = false;
559           resolvedURI = Services.io.newURI("about:privatebrowsing");
560         } else if (url?.startsWith("firefox-private:")) {
561           // We can safely remove the flag and parameter now.
562           cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
563           resolvedURI = resolveURIInternal(cmdLine, url);
564         } else {
565           resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
566         }
567         handURIToExistingBrowser(
568           resolvedURI,
569           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
570           cmdLine,
571           forcePrivate,
572           lazy.gSystemPrincipal
573         );
574         cmdLine.preventDefault = true;
575       }
576     } catch (e) {
577       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
578         throw e;
579       }
580       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
581       if (cmdLine.handleFlag("private-window", false)) {
582         openBrowserWindow(
583           cmdLine,
584           lazy.gSystemPrincipal,
585           "about:privatebrowsing",
586           null,
587           lazy.PrivateBrowsingUtils.enabled
588         );
589         cmdLine.preventDefault = true;
590       }
591     }
593     var searchParam = cmdLine.handleFlagWithParam("search", false);
594     if (searchParam) {
595       doSearch(searchParam, cmdLine);
596       cmdLine.preventDefault = true;
597     }
599     // The global PB Service consumes this flag, so only eat it in per-window
600     // PB builds.
601     if (
602       cmdLine.handleFlag("private", false) &&
603       lazy.PrivateBrowsingUtils.enabled
604     ) {
605       lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
606       if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
607         let win = Services.wm.getMostRecentWindow("navigator:blank");
608         if (win) {
609           win.docShell.QueryInterface(
610             Ci.nsILoadContext
611           ).usePrivateBrowsing = true;
612         }
613       }
614     }
615     if (cmdLine.handleFlag("setDefaultBrowser", false)) {
616       // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
617       // is an implementation of an interface method and changing it to be async would be complicated
618       // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
619       // an await.
620       lazy.ShellService.setDefaultBrowser(true).catch(e => {
621         console.error("setDefaultBrowser failed:", e);
622       });
623     }
625     if (cmdLine.handleFlag("first-startup", false)) {
626       lazy.FirstStartup.init();
627     }
629     var fileParam = cmdLine.handleFlagWithParam("file", false);
630     if (fileParam) {
631       var file = cmdLine.resolveFile(fileParam);
632       var fileURI = Services.io.newFileURI(file);
633       openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
634       cmdLine.preventDefault = true;
635     }
637     if (AppConstants.platform == "win") {
638       // Handle "? searchterm" for Windows Vista start menu integration
639       for (var i = cmdLine.length - 1; i >= 0; --i) {
640         var param = cmdLine.getArgument(i);
641         if (param.match(/^\? /)) {
642           cmdLine.removeArguments(i, i);
643           cmdLine.preventDefault = true;
645           searchParam = param.substr(2);
646           doSearch(searchParam, cmdLine);
647         }
648       }
649     }
650   },
652   get helpInfo() {
653     let info =
654       "  --browser          Open a browser window.\n" +
655       "  --new-window <url> Open <url> in a new window.\n" +
656       "  --new-tab <url>    Open <url> in a new tab.\n" +
657       "  --private-window <url> Open <url> in a new private window.\n";
658     if (AppConstants.platform == "win") {
659       info += "  --preferences      Open Options dialog.\n";
660     } else {
661       info += "  --preferences      Open Preferences dialog.\n";
662     }
663     info +=
664       "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
665     info +=
666       "  --window-size width[,height] Width and optionally height of screenshot.\n";
667     info +=
668       "  --search <term>    Search <term> with your default search engine.\n";
669     info += "  --setDefaultBrowser Set this app as the default browser.\n";
670     info +=
671       "  --first-startup    Run post-install actions before opening a new window.\n";
672     info += "  --kiosk            Start the browser in kiosk mode.\n";
673     info +=
674       "  --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
675     info +=
676       "  --disable-pinch    Disable touch-screen and touch-pad pinch gestures.\n";
677     return info;
678   },
680   /* nsIBrowserHandler */
682   get defaultArgs() {
683     return this.getArgs();
684   },
686   getArgs(isStartup = false) {
687     var prefb = Services.prefs;
689     if (!gFirstWindow) {
690       gFirstWindow = true;
691       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
692         return "about:privatebrowsing";
693       }
694     }
696     var override;
697     var overridePage = "";
698     var additionalPage = "";
699     var willRestoreSession = false;
700     try {
701       // Read the old value of homepage_override.mstone before
702       // needHomepageOverride updates it, so that we can later add it to the
703       // URL if we do end up showing an overridePage. This makes it possible
704       // to have the overridePage's content vary depending on the version we're
705       // upgrading from.
706       let old_mstone = Services.prefs.getCharPref(
707         "browser.startup.homepage_override.mstone",
708         "unknown"
709       );
710       let old_buildId = Services.prefs.getCharPref(
711         "browser.startup.homepage_override.buildID",
712         "unknown"
713       );
714       override = needHomepageOverride(prefb);
715       if (override != OVERRIDE_NONE) {
716         switch (override) {
717           case OVERRIDE_NEW_PROFILE:
718             // New profile.
719             gFirstRunProfile = true;
720             if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
721               break;
722             }
723             overridePage = Services.urlFormatter.formatURLPref(
724               "startup.homepage_welcome_url"
725             );
726             additionalPage = Services.urlFormatter.formatURLPref(
727               "startup.homepage_welcome_url.additional"
728             );
729             // Turn on 'later run' pages for new profiles.
730             lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
731             break;
732           case OVERRIDE_NEW_MSTONE:
733             // Check whether we will restore a session. If we will, we assume
734             // that this is an "update" session. This does not take crashes
735             // into account because that requires waiting for the session file
736             // to be read. If a crash occurs after updating, before restarting,
737             // we may open the startPage in addition to restoring the session.
738             willRestoreSession =
739               lazy.SessionStartup.isAutomaticRestoreEnabled();
741             overridePage = Services.urlFormatter.formatURLPref(
742               "startup.homepage_override_url"
743             );
744             let update = lazy.UpdateManager.readyUpdate;
745             if (
746               update &&
747               Services.vc.compare(update.appVersion, old_mstone) > 0
748             ) {
749               overridePage = getPostUpdateOverridePage(update, overridePage);
750               // Send the update ping to signal that the update was successful.
751               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
752               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
753             }
755             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
756             break;
757           case OVERRIDE_NEW_BUILD_ID:
758             if (lazy.UpdateManager.readyUpdate) {
759               // Send the update ping to signal that the update was successful.
760               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
761               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
762             }
763             break;
764         }
765       }
766     } catch (ex) {}
768     // formatURLPref might return "about:blank" if getting the pref fails
769     if (overridePage == "about:blank") {
770       overridePage = "";
771     }
773     // Allow showing a one-time startup override if we're not showing one
774     if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
775       try {
776         // Show if we haven't passed the expiration or there's no expiration
777         const { expire, url } = JSON.parse(
778           Services.urlFormatter.formatURLPref(ONCE_PREF)
779         );
780         if (!(Date.now() > expire)) {
781           // Only set allowed urls as override pages
782           overridePage = url
783             .split("|")
784             .map(val => {
785               try {
786                 return new URL(val);
787               } catch (ex) {
788                 // Invalid URL, so filter out below
789                 console.error("Invalid once url:", ex);
790                 return null;
791               }
792             })
793             .filter(
794               parsed =>
795                 parsed &&
796                 parsed.protocol == "https:" &&
797                 // Only accept exact hostname or subdomain; without port
798                 ONCE_DOMAINS.includes(
799                   Services.eTLD.getBaseDomainFromHost(parsed.host)
800                 )
801             )
802             .join("|");
804           // Be noisy as properly configured urls should be unchanged
805           if (overridePage != url) {
806             console.error(`Mismatched once urls: ${url}`);
807           }
808         }
809       } catch (ex) {
810         // Invalid json pref, so ignore (and clear below)
811         console.error("Invalid once pref:", ex);
812       } finally {
813         prefb.clearUserPref(ONCE_PREF);
814       }
815     }
817     if (!additionalPage) {
818       additionalPage = lazy.LaterRun.getURL() || "";
819     }
821     if (additionalPage && additionalPage != "about:blank") {
822       if (overridePage) {
823         overridePage += "|" + additionalPage;
824       } else {
825         overridePage = additionalPage;
826       }
827     }
829     var startPage = "";
830     try {
831       var choice = prefb.getIntPref("browser.startup.page");
832       if (choice == 1 || choice == 3) {
833         startPage = lazy.HomePage.get();
834       }
835     } catch (e) {
836       console.error(e);
837     }
839     if (startPage == "about:blank") {
840       startPage = "";
841     }
843     let skipStartPage =
844       override == OVERRIDE_NEW_PROFILE &&
845       prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
846     // Only show the startPage if we're not restoring an update session and are
847     // not set to skip the start page on this profile
848     if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
849       return overridePage + "|" + startPage;
850     }
852     return overridePage || startPage || "about:blank";
853   },
855   mFeatures: null,
857   getFeatures: function bch_features(cmdLine) {
858     if (this.mFeatures === null) {
859       this.mFeatures = "";
861       if (cmdLine) {
862         try {
863           var width = cmdLine.handleFlagWithParam("width", false);
864           var height = cmdLine.handleFlagWithParam("height", false);
865           var left = cmdLine.handleFlagWithParam("left", false);
866           var top = cmdLine.handleFlagWithParam("top", false);
868           if (width) {
869             this.mFeatures += ",width=" + width;
870           }
871           if (height) {
872             this.mFeatures += ",height=" + height;
873           }
874           if (left) {
875             this.mFeatures += ",left=" + left;
876           }
877           if (top) {
878             this.mFeatures += ",top=" + top;
879           }
880         } catch (e) {}
881       }
883       // The global PB Service consumes this flag, so only eat it in per-window
884       // PB builds.
885       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
886         this.mFeatures += ",private";
887       }
889       if (
890         Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
891         !Services.wm.getMostRecentWindow("navigator:browser")
892       ) {
893         this.mFeatures += ",suppressanimation";
894       }
895     }
897     return this.mFeatures;
898   },
900   get kiosk() {
901     return gKiosk;
902   },
904   get majorUpgrade() {
905     return gMajorUpgrade;
906   },
908   set majorUpgrade(val) {
909     gMajorUpgrade = val;
910   },
912   get firstRunProfile() {
913     return gFirstRunProfile;
914   },
916   set firstRunProfile(val) {
917     gFirstRunProfile = val;
918   },
920   /* nsIContentHandler */
922   handleContent: function bch_handleContent(contentType, context, request) {
923     const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
925     try {
926       var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
927         Ci.nsIWebNavigationInfo
928       );
929       if (!webNavInfo.isTypeSupported(contentType)) {
930         throw NS_ERROR_WONT_HANDLE_CONTENT;
931       }
932     } catch (e) {
933       throw NS_ERROR_WONT_HANDLE_CONTENT;
934     }
936     request.QueryInterface(Ci.nsIChannel);
937     handURIToExistingBrowser(
938       request.URI,
939       Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
940       null,
941       false,
942       request.loadInfo.triggeringPrincipal
943     );
944     request.cancel(Cr.NS_BINDING_ABORTED);
945   },
947   /* nsICommandLineValidator */
948   validate: function bch_validate(cmdLine) {
949     var urlFlagIdx = cmdLine.findFlag("url", false);
950     if (
951       urlFlagIdx > -1 &&
952       cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
953     ) {
954       var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
955       if (
956         cmdLine.length != urlFlagIdx + 2 ||
957         /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
958       ) {
959         throw Components.Exception("", Cr.NS_ERROR_ABORT);
960       }
961     }
962   },
964 var gBrowserContentHandler = new nsBrowserContentHandler();
966 function handURIToExistingBrowser(
967   uri,
968   location,
969   cmdLine,
970   forcePrivate,
971   triggeringPrincipal
972 ) {
973   if (!shouldLoadURI(uri)) {
974     return;
975   }
977   let openInWindow = ({ browserDOMWindow }) => {
978     browserDOMWindow.openURI(
979       uri,
980       null,
981       location,
982       Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
983       triggeringPrincipal
984     );
985   };
987   // Unless using a private window is forced, open external links in private
988   // windows only if we're in perma-private mode.
989   let allowPrivate =
990     forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
991   let navWin = lazy.BrowserWindowTracker.getTopWindow({
992     private: allowPrivate,
993   });
995   if (navWin) {
996     openInWindow(navWin);
997     return;
998   }
1000   let pending = lazy.BrowserWindowTracker.getPendingWindow({
1001     private: allowPrivate,
1002   });
1003   if (pending) {
1004     // Note that we cannot make this function async as some callers rely on
1005     // catching exceptions it can throw in some cases and some of those callers
1006     // cannot be made async.
1007     pending.then(openInWindow);
1008     return;
1009   }
1011   // if we couldn't load it in an existing window, open a new one
1012   openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
1016  * If given URI is a file type or a protocol, record telemetry that
1017  * Firefox was invoked or launched (if `isLaunch` is truth-y).  If the
1018  * file type or protocol is not registered by default, record it as
1019  * ".<other extension>" or "<other protocol>".
1021  * @param uri
1022  *        The URI Firefox was asked to handle.
1023  * @param isLaunch
1024  *        truth-y if Firefox was launched/started rather than running and invoked.
1025  */
1026 function maybeRecordToHandleTelemetry(uri, isLaunch) {
1027   let scalar = isLaunch
1028     ? "os.environment.launched_to_handle"
1029     : "os.environment.invoked_to_handle";
1031   if (uri instanceof Ci.nsIFileURL) {
1032     let extension = "." + uri.fileExtension.toLowerCase();
1033     // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
1034     // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
1035     let registeredExtensions = new Set([
1036       ".avif",
1037       ".htm",
1038       ".html",
1039       ".pdf",
1040       ".shtml",
1041       ".xht",
1042       ".xhtml",
1043       ".svg",
1044       ".webp",
1045     ]);
1046     if (registeredExtensions.has(extension)) {
1047       Services.telemetry.keyedScalarAdd(scalar, extension, 1);
1048     } else {
1049       Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
1050     }
1051   } else if (uri) {
1052     let scheme = uri.scheme.toLowerCase();
1053     let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
1054     if (registeredSchemes.has(scheme)) {
1055       Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
1056     } else {
1057       Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1058     }
1059   }
1062 export function nsDefaultCommandLineHandler() {}
1064 nsDefaultCommandLineHandler.prototype = {
1065   /* nsISupports */
1066   QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1068   _haveProfile: false,
1070   /* nsICommandLineHandler */
1071   handle: function dch_handle(cmdLine) {
1072     var urilist = [];
1074     if (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1075       // Since the purpose of this is to record early in startup,
1076       // only record on launches, not already-running invocations.
1077       Services.telemetry.setEventRecordingEnabled("telemetry", true);
1078       Glean.fogValidation.validateEarlyEvent.record();
1079     }
1081     if (AppConstants.platform == "win") {
1082       // Windows itself does disk I/O when the notification service is
1083       // initialized, so make sure that is lazy.
1084       while (true) {
1085         let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
1086         if (!tag) {
1087           break;
1088         }
1090         // All notifications will invoke Firefox with an action.  Prior to Bug 1805514,
1091         // this data was extracted from the Windows toast object directly (keyed by the
1092         // notification ID) and not passed over the command line.  This is acceptable
1093         // because the data passed is chrome-controlled, but if we implement the `actions`
1094         // part of the DOM Web Notifications API, this will no longer be true:
1095         // content-controlled data might transit over the command line.  This could lead
1096         // to escaping bugs and overflows.  In the future, we intend to avoid any such
1097         // issue by once again extracting all such data from the Windows toast object.
1098         let notificationData = cmdLine.handleFlagWithParam(
1099           "notification-windowsAction",
1100           false
1101         );
1102         if (!notificationData) {
1103           break;
1104         }
1106         let alertService = lazy.gWindowsAlertsService;
1107         if (!alertService) {
1108           console.error("Windows alert service not available.");
1109           break;
1110         }
1112         async function handleNotification() {
1113           let { tagWasHandled } = await alertService.handleWindowsTag(tag);
1115           // If the tag was not handled via callback, then the notification was
1116           // from a prior instance of the application and we need to handle
1117           // fallback behavior.
1118           if (!tagWasHandled) {
1119             console.info(
1120               `Completing Windows notification (tag=${JSON.stringify(
1121                 tag
1122               )}, notificationData=${notificationData})`
1123             );
1124             try {
1125               notificationData = JSON.parse(notificationData);
1126             } catch (e) {
1127               console.error(
1128                 `Completing Windows notification (tag=${JSON.stringify(
1129                   tag
1130                 )}, failed to parse (notificationData=${notificationData})`
1131               );
1132             }
1133           }
1135           // This is awkward: the relaunch data set by the caller is _wrapped_
1136           // into a compound object that includes additional notification data,
1137           // and everything is exchanged as strings.  Unwrap and parse here.
1138           let opaqueRelaunchData = null;
1139           if (notificationData?.opaqueRelaunchData) {
1140             try {
1141               opaqueRelaunchData = JSON.parse(
1142                 notificationData.opaqueRelaunchData
1143               );
1144             } catch (e) {
1145               console.error(
1146                 `Completing Windows notification (tag=${JSON.stringify(
1147                   tag
1148                 )}, failed to parse (opaqueRelaunchData=${
1149                   notificationData.opaqueRelaunchData
1150                 })`
1151               );
1152             }
1153           }
1155           if (notificationData?.privilegedName) {
1156             Services.telemetry.setEventRecordingEnabled(
1157               "browser.launched_to_handle",
1158               true
1159             );
1160             Glean.browserLaunchedToHandle.systemNotification.record({
1161               name: notificationData.privilegedName,
1162             });
1163           }
1165           // If we have an action in the notification data, this will be the
1166           // window to perform the action in.
1167           let winForAction;
1169           if (notificationData?.launchUrl && !opaqueRelaunchData) {
1170             // Unprivileged Web Notifications contain a launch URL and are handled
1171             // slightly differently than privileged notifications with actions.
1172             let uri = resolveURIInternal(cmdLine, notificationData.launchUrl);
1173             if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1174               // Try to find an existing window and load our URI into the current
1175               // tab, new tab, or new window as prefs determine.
1176               try {
1177                 handURIToExistingBrowser(
1178                   uri,
1179                   Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1180                   cmdLine,
1181                   false,
1182                   lazy.gSystemPrincipal
1183                 );
1184                 return;
1185               } catch (e) {}
1186             }
1188             if (shouldLoadURI(uri)) {
1189               openBrowserWindow(cmdLine, lazy.gSystemPrincipal, [uri.spec]);
1190             }
1191           } else if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1192             // No URL provided, but notification was interacted with while the
1193             // application was closed. Fall back to opening the browser without url.
1194             winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1195             await new Promise(resolve => {
1196               Services.obs.addObserver(function observe(subject) {
1197                 if (subject == winForAction) {
1198                   Services.obs.removeObserver(
1199                     observe,
1200                     "browser-delayed-startup-finished"
1201                   );
1202                   resolve();
1203                 }
1204               }, "browser-delayed-startup-finished");
1205             });
1206           } else {
1207             // Relaunch in private windows only if we're in perma-private mode.
1208             let allowPrivate =
1209               lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1210             winForAction = lazy.BrowserWindowTracker.getTopWindow({
1211               private: allowPrivate,
1212             });
1213           }
1215           if (opaqueRelaunchData && winForAction) {
1216             // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
1217             Services.tm.dispatchToMainThread(() => {
1218               lazy.SpecialMessageActions.handleAction(
1219                 opaqueRelaunchData,
1220                 winForAction.gBrowser
1221               );
1222             });
1223           }
1224         }
1226         // Notification handling occurs asynchronously to prevent blocking the
1227         // main thread. As a result we won't have the information we need to open
1228         // a new tab in the case of notification fallback handling before
1229         // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
1230         // the browser from exiting in case early blank window is pref'd off.
1231         if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1232           Services.startup.enterLastWindowClosingSurvivalArea();
1233         }
1234         handleNotification()
1235           .catch(e => {
1236             console.error(
1237               `Error handling Windows notification with tag '${tag}':`,
1238               e
1239             );
1240           })
1241           .finally(() => {
1242             if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1243               Services.startup.exitLastWindowClosingSurvivalArea();
1244             }
1245           });
1247         return;
1248       }
1249     }
1251     if (
1252       cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1253       Services.startup.wasSilentlyStarted
1254     ) {
1255       // If we are starting up in silent mode, don't open a window. We also need
1256       // to make sure that the application doesn't immediately exit, so stay in
1257       // a LastWindowClosingSurvivalArea until a window opens.
1258       Services.startup.enterLastWindowClosingSurvivalArea();
1259       Services.obs.addObserver(function windowOpenObserver() {
1260         Services.startup.exitLastWindowClosingSurvivalArea();
1261         Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
1262       }, "domwindowopened");
1263       return;
1264     }
1266     if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
1267       // Handle the case where we don't have a profile selected yet (e.g. the
1268       // Profile Manager is displayed).
1269       // On Windows, we will crash if we open an url and then select a profile.
1270       // On macOS, if we open an url we don't experience a crash but a broken
1271       // window is opened.
1272       // To prevent this handle all url command line flags and set the
1273       // command line's preventDefault to true to prevent the display of the ui.
1274       // The initial command line will be retained when nsAppRunner calls
1275       // LaunchChild though urls launched after the initial launch will be lost.
1276       if (!this._haveProfile) {
1277         try {
1278           // This will throw when a profile has not been selected.
1279           Services.dirsvc.get("ProfD", Ci.nsIFile);
1280           this._haveProfile = true;
1281         } catch (e) {
1282           // eslint-disable-next-line no-empty
1283           while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1284           cmdLine.preventDefault = true;
1285         }
1286       }
1287     }
1289     // `-osint` and handling registered file types and protocols is Windows-only.
1290     let launchedWithArg_osint =
1291       AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
1292     if (launchedWithArg_osint) {
1293       cmdLine.handleFlag("osint", false);
1294     }
1296     try {
1297       var ar;
1298       while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1299         var uri = resolveURIInternal(cmdLine, ar);
1300         urilist.push(uri);
1302         if (launchedWithArg_osint) {
1303           launchedWithArg_osint = false;
1305           // We use the resolved URI here, even though it can produce
1306           // surprising results where-by `-osint -url test.pdf` resolves to
1307           // a query with search parameter "test.pdf".  But that shouldn't
1308           // happen when Firefox is launched by Windows itself: files should
1309           // exist and be resolved to file URLs.
1310           const isLaunch =
1311             cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1313           maybeRecordToHandleTelemetry(uri, isLaunch);
1314         }
1315       }
1316     } catch (e) {
1317       console.error(e);
1318     }
1320     if (
1321       AppConstants.platform == "win" &&
1322       cmdLine.handleFlag("to-handle-default-browser-agent", false)
1323     ) {
1324       // The Default Browser Agent launches Firefox in response to a Windows
1325       // native notification, but it does so in a non-standard manner.
1326       Services.telemetry.setEventRecordingEnabled(
1327         "browser.launched_to_handle",
1328         true
1329       );
1330       Glean.browserLaunchedToHandle.systemNotification.record({
1331         name: "default-browser-agent",
1332       });
1334       let thanksURI = Services.io.newURI(
1335         Services.urlFormatter.formatURLPref(
1336           "browser.shell.defaultBrowserAgent.thanksURL"
1337         )
1338       );
1339       urilist.push(thanksURI);
1340     }
1342     if (cmdLine.findFlag("screenshot", true) != -1) {
1343       lazy.HeadlessShell.handleCmdLineArgs(
1344         cmdLine,
1345         urilist.filter(shouldLoadURI).map(u => u.spec)
1346       );
1347       return;
1348     }
1350     for (let i = 0; i < cmdLine.length; ++i) {
1351       var curarg = cmdLine.getArgument(i);
1352       if (curarg.match(/^-/)) {
1353         console.error("Warning: unrecognized command line flag", curarg);
1354         // To emulate the pre-nsICommandLine behavior, we ignore
1355         // the argument after an unrecognized flag.
1356         ++i;
1357       } else {
1358         try {
1359           urilist.push(resolveURIInternal(cmdLine, curarg));
1360         } catch (e) {
1361           console.error(
1362             `Error opening URI ${curarg} from the command line:`,
1363             e
1364           );
1365         }
1366       }
1367     }
1369     if (urilist.length) {
1370       if (
1371         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1372         urilist.length == 1
1373       ) {
1374         // Try to find an existing window and load our URI into the
1375         // current tab, new tab, or new window as prefs determine.
1376         try {
1377           handURIToExistingBrowser(
1378             urilist[0],
1379             Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1380             cmdLine,
1381             false,
1382             lazy.gSystemPrincipal
1383           );
1384           return;
1385         } catch (e) {}
1386       }
1388       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
1389       if (URLlist.length) {
1390         openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
1391       }
1392     } else if (!cmdLine.preventDefault) {
1393       if (
1394         AppConstants.platform == "win" &&
1395         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1396         lazy.WindowsUIUtils.inTabletMode
1397       ) {
1398         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1399         let win = lazy.BrowserWindowTracker.getTopWindow();
1400         if (win) {
1401           win.focus();
1402           return;
1403         }
1404       }
1405       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1406     } else {
1407       // Need a better solution in the future to avoid opening the blank window
1408       // when command line parameters say we are not going to show a browser
1409       // window, but for now the blank window getting closed quickly (and
1410       // causing only a slight flicker) is better than leaving it open.
1411       let win = Services.wm.getMostRecentWindow("navigator:blank");
1412       if (win) {
1413         win.close();
1414       }
1415     }
1416   },
1418   helpInfo: "",