Backed out 3 changesets (bug 1883476, bug 1826375) for causing windows build bustages...
[gecko.git] / browser / components / BrowserContentHandler.sys.mjs
blobd450aa493ab8e27f2034a3e017bf838230f451e5
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 validateFirefoxProtocol(aCmdLine, launchedWithArg_osint) {
64   let paramCount = 0;
65   // Only accept one parameter when we're handling the protocol.
66   for (let i = 0; i < aCmdLine.length; i++) {
67     if (!aCmdLine.getArgument(i).startsWith("-")) {
68       paramCount++;
69     }
70     if (paramCount > 1) {
71       return false;
72     }
73   }
74   // `-osint` and handling registered file types and protocols is Windows-only.
75   return AppConstants.platform != "win" || launchedWithArg_osint;
78 function resolveURIInternal(
79   aCmdLine,
80   aArgument,
81   launchedWithArg_osint = false
82 ) {
83   let principal = lazy.gSystemPrincipal;
85   // If using Firefox protocol handler remove it from URI
86   // at this stage. This is before we would otherwise
87   // record telemetry so do that here.
88   let handleFirefoxProtocol = protocol => {
89     let protocolWithColon = protocol + ":";
90     if (aArgument.startsWith(protocolWithColon)) {
91       if (!validateFirefoxProtocol(aCmdLine, launchedWithArg_osint)) {
92         throw new Error(
93           "Invalid use of Firefox-bridge and Firefox-private-bridge protocols."
94         );
95       }
96       aArgument = aArgument.substring(protocolWithColon.length);
98       if (
99         !aArgument.startsWith("http://") &&
100         !aArgument.startsWith("https://")
101       ) {
102         throw new Error(
103           "Firefox-bridge and Firefox-private-bridge protocols can only be used in conjunction with http and https urls."
104         );
105       }
107       principal = Services.scriptSecurityManager.createNullPrincipal({});
108       Services.telemetry.keyedScalarAdd(
109         "os.environment.launched_to_handle",
110         protocol,
111         1
112       );
113     }
114   };
116   handleFirefoxProtocol("firefox-bridge");
117   handleFirefoxProtocol("firefox-private-bridge");
119   var uri = aCmdLine.resolveURI(aArgument);
120   var uriFixup = Services.uriFixup;
122   if (!(uri instanceof Ci.nsIFileURL)) {
123     let prefURI = Services.uriFixup.getFixupURIInfo(
124       aArgument,
125       uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
126     ).preferredURI;
127     return { uri: prefURI, principal };
128   }
130   try {
131     if (uri.file.exists()) {
132       return { uri, principal };
133     }
134   } catch (e) {
135     console.error(e);
136   }
138   // We have interpreted the argument as a relative file URI, but the file
139   // doesn't exist. Try URI fixup heuristics: see bug 290782.
141   try {
142     uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
143   } catch (e) {
144     console.error(e);
145   }
147   return { uri, principal };
150 let gKiosk = false;
151 let gMajorUpgrade = false;
152 let gFirstRunProfile = false;
153 var gFirstWindow = false;
155 const OVERRIDE_NONE = 0;
156 const OVERRIDE_NEW_PROFILE = 1;
157 const OVERRIDE_NEW_MSTONE = 2;
158 const OVERRIDE_NEW_BUILD_ID = 3;
160  * Determines whether a home page override is needed.
161  * @param {boolean} [updateMilestones=true]
162  *   True if we should update the milestone prefs after comparing those prefs
163  *   with the current platform version and build ID.
165  *   If updateMilestones is false, then this function has no side-effects.
167  * @returns {number}
168  *   One of the following constants:
169  *     OVERRIDE_NEW_PROFILE
170  *       if this is the first run with a new profile.
171  *     OVERRIDE_NEW_MSTONE
172  *       if this is the first run with a build with a different Gecko milestone
173  *       (i.e. right after an upgrade).
174  *     OVERRIDE_NEW_BUILD_ID
175  *       if this is the first run with a new build ID of the same Gecko
176  *       milestone (i.e. after a nightly upgrade).
177  *     OVERRIDE_NONE
178  *       otherwise.
179  */
180 function needHomepageOverride(updateMilestones = true) {
181   var savedmstone = Services.prefs.getCharPref(
182     "browser.startup.homepage_override.mstone",
183     ""
184   );
186   if (savedmstone == "ignore") {
187     return OVERRIDE_NONE;
188   }
190   var mstone = Services.appinfo.platformVersion;
192   var savedBuildID = Services.prefs.getCharPref(
193     "browser.startup.homepage_override.buildID",
194     ""
195   );
197   var buildID = Services.appinfo.platformBuildID;
199   if (mstone != savedmstone) {
200     // Bug 462254. Previous releases had a default pref to suppress the EULA
201     // agreement if the platform's installer had already shown one. Now with
202     // about:rights we've removed the EULA stuff and default pref, but we need
203     // a way to make existing profiles retain the default that we removed.
204     if (savedmstone) {
205       Services.prefs.setBoolPref("browser.rights.3.shown", true);
207       // Remember that we saw a major version change.
208       gMajorUpgrade = true;
209     }
211     if (updateMilestones) {
212       Services.prefs.setCharPref(
213         "browser.startup.homepage_override.mstone",
214         mstone
215       );
216       Services.prefs.setCharPref(
217         "browser.startup.homepage_override.buildID",
218         buildID
219       );
220     }
221     return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
222   }
224   if (buildID != savedBuildID) {
225     if (updateMilestones) {
226       Services.prefs.setCharPref(
227         "browser.startup.homepage_override.buildID",
228         buildID
229       );
230     }
231     return OVERRIDE_NEW_BUILD_ID;
232   }
234   return OVERRIDE_NONE;
238  * Gets the override page for the first run after the application has been
239  * updated.
240  * @param  update
241  *         The nsIUpdate for the update that has been applied.
242  * @param  defaultOverridePage
243  *         The default override page
244  * @param  nimbusOverridePage
245  *         Nimbus provided URL
246  * @return The override page.
247  */
248 function getPostUpdateOverridePage(
249   update,
250   defaultOverridePage,
251   nimbusOverridePage
252 ) {
253   update = update.QueryInterface(Ci.nsIWritablePropertyBag);
254   let actions = update.getProperty("actions");
255   // When the update doesn't specify actions fallback to the original behavior
256   // of displaying the default override page.
257   if (!actions) {
258     return defaultOverridePage;
259   }
261   // The existence of silent or the non-existence of showURL in the actions both
262   // mean that an override page should not be displayed.
263   if (actions.includes("silent") || !actions.includes("showURL")) {
264     return "";
265   }
267   // If a policy was set to not allow the update.xml-provided URL to be used,
268   // use the default fallback (which will also be provided by the policy).
269   if (!Services.policies.isAllowed("postUpdateCustomPage")) {
270     return defaultOverridePage;
271   }
273   if (nimbusOverridePage) {
274     return nimbusOverridePage;
275   }
277   return update.getProperty("openURL") || defaultOverridePage;
281  * Open a browser window. If this is the initial launch, this function will
282  * attempt to use the navigator:blank window opened by BrowserGlue.sys.mjs during
283  * early startup.
285  * @param cmdLine
286  *        The nsICommandLine object given to nsICommandLineHandler's handle
287  *        method.
288  *        Used to check if we are processing the command line for the initial launch.
289  * @param triggeringPrincipal
290  *        The nsIPrincipal to use as triggering principal for the page load(s).
291  * @param urlOrUrlList (optional)
292  *        When omitted, the browser window will be opened with the default
293  *        arguments, which will usually load the homepage.
294  *        This can be a JS array of urls provided as strings, each url will be
295  *        loaded in a tab. postData will be ignored in this case.
296  *        This can be a single url to load in the new window, provided as a string.
297  *        postData will be used in this case if provided.
298  * @param postData (optional)
299  *        An nsIInputStream object to use as POST data when loading the provided
300  *        url, or null.
301  * @param forcePrivate (optional)
302  *        Boolean. If set to true, the new window will be a private browsing one.
304  * @returns {ChromeWindow}
305  *          Returns the top level window opened.
306  */
307 function openBrowserWindow(
308   cmdLine,
309   triggeringPrincipal,
310   urlOrUrlList,
311   postData = null,
312   forcePrivate = false
313 ) {
314   const isStartup =
315     cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
317   let args;
318   if (!urlOrUrlList) {
319     // Just pass in the defaultArgs directly. We'll use system principal on the other end.
320     args = [gBrowserContentHandler.getArgs(isStartup)];
321   } else if (Array.isArray(urlOrUrlList)) {
322     // There isn't an explicit way to pass a principal here, so we load multiple URLs
323     // with system principal when we get to actually loading them.
324     if (
325       !triggeringPrincipal ||
326       !triggeringPrincipal.equals(lazy.gSystemPrincipal)
327     ) {
328       throw new Error(
329         "Can't open multiple URLs with something other than system principal."
330       );
331     }
332     // Passing an nsIArray for the url disables the "|"-splitting behavior.
333     let uriArray = Cc["@mozilla.org/array;1"].createInstance(
334       Ci.nsIMutableArray
335     );
336     urlOrUrlList.forEach(function (uri) {
337       var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
338         Ci.nsISupportsString
339       );
340       sstring.data = uri;
341       uriArray.appendElement(sstring);
342     });
343     args = [uriArray];
344   } else {
345     let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
346       Ci.nsIWritablePropertyBag2
347     );
348     extraOptions.setPropertyAsBool("fromExternal", true);
350     // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
351     // ie. avoid the loadOneOrMoreURIs function.
352     // Also, we need to pass the triggering principal.
353     args = [
354       urlOrUrlList,
355       extraOptions,
356       null, // refererInfo
357       postData,
358       undefined, // allowThirdPartyFixup; this would be `false` but that
359       // needs a conversion. Hopefully bug 1485961 will fix.
360       undefined, // user context id
361       null, // origin principal
362       null, // origin storage principal
363       triggeringPrincipal,
364     ];
365   }
367   if (isStartup) {
368     let win = Services.wm.getMostRecentWindow("navigator:blank");
369     if (win) {
370       // Remove the windowtype of our blank window so that we don't close it
371       // later on when seeing cmdLine.preventDefault is true.
372       win.document.documentElement.removeAttribute("windowtype");
374       if (forcePrivate) {
375         win.docShell.QueryInterface(
376           Ci.nsILoadContext
377         ).usePrivateBrowsing = true;
379         if (
380           AppConstants.platform == "win" &&
381           lazy.NimbusFeatures.majorRelease2022.getVariable(
382             "feltPrivacyWindowSeparation"
383           )
384         ) {
385           lazy.WinTaskbar.setGroupIdForWindow(
386             win,
387             lazy.WinTaskbar.defaultPrivateGroupId
388           );
389           lazy.WindowsUIUtils.setWindowIconFromExe(
390             win,
391             Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
392             // This corresponds to the definitions in
393             // nsNativeAppSupportWin.h
394             PRIVATE_BROWSING_ICON_INDEX
395           );
396         }
397       }
399       let openTime = win.openTime;
400       win.location = AppConstants.BROWSER_CHROME_URL;
401       win.arguments = args; // <-- needs to be a plain JS array here.
403       ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
404       lazy.BrowserWindowTracker.registerOpeningWindow(win, forcePrivate);
405       return win;
406     }
407   }
409   // We can't provide arguments to openWindow as a JS array.
410   if (!urlOrUrlList) {
411     // If we have a single string guaranteed to not contain '|' we can simply
412     // wrap it in an nsISupportsString object.
413     let [url] = args;
414     args = Cc["@mozilla.org/supports-string;1"].createInstance(
415       Ci.nsISupportsString
416     );
417     args.data = url;
418   } else {
419     // Otherwise, pass an nsIArray.
420     if (args.length > 1) {
421       let string = Cc["@mozilla.org/supports-string;1"].createInstance(
422         Ci.nsISupportsString
423       );
424       string.data = args[0];
425       args[0] = string;
426     }
427     let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
428     args.forEach(a => {
429       array.appendElement(a);
430     });
431     args = array;
432   }
434   return lazy.BrowserWindowTracker.openWindow({
435     args,
436     features: gBrowserContentHandler.getFeatures(cmdLine),
437     private: forcePrivate,
438   });
441 function openPreferences(cmdLine, extraArgs) {
442   openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
445 async function doSearch(searchTerm, cmdLine) {
446   // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
447   // preferences, but need nsIBrowserDOMWindow extensions
448   // Open the window immediately as BrowserContentHandler needs to
449   // be handled synchronously. Then load the search URI when the
450   // SearchService has loaded.
451   let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
452   await new Promise(resolve => {
453     Services.obs.addObserver(function observe(subject) {
454       if (subject == win) {
455         Services.obs.removeObserver(
456           observe,
457           "browser-delayed-startup-finished"
458         );
459         resolve();
460       }
461     }, "browser-delayed-startup-finished");
462   });
464   win.BrowserSearch.loadSearchFromCommandLine(
465     searchTerm,
466     lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
467       lazy.PrivateBrowsingUtils.isWindowPrivate(win),
468     lazy.gSystemPrincipal,
469     win.gBrowser.selectedBrowser.csp
470   ).catch(console.error);
473 export function nsBrowserContentHandler() {
474   if (!gBrowserContentHandler) {
475     gBrowserContentHandler = this;
476   }
477   return gBrowserContentHandler;
480 nsBrowserContentHandler.prototype = {
481   /* nsISupports */
482   QueryInterface: ChromeUtils.generateQI([
483     "nsICommandLineHandler",
484     "nsIBrowserHandler",
485     "nsIContentHandler",
486     "nsICommandLineValidator",
487   ]),
489   /* nsICommandLineHandler */
490   handle: function bch_handle(cmdLine) {
491     if (
492       cmdLine.handleFlag("kiosk", false) ||
493       cmdLine.handleFlagWithParam("kiosk-monitor", false)
494     ) {
495       gKiosk = true;
496     }
497     if (cmdLine.handleFlag("disable-pinch", false)) {
498       let defaults = Services.prefs.getDefaultBranch(null);
499       defaults.setBoolPref("apz.allow_zooming", false);
500       Services.prefs.lockPref("apz.allow_zooming");
501       defaults.setCharPref("browser.gesture.pinch.in", "");
502       Services.prefs.lockPref("browser.gesture.pinch.in");
503       defaults.setCharPref("browser.gesture.pinch.in.shift", "");
504       Services.prefs.lockPref("browser.gesture.pinch.in.shift");
505       defaults.setCharPref("browser.gesture.pinch.out", "");
506       Services.prefs.lockPref("browser.gesture.pinch.out");
507       defaults.setCharPref("browser.gesture.pinch.out.shift", "");
508       Services.prefs.lockPref("browser.gesture.pinch.out.shift");
509     }
510     if (cmdLine.handleFlag("browser", false)) {
511       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
512       cmdLine.preventDefault = true;
513     }
515     var uriparam;
516     try {
517       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
518         let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
519         if (!shouldLoadURI(uri)) {
520           continue;
521         }
522         openBrowserWindow(cmdLine, principal, uri.spec);
523         cmdLine.preventDefault = true;
524       }
525     } catch (e) {
526       console.error(e);
527     }
529     try {
530       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
531         let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
532         handURIToExistingBrowser(
533           uri,
534           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
535           cmdLine,
536           false,
537           principal
538         );
539         cmdLine.preventDefault = true;
540       }
541     } catch (e) {
542       console.error(e);
543     }
545     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
546     if (chromeParam) {
547       // Handle old preference dialog URLs.
548       if (
549         chromeParam == "chrome://browser/content/pref/pref.xul" ||
550         chromeParam == "chrome://browser/content/preferences/preferences.xul"
551       ) {
552         openPreferences(cmdLine);
553         cmdLine.preventDefault = true;
554       } else {
555         try {
556           let { uri: resolvedURI } = resolveURIInternal(cmdLine, chromeParam);
557           let isLocal = uri => {
558             let localSchemes = new Set(["chrome", "file", "resource"]);
559             if (uri instanceof Ci.nsINestedURI) {
560               uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
561             }
562             return localSchemes.has(uri.scheme);
563           };
564           if (isLocal(resolvedURI)) {
565             // If the URI is local, we are sure it won't wrongly inherit chrome privs
566             let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
567             // Provide 1 null argument, as openWindow has a different behavior
568             // when the arg count is 0.
569             let argArray = Cc["@mozilla.org/array;1"].createInstance(
570               Ci.nsIMutableArray
571             );
572             argArray.appendElement(null);
573             Services.ww.openWindow(
574               null,
575               resolvedURI.spec,
576               "_blank",
577               features,
578               argArray
579             );
580             cmdLine.preventDefault = true;
581           } else {
582             dump("*** Preventing load of web URI as chrome\n");
583             dump(
584               "    If you're trying to load a webpage, do not pass --chrome.\n"
585             );
586           }
587         } catch (e) {
588           console.error(e);
589         }
590       }
591     }
592     if (cmdLine.handleFlag("preferences", false)) {
593       openPreferences(cmdLine);
594       cmdLine.preventDefault = true;
595     }
596     if (cmdLine.handleFlag("silent", false)) {
597       cmdLine.preventDefault = true;
598     }
600     try {
601       var privateWindowParam = cmdLine.handleFlagWithParam(
602         "private-window",
603         false
604       );
605       // Check for Firefox private browsing protocol handler here.
606       let url = null;
607       let urlFlagIdx = cmdLine.findFlag("url", false);
608       if (urlFlagIdx > -1 && cmdLine.length > 1) {
609         url = cmdLine.getArgument(urlFlagIdx + 1);
610       }
611       if (privateWindowParam || url?.startsWith("firefox-private-bridge:")) {
612         // Check if the osint flag is present on Windows
613         let launchedWithArg_osint =
614           AppConstants.platform == "win" &&
615           cmdLine.findFlag("osint", false) == 0;
616         let forcePrivate = true;
617         let resolvedInfo;
618         if (!lazy.PrivateBrowsingUtils.enabled) {
619           // Load about:privatebrowsing in a normal tab, which will display an error indicating
620           // access to private browsing has been disabled.
621           forcePrivate = false;
622           resolvedInfo = {
623             uri: Services.io.newURI("about:privatebrowsing"),
624             principal: lazy.gSystemPrincipal,
625           };
626         } else if (url?.startsWith("firefox-private-bridge:")) {
627           cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
628           resolvedInfo = resolveURIInternal(
629             cmdLine,
630             url,
631             launchedWithArg_osint
632           );
633         } else {
634           resolvedInfo = resolveURIInternal(
635             cmdLine,
636             privateWindowParam,
637             launchedWithArg_osint
638           );
639         }
640         handURIToExistingBrowser(
641           resolvedInfo.uri,
642           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
643           cmdLine,
644           forcePrivate,
645           resolvedInfo.principal
646         );
647         cmdLine.preventDefault = true;
648       }
649     } catch (e) {
650       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
651         throw e;
652       }
653       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
654       if (cmdLine.handleFlag("private-window", false)) {
655         openBrowserWindow(
656           cmdLine,
657           lazy.gSystemPrincipal,
658           "about:privatebrowsing",
659           null,
660           lazy.PrivateBrowsingUtils.enabled
661         );
662         cmdLine.preventDefault = true;
663       }
664     }
666     var searchParam = cmdLine.handleFlagWithParam("search", false);
667     if (searchParam) {
668       doSearch(searchParam, cmdLine);
669       cmdLine.preventDefault = true;
670     }
672     // The global PB Service consumes this flag, so only eat it in per-window
673     // PB builds.
674     if (
675       cmdLine.handleFlag("private", false) &&
676       lazy.PrivateBrowsingUtils.enabled
677     ) {
678       lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
679       if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
680         let win = Services.wm.getMostRecentWindow("navigator:blank");
681         if (win) {
682           win.docShell.QueryInterface(
683             Ci.nsILoadContext
684           ).usePrivateBrowsing = true;
685         }
686       }
687     }
688     if (cmdLine.handleFlag("setDefaultBrowser", false)) {
689       // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
690       // is an implementation of an interface method and changing it to be async would be complicated
691       // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
692       // an await.
693       lazy.ShellService.setDefaultBrowser(true).catch(e => {
694         console.error("setDefaultBrowser failed:", e);
695       });
696     }
698     if (cmdLine.handleFlag("first-startup", false)) {
699       // We don't want subsequent calls to needHompageOverride to have different
700       // values because the milestones in prefs got updated, so we intentionally
701       // tell needHomepageOverride to leave the milestone prefs alone when doing
702       // this check.
703       let override = needHomepageOverride(false /* updateMilestones */);
704       lazy.FirstStartup.init(override == OVERRIDE_NEW_PROFILE /* newProfile */);
705     }
707     var fileParam = cmdLine.handleFlagWithParam("file", false);
708     if (fileParam) {
709       var file = cmdLine.resolveFile(fileParam);
710       var fileURI = Services.io.newFileURI(file);
711       openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
712       cmdLine.preventDefault = true;
713     }
715     if (AppConstants.platform == "win") {
716       // Handle "? searchterm" for Windows Vista start menu integration
717       for (var i = cmdLine.length - 1; i >= 0; --i) {
718         var param = cmdLine.getArgument(i);
719         if (param.match(/^\? /)) {
720           cmdLine.removeArguments(i, i);
721           cmdLine.preventDefault = true;
723           searchParam = param.substr(2);
724           doSearch(searchParam, cmdLine);
725         }
726       }
727     }
728   },
730   get helpInfo() {
731     let info =
732       "  --browser          Open a browser window.\n" +
733       "  --new-window <url> Open <url> in a new window.\n" +
734       "  --new-tab <url>    Open <url> in a new tab.\n" +
735       "  --private-window <url> Open <url> in a new private window.\n";
736     if (AppConstants.platform == "win") {
737       info += "  --preferences      Open Options dialog.\n";
738     } else {
739       info += "  --preferences      Open Preferences dialog.\n";
740     }
741     info +=
742       "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
743     info +=
744       "  --window-size width[,height] Width and optionally height of screenshot.\n";
745     info +=
746       "  --search <term>    Search <term> with your default search engine.\n";
747     info += "  --setDefaultBrowser Set this app as the default browser.\n";
748     info +=
749       "  --first-startup    Run post-install actions before opening a new window.\n";
750     info += "  --kiosk            Start the browser in kiosk mode.\n";
751     info +=
752       "  --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
753     info +=
754       "  --disable-pinch    Disable touch-screen and touch-pad pinch gestures.\n";
755     return info;
756   },
758   /* nsIBrowserHandler */
760   get defaultArgs() {
761     return this.getArgs();
762   },
764   getArgs(isStartup = false) {
765     var prefb = Services.prefs;
767     if (!gFirstWindow) {
768       gFirstWindow = true;
769       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
770         return "about:privatebrowsing";
771       }
772     }
774     var override;
775     var overridePage = "";
776     var additionalPage = "";
777     var willRestoreSession = false;
778     try {
779       // Read the old value of homepage_override.mstone before
780       // needHomepageOverride updates it, so that we can later add it to the
781       // URL if we do end up showing an overridePage. This makes it possible
782       // to have the overridePage's content vary depending on the version we're
783       // upgrading from.
784       let old_mstone = Services.prefs.getCharPref(
785         "browser.startup.homepage_override.mstone",
786         "unknown"
787       );
788       let old_buildId = Services.prefs.getCharPref(
789         "browser.startup.homepage_override.buildID",
790         "unknown"
791       );
792       override = needHomepageOverride();
793       if (override != OVERRIDE_NONE) {
794         switch (override) {
795           case OVERRIDE_NEW_PROFILE:
796             // New profile.
797             gFirstRunProfile = true;
798             if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
799               break;
800             }
801             overridePage = Services.urlFormatter.formatURLPref(
802               "startup.homepage_welcome_url"
803             );
804             additionalPage = Services.urlFormatter.formatURLPref(
805               "startup.homepage_welcome_url.additional"
806             );
807             // Turn on 'later run' pages for new profiles.
808             lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
809             break;
810           case OVERRIDE_NEW_MSTONE: {
811             // Check whether we will restore a session. If we will, we assume
812             // that this is an "update" session. This does not take crashes
813             // into account because that requires waiting for the session file
814             // to be read. If a crash occurs after updating, before restarting,
815             // we may open the startPage in addition to restoring the session.
816             willRestoreSession =
817               lazy.SessionStartup.isAutomaticRestoreEnabled();
819             overridePage = Services.urlFormatter.formatURLPref(
820               "startup.homepage_override_url"
821             );
822             let update = lazy.UpdateManager.readyUpdate;
824             /** If the override URL is provided by an experiment, is a valid
825              * Firefox What's New Page URL, and the update version is less than
826              * or equal to the maxVersion set by the experiment, we'll try to use
827              * the experiment override URL instead of the default or the
828              * update-provided URL. Additional policy checks are done in
829              * @see getPostUpdateOverridePage */
830             const nimbusOverrideUrl = Services.urlFormatter.formatURLPref(
831               "startup.homepage_override_url_nimbus"
832             );
833             const maxVersion = Services.prefs.getCharPref(
834               "startup.homepage_override_nimbus_maxVersion",
835               ""
836             );
837             let nimbusWNP;
839             // Update version should be less than or equal to maxVersion set by
840             // the experiment
841             if (
842               nimbusOverrideUrl &&
843               Services.vc.compare(update.appVersion, maxVersion) <= 0
844             ) {
845               try {
846                 let uri = Services.io.newURI(nimbusOverrideUrl);
847                 // Only allow https://www.mozilla.org and https://www.mozilla.com
848                 if (
849                   uri.scheme === "https" &&
850                   ["www.mozilla.org", "www.mozilla.com"].includes(uri.host)
851                 ) {
852                   nimbusWNP = uri.spec;
853                 } else {
854                   throw new Error("Bad URL");
855                 }
856               } catch {
857                 console.error("Invalid WNP URL: ", nimbusOverrideUrl);
858               }
859             }
861             if (
862               update &&
863               Services.vc.compare(update.appVersion, old_mstone) > 0
864             ) {
865               overridePage = getPostUpdateOverridePage(
866                 update,
867                 overridePage,
868                 nimbusWNP
869               );
870               // Record a Nimbus exposure event for the whatsNewPage feature.
871               // The override page could be set in 3 ways: 1. set by Nimbus 2.
872               // set by the update file(openURL) 3. The default evergreen page(Set by the
873               // startup.homepage_override_url pref, could be different
874               // depending on the Fx channel). This is done to record that the
875               // control cohort could have seen the experimental What's New Page
876               // (and will instead see the default What's New Page).
877               // recordExposureEvent only records an event if the user is
878               // enrolled in an experiment or rollout on the whatsNewPage
879               // feature, so it's safe to call it unconditionally.
880               if (overridePage) {
881                 let nimbusWNPFeature = lazy.NimbusFeatures.whatsNewPage;
882                 nimbusWNPFeature
883                   .ready()
884                   .then(() => nimbusWNPFeature.recordExposureEvent());
885               }
887               // Send the update ping to signal that the update was successful.
888               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
889               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
890             }
892             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
893             break;
894           }
895           case OVERRIDE_NEW_BUILD_ID:
896             if (lazy.UpdateManager.readyUpdate) {
897               // Send the update ping to signal that the update was successful.
898               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
899               lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
900             }
901             break;
902         }
903       }
904     } catch (ex) {}
906     // formatURLPref might return "about:blank" if getting the pref fails
907     if (overridePage == "about:blank") {
908       overridePage = "";
909     }
911     // Allow showing a one-time startup override if we're not showing one
912     if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
913       try {
914         // Show if we haven't passed the expiration or there's no expiration
915         const { expire, url } = JSON.parse(
916           Services.urlFormatter.formatURLPref(ONCE_PREF)
917         );
918         if (!(Date.now() > expire)) {
919           // Only set allowed urls as override pages
920           overridePage = url
921             .split("|")
922             .map(val => {
923               try {
924                 return new URL(val);
925               } catch (ex) {
926                 // Invalid URL, so filter out below
927                 console.error("Invalid once url:", ex);
928                 return null;
929               }
930             })
931             .filter(
932               parsed =>
933                 parsed &&
934                 parsed.protocol == "https:" &&
935                 // Only accept exact hostname or subdomain; without port
936                 ONCE_DOMAINS.includes(
937                   Services.eTLD.getBaseDomainFromHost(parsed.host)
938                 )
939             )
940             .join("|");
942           // Be noisy as properly configured urls should be unchanged
943           if (overridePage != url) {
944             console.error(`Mismatched once urls: ${url}`);
945           }
946         }
947       } catch (ex) {
948         // Invalid json pref, so ignore (and clear below)
949         console.error("Invalid once pref:", ex);
950       } finally {
951         prefb.clearUserPref(ONCE_PREF);
952       }
953     }
955     if (!additionalPage) {
956       additionalPage = lazy.LaterRun.getURL() || "";
957     }
959     if (additionalPage && additionalPage != "about:blank") {
960       if (overridePage) {
961         overridePage += "|" + additionalPage;
962       } else {
963         overridePage = additionalPage;
964       }
965     }
967     var startPage = "";
968     try {
969       var choice = prefb.getIntPref("browser.startup.page");
970       if (choice == 1 || choice == 3) {
971         startPage = lazy.HomePage.get();
972       }
973     } catch (e) {
974       console.error(e);
975     }
977     if (startPage == "about:blank") {
978       startPage = "";
979     }
981     let skipStartPage =
982       override == OVERRIDE_NEW_PROFILE &&
983       prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
984     // Only show the startPage if we're not restoring an update session and are
985     // not set to skip the start page on this profile
986     if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
987       return overridePage + "|" + startPage;
988     }
990     return overridePage || startPage || "about:blank";
991   },
993   mFeatures: null,
995   getFeatures: function bch_features(cmdLine) {
996     if (this.mFeatures === null) {
997       this.mFeatures = "";
999       if (cmdLine) {
1000         try {
1001           var width = cmdLine.handleFlagWithParam("width", false);
1002           var height = cmdLine.handleFlagWithParam("height", false);
1003           var left = cmdLine.handleFlagWithParam("left", false);
1004           var top = cmdLine.handleFlagWithParam("top", false);
1006           if (width) {
1007             this.mFeatures += ",width=" + width;
1008           }
1009           if (height) {
1010             this.mFeatures += ",height=" + height;
1011           }
1012           if (left) {
1013             this.mFeatures += ",left=" + left;
1014           }
1015           if (top) {
1016             this.mFeatures += ",top=" + top;
1017           }
1018         } catch (e) {}
1019       }
1021       // The global PB Service consumes this flag, so only eat it in per-window
1022       // PB builds.
1023       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
1024         this.mFeatures += ",private";
1025       }
1027       if (
1028         Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
1029         !Services.wm.getMostRecentWindow("navigator:browser")
1030       ) {
1031         this.mFeatures += ",suppressanimation";
1032       }
1033     }
1035     return this.mFeatures;
1036   },
1038   get kiosk() {
1039     return gKiosk;
1040   },
1042   get majorUpgrade() {
1043     return gMajorUpgrade;
1044   },
1046   set majorUpgrade(val) {
1047     gMajorUpgrade = val;
1048   },
1050   get firstRunProfile() {
1051     return gFirstRunProfile;
1052   },
1054   set firstRunProfile(val) {
1055     gFirstRunProfile = val;
1056   },
1058   /* nsIContentHandler */
1060   handleContent: function bch_handleContent(contentType, context, request) {
1061     const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
1063     try {
1064       var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
1065         Ci.nsIWebNavigationInfo
1066       );
1067       if (!webNavInfo.isTypeSupported(contentType)) {
1068         throw NS_ERROR_WONT_HANDLE_CONTENT;
1069       }
1070     } catch (e) {
1071       throw NS_ERROR_WONT_HANDLE_CONTENT;
1072     }
1074     request.QueryInterface(Ci.nsIChannel);
1075     handURIToExistingBrowser(
1076       request.URI,
1077       Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1078       null,
1079       false,
1080       request.loadInfo.triggeringPrincipal
1081     );
1082     request.cancel(Cr.NS_BINDING_ABORTED);
1083   },
1085   /* nsICommandLineValidator */
1086   validate: function bch_validate(cmdLine) {
1087     var urlFlagIdx = cmdLine.findFlag("url", false);
1088     if (
1089       urlFlagIdx > -1 &&
1090       cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
1091     ) {
1092       var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
1093       if (
1094         cmdLine.length != urlFlagIdx + 2 ||
1095         /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
1096       ) {
1097         throw Components.Exception("", Cr.NS_ERROR_ABORT);
1098       }
1099     }
1100   },
1102 var gBrowserContentHandler = new nsBrowserContentHandler();
1104 function handURIToExistingBrowser(
1105   uri,
1106   location,
1107   cmdLine,
1108   forcePrivate,
1109   triggeringPrincipal
1110 ) {
1111   if (!shouldLoadURI(uri)) {
1112     return;
1113   }
1115   let openInWindow = ({ browserDOMWindow }) => {
1116     browserDOMWindow.openURI(
1117       uri,
1118       null,
1119       location,
1120       Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
1121       triggeringPrincipal
1122     );
1123   };
1125   // Unless using a private window is forced, open external links in private
1126   // windows only if we're in perma-private mode.
1127   let allowPrivate =
1128     forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1129   let navWin = lazy.BrowserWindowTracker.getTopWindow({
1130     private: allowPrivate,
1131   });
1133   if (navWin) {
1134     openInWindow(navWin);
1135     return;
1136   }
1138   let pending = lazy.BrowserWindowTracker.getPendingWindow({
1139     private: allowPrivate,
1140   });
1141   if (pending) {
1142     // Note that we cannot make this function async as some callers rely on
1143     // catching exceptions it can throw in some cases and some of those callers
1144     // cannot be made async.
1145     pending.then(openInWindow);
1146     return;
1147   }
1149   // if we couldn't load it in an existing window, open a new one
1150   openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
1154  * If given URI is a file type or a protocol, record telemetry that
1155  * Firefox was invoked or launched (if `isLaunch` is truth-y).  If the
1156  * file type or protocol is not registered by default, record it as
1157  * ".<other extension>" or "<other protocol>".
1159  * @param uri
1160  *        The URI Firefox was asked to handle.
1161  * @param isLaunch
1162  *        truth-y if Firefox was launched/started rather than running and invoked.
1163  */
1164 function maybeRecordToHandleTelemetry(uri, isLaunch) {
1165   let scalar = isLaunch
1166     ? "os.environment.launched_to_handle"
1167     : "os.environment.invoked_to_handle";
1169   if (uri instanceof Ci.nsIFileURL) {
1170     let extension = "." + uri.fileExtension.toLowerCase();
1171     // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
1172     // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
1173     let registeredExtensions = new Set([
1174       ".avif",
1175       ".htm",
1176       ".html",
1177       ".pdf",
1178       ".shtml",
1179       ".xht",
1180       ".xhtml",
1181       ".svg",
1182       ".webp",
1183     ]);
1184     if (registeredExtensions.has(extension)) {
1185       Services.telemetry.keyedScalarAdd(scalar, extension, 1);
1186     } else {
1187       Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
1188     }
1189   } else if (uri) {
1190     let scheme = uri.scheme.toLowerCase();
1191     let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
1192     if (registeredSchemes.has(scheme)) {
1193       Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
1194     } else {
1195       Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1196     }
1197   }
1200 export function nsDefaultCommandLineHandler() {}
1202 nsDefaultCommandLineHandler.prototype = {
1203   /* nsISupports */
1204   QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1206   _haveProfile: false,
1208   /* nsICommandLineHandler */
1209   handle: function dch_handle(cmdLine) {
1210     var urilist = [];
1211     var principalList = [];
1213     if (AppConstants.platform == "win") {
1214       // Windows itself does disk I/O when the notification service is
1215       // initialized, so make sure that is lazy.
1216       while (true) {
1217         let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
1218         if (!tag) {
1219           break;
1220         }
1222         // All notifications will invoke Firefox with an action.  Prior to Bug 1805514,
1223         // this data was extracted from the Windows toast object directly (keyed by the
1224         // notification ID) and not passed over the command line.  This is acceptable
1225         // because the data passed is chrome-controlled, but if we implement the `actions`
1226         // part of the DOM Web Notifications API, this will no longer be true:
1227         // content-controlled data might transit over the command line.  This could lead
1228         // to escaping bugs and overflows.  In the future, we intend to avoid any such
1229         // issue by once again extracting all such data from the Windows toast object.
1230         let notificationData = cmdLine.handleFlagWithParam(
1231           "notification-windowsAction",
1232           false
1233         );
1234         if (!notificationData) {
1235           break;
1236         }
1238         let alertService = lazy.gWindowsAlertsService;
1239         if (!alertService) {
1240           console.error("Windows alert service not available.");
1241           break;
1242         }
1244         async function handleNotification() {
1245           let { tagWasHandled } = await alertService.handleWindowsTag(tag);
1247           // If the tag was not handled via callback, then the notification was
1248           // from a prior instance of the application and we need to handle
1249           // fallback behavior.
1250           if (!tagWasHandled) {
1251             console.info(
1252               `Completing Windows notification (tag=${JSON.stringify(
1253                 tag
1254               )}, notificationData=${notificationData})`
1255             );
1256             try {
1257               notificationData = JSON.parse(notificationData);
1258             } catch (e) {
1259               console.error(
1260                 `Completing Windows notification (tag=${JSON.stringify(
1261                   tag
1262                 )}, failed to parse (notificationData=${notificationData})`
1263               );
1264             }
1265           }
1267           // This is awkward: the relaunch data set by the caller is _wrapped_
1268           // into a compound object that includes additional notification data,
1269           // and everything is exchanged as strings.  Unwrap and parse here.
1270           let opaqueRelaunchData = null;
1271           if (notificationData?.opaqueRelaunchData) {
1272             try {
1273               opaqueRelaunchData = JSON.parse(
1274                 notificationData.opaqueRelaunchData
1275               );
1276             } catch (e) {
1277               console.error(
1278                 `Completing Windows notification (tag=${JSON.stringify(
1279                   tag
1280                 )}, failed to parse (opaqueRelaunchData=${
1281                   notificationData.opaqueRelaunchData
1282                 })`
1283               );
1284             }
1285           }
1287           if (notificationData?.privilegedName) {
1288             Services.telemetry.setEventRecordingEnabled(
1289               "browser.launched_to_handle",
1290               true
1291             );
1292             Glean.browserLaunchedToHandle.systemNotification.record({
1293               name: notificationData.privilegedName,
1294             });
1295           }
1297           // If we have an action in the notification data, this will be the
1298           // window to perform the action in.
1299           let winForAction;
1301           if (notificationData?.launchUrl && !opaqueRelaunchData) {
1302             // Unprivileged Web Notifications contain a launch URL and are handled
1303             // slightly differently than privileged notifications with actions.
1304             let { uri, principal } = resolveURIInternal(
1305               cmdLine,
1306               notificationData.launchUrl
1307             );
1308             if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1309               // Try to find an existing window and load our URI into the current
1310               // tab, new tab, or new window as prefs determine.
1311               try {
1312                 handURIToExistingBrowser(
1313                   uri,
1314                   Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1315                   cmdLine,
1316                   false,
1317                   principal
1318                 );
1319                 return;
1320               } catch (e) {}
1321             }
1323             if (shouldLoadURI(uri)) {
1324               openBrowserWindow(cmdLine, principal, [uri.spec]);
1325             }
1326           } else if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1327             // No URL provided, but notification was interacted with while the
1328             // application was closed. Fall back to opening the browser without url.
1329             winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1330             await new Promise(resolve => {
1331               Services.obs.addObserver(function observe(subject) {
1332                 if (subject == winForAction) {
1333                   Services.obs.removeObserver(
1334                     observe,
1335                     "browser-delayed-startup-finished"
1336                   );
1337                   resolve();
1338                 }
1339               }, "browser-delayed-startup-finished");
1340             });
1341           } else {
1342             // Relaunch in private windows only if we're in perma-private mode.
1343             let allowPrivate =
1344               lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1345             winForAction = lazy.BrowserWindowTracker.getTopWindow({
1346               private: allowPrivate,
1347             });
1348           }
1350           if (opaqueRelaunchData && winForAction) {
1351             // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
1352             Services.tm.dispatchToMainThread(() => {
1353               lazy.SpecialMessageActions.handleAction(
1354                 opaqueRelaunchData,
1355                 winForAction.gBrowser
1356               );
1357             });
1358           }
1359         }
1361         // Notification handling occurs asynchronously to prevent blocking the
1362         // main thread. As a result we won't have the information we need to open
1363         // a new tab in the case of notification fallback handling before
1364         // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
1365         // the browser from exiting in case early blank window is pref'd off.
1366         if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1367           Services.startup.enterLastWindowClosingSurvivalArea();
1368         }
1369         handleNotification()
1370           .catch(e => {
1371             console.error(
1372               `Error handling Windows notification with tag '${tag}':`,
1373               e
1374             );
1375           })
1376           .finally(() => {
1377             if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1378               Services.startup.exitLastWindowClosingSurvivalArea();
1379             }
1380           });
1382         return;
1383       }
1384     }
1386     if (
1387       cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1388       Services.startup.wasSilentlyStarted
1389     ) {
1390       // If we are starting up in silent mode, don't open a window. We also need
1391       // to make sure that the application doesn't immediately exit, so stay in
1392       // a LastWindowClosingSurvivalArea until a window opens.
1393       Services.startup.enterLastWindowClosingSurvivalArea();
1394       Services.obs.addObserver(function windowOpenObserver() {
1395         Services.startup.exitLastWindowClosingSurvivalArea();
1396         Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
1397       }, "domwindowopened");
1398       return;
1399     }
1401     if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
1402       // Handle the case where we don't have a profile selected yet (e.g. the
1403       // Profile Manager is displayed).
1404       // On Windows, we will crash if we open an url and then select a profile.
1405       // On macOS, if we open an url we don't experience a crash but a broken
1406       // window is opened.
1407       // To prevent this handle all url command line flags and set the
1408       // command line's preventDefault to true to prevent the display of the ui.
1409       // The initial command line will be retained when nsAppRunner calls
1410       // LaunchChild though urls launched after the initial launch will be lost.
1411       if (!this._haveProfile) {
1412         try {
1413           // This will throw when a profile has not been selected.
1414           Services.dirsvc.get("ProfD", Ci.nsIFile);
1415           this._haveProfile = true;
1416         } catch (e) {
1417           // eslint-disable-next-line no-empty
1418           while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1419           cmdLine.preventDefault = true;
1420         }
1421       }
1422     }
1424     // `-osint` and handling registered file types and protocols is Windows-only.
1425     let launchedWithArg_osint =
1426       AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
1427     if (launchedWithArg_osint) {
1428       cmdLine.handleFlag("osint", false);
1429     }
1431     try {
1432       var ar;
1433       while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1434         let { uri, principal } = resolveURIInternal(
1435           cmdLine,
1436           ar,
1437           launchedWithArg_osint
1438         );
1439         urilist.push(uri);
1440         principalList.push(principal);
1442         if (launchedWithArg_osint) {
1443           launchedWithArg_osint = false;
1445           // We use the resolved URI here, even though it can produce
1446           // surprising results where-by `-osint -url test.pdf` resolves to
1447           // a query with search parameter "test.pdf".  But that shouldn't
1448           // happen when Firefox is launched by Windows itself: files should
1449           // exist and be resolved to file URLs.
1450           const isLaunch =
1451             cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1453           maybeRecordToHandleTelemetry(uri, isLaunch);
1454         }
1455       }
1456     } catch (e) {
1457       console.error(e);
1458     }
1460     if (
1461       AppConstants.platform == "win" &&
1462       cmdLine.handleFlag("to-handle-default-browser-agent", false)
1463     ) {
1464       // The Default Browser Agent launches Firefox in response to a Windows
1465       // native notification, but it does so in a non-standard manner.
1466       Services.telemetry.setEventRecordingEnabled(
1467         "browser.launched_to_handle",
1468         true
1469       );
1470       Glean.browserLaunchedToHandle.systemNotification.record({
1471         name: "default-browser-agent",
1472       });
1474       let thanksURI = Services.io.newURI(
1475         Services.urlFormatter.formatURLPref(
1476           "browser.shell.defaultBrowserAgent.thanksURL"
1477         )
1478       );
1479       urilist.push(thanksURI);
1480       principalList.push(lazy.gSystemPrincipal);
1481     }
1483     if (cmdLine.findFlag("screenshot", true) != -1) {
1484       // Shouldn't have to push principal here with the screenshot flag
1485       lazy.HeadlessShell.handleCmdLineArgs(
1486         cmdLine,
1487         urilist.filter(shouldLoadURI).map(u => u.spec)
1488       );
1489       return;
1490     }
1492     for (let i = 0; i < cmdLine.length; ++i) {
1493       var curarg = cmdLine.getArgument(i);
1494       if (curarg.match(/^-/)) {
1495         console.error("Warning: unrecognized command line flag", curarg);
1496         // To emulate the pre-nsICommandLine behavior, we ignore
1497         // the argument after an unrecognized flag.
1498         ++i;
1499       } else {
1500         try {
1501           let { uri, principal } = resolveURIInternal(cmdLine, curarg);
1502           urilist.push(uri);
1503           principalList.push(principal);
1504         } catch (e) {
1505           console.error(
1506             `Error opening URI ${curarg} from the command line:`,
1507             e
1508           );
1509         }
1510       }
1511     }
1513     if (urilist.length) {
1514       if (
1515         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1516         urilist.length == 1
1517       ) {
1518         // Try to find an existing window and load our URI into the
1519         // current tab, new tab, or new window as prefs determine.
1520         try {
1521           handURIToExistingBrowser(
1522             urilist[0],
1523             Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1524             cmdLine,
1525             false,
1526             principalList[0] ?? lazy.gSystemPrincipal
1527           );
1528           return;
1529         } catch (e) {}
1530       }
1532       // Can't open multiple URLs without using system principal.
1533       // The firefox-bridge and firefox-private-bridge protocols should only
1534       // accept a single URL due to using the -osint option
1535       // so this isn't very relevant.
1536       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
1537       if (URLlist.length) {
1538         openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
1539       }
1540     } else if (!cmdLine.preventDefault) {
1541       if (
1542         AppConstants.platform == "win" &&
1543         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1544         lazy.WindowsUIUtils.inTabletMode
1545       ) {
1546         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1547         let win = lazy.BrowserWindowTracker.getTopWindow();
1548         if (win) {
1549           win.focus();
1550           return;
1551         }
1552       }
1553       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1554     } else {
1555       // Need a better solution in the future to avoid opening the blank window
1556       // when command line parameters say we are not going to show a browser
1557       // window, but for now the blank window getting closed quickly (and
1558       // causing only a slight flicker) is better than leaving it open.
1559       let win = Services.wm.getMostRecentWindow("navigator:blank");
1560       if (win) {
1561         win.close();
1562       }
1563     }
1564   },
1566   helpInfo: "",