Bug 1775107 [wpt PR 34499] - Add requestPermission() to DeviceOrientationEvent and...
[gecko.git] / browser / components / BrowserContentHandler.jsm
blob47647e8e9126af0ceb4ce133db8ee86b4b7cde09
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 var EXPORTED_SYMBOLS = [
6   "nsBrowserContentHandler",
7   "nsDefaultCommandLineHandler",
8 ];
10 const { XPCOMUtils } = ChromeUtils.import(
11   "resource://gre/modules/XPCOMUtils.jsm"
13 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
14 const { AppConstants } = ChromeUtils.import(
15   "resource://gre/modules/AppConstants.jsm"
18 const lazy = {};
20 XPCOMUtils.defineLazyModuleGetters(lazy, {
21   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
22   HeadlessShell: "resource:///modules/HeadlessShell.jsm",
23   HomePage: "resource:///modules/HomePage.jsm",
24   FirstStartup: "resource://gre/modules/FirstStartup.jsm",
25   LaterRun: "resource:///modules/LaterRun.jsm",
26   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
27   SessionStartup: "resource:///modules/sessionstore/SessionStartup.jsm",
28   ShellService: "resource:///modules/ShellService.jsm",
29   UpdatePing: "resource://gre/modules/UpdatePing.jsm",
30 });
31 XPCOMUtils.defineLazyServiceGetters(lazy, {
32   UpdateManager: ["@mozilla.org/updates/update-manager;1", "nsIUpdateManager"],
33   WinTaskbar: ["@mozilla.org/windows-taskbar;1", "nsIWinTaskbar"],
34   WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
35 });
37 XPCOMUtils.defineLazyGetter(lazy, "gSystemPrincipal", () =>
38   Services.scriptSecurityManager.getSystemPrincipal()
41 // One-time startup homepage override configurations
42 const ONCE_DOMAINS = ["mozilla.org", "firefox.com"];
43 const ONCE_PREF = "browser.startup.homepage_override.once";
45 // Index of Private Browsing icon in firefox.exe
46 // Must line up with the one in nsNativeAppSupportWin.h.
47 const PRIVATE_BROWSING_ICON_INDEX = 5;
48 const PRIVACY_SEGMENTATION_PREF = "browser.privacySegmentation.enabled";
50 function shouldLoadURI(aURI) {
51   if (aURI && !aURI.schemeIs("chrome")) {
52     return true;
53   }
55   dump("*** Preventing external load of chrome: URI into browser window\n");
56   dump("    Use --chrome <uri> instead\n");
57   return false;
60 function resolveURIInternal(aCmdLine, aArgument) {
61   var uri = aCmdLine.resolveURI(aArgument);
62   var uriFixup = Services.uriFixup;
64   if (!(uri instanceof Ci.nsIFileURL)) {
65     return Services.uriFixup.getFixupURIInfo(
66       aArgument,
67       uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
68     ).preferredURI;
69   }
71   try {
72     if (uri.file.exists()) {
73       return uri;
74     }
75   } catch (e) {
76     Cu.reportError(e);
77   }
79   // We have interpreted the argument as a relative file URI, but the file
80   // doesn't exist. Try URI fixup heuristics: see bug 290782.
82   try {
83     uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
84   } catch (e) {
85     Cu.reportError(e);
86   }
88   return uri;
91 let gKiosk = false;
92 let gMajorUpgrade = false;
93 var gFirstWindow = false;
95 const OVERRIDE_NONE = 0;
96 const OVERRIDE_NEW_PROFILE = 1;
97 const OVERRIDE_NEW_MSTONE = 2;
98 const OVERRIDE_NEW_BUILD_ID = 3;
99 /**
100  * Determines whether a home page override is needed.
101  * Returns:
102  *  OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
103  *  OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
104  *                      Gecko milestone (i.e. right after an upgrade).
105  *  OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
106  *                        same Gecko milestone (i.e. after a nightly upgrade).
107  *  OVERRIDE_NONE otherwise.
108  */
109 function needHomepageOverride(prefb) {
110   var savedmstone = prefb.getCharPref(
111     "browser.startup.homepage_override.mstone",
112     ""
113   );
115   if (savedmstone == "ignore") {
116     return OVERRIDE_NONE;
117   }
119   var mstone = Services.appinfo.platformVersion;
121   var savedBuildID = prefb.getCharPref(
122     "browser.startup.homepage_override.buildID",
123     ""
124   );
126   var buildID = Services.appinfo.platformBuildID;
128   if (mstone != savedmstone) {
129     // Bug 462254. Previous releases had a default pref to suppress the EULA
130     // agreement if the platform's installer had already shown one. Now with
131     // about:rights we've removed the EULA stuff and default pref, but we need
132     // a way to make existing profiles retain the default that we removed.
133     if (savedmstone) {
134       prefb.setBoolPref("browser.rights.3.shown", true);
136       // Remember that we saw a major version change.
137       gMajorUpgrade = true;
138     }
140     prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
141     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
142     return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
143   }
145   if (buildID != savedBuildID) {
146     prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
147     return OVERRIDE_NEW_BUILD_ID;
148   }
150   return OVERRIDE_NONE;
154  * Gets the override page for the first run after the application has been
155  * updated.
156  * @param  update
157  *         The nsIUpdate for the update that has been applied.
158  * @param  defaultOverridePage
159  *         The default override page.
160  * @return The override page.
161  */
162 function getPostUpdateOverridePage(update, defaultOverridePage) {
163   update = update.QueryInterface(Ci.nsIWritablePropertyBag);
164   let actions = update.getProperty("actions");
165   // When the update doesn't specify actions fallback to the original behavior
166   // of displaying the default override page.
167   if (!actions) {
168     return defaultOverridePage;
169   }
171   // The existence of silent or the non-existence of showURL in the actions both
172   // mean that an override page should not be displayed.
173   if (actions.includes("silent") || !actions.includes("showURL")) {
174     return "";
175   }
177   // If a policy was set to not allow the update.xml-provided
178   // URL to be used, use the default fallback (which will also
179   // be provided by the policy).
180   if (!Services.policies.isAllowed("postUpdateCustomPage")) {
181     return defaultOverridePage;
182   }
184   return update.getProperty("openURL") || defaultOverridePage;
188  * Open a browser window. If this is the initial launch, this function will
189  * attempt to use the navigator:blank window opened by BrowserGlue.jsm during
190  * early startup.
192  * @param cmdLine
193  *        The nsICommandLine object given to nsICommandLineHandler's handle
194  *        method.
195  *        Used to check if we are processing the command line for the initial launch.
196  * @param triggeringPrincipal
197  *        The nsIPrincipal to use as triggering principal for the page load(s).
198  * @param urlOrUrlList (optional)
199  *        When omitted, the browser window will be opened with the default
200  *        arguments, which will usually load the homepage.
201  *        This can be a JS array of urls provided as strings, each url will be
202  *        loaded in a tab. postData will be ignored in this case.
203  *        This can be a single url to load in the new window, provided as a string.
204  *        postData will be used in this case if provided.
205  * @param postData (optional)
206  *        An nsIInputStream object to use as POST data when loading the provided
207  *        url, or null.
208  * @param forcePrivate (optional)
209  *        Boolean. If set to true, the new window will be a private browsing one.
211  * @returns {ChromeWindow}
212  *          Returns the top level window opened.
213  */
214 function openBrowserWindow(
215   cmdLine,
216   triggeringPrincipal,
217   urlOrUrlList,
218   postData = null,
219   forcePrivate = false
220 ) {
221   let chromeURL = AppConstants.BROWSER_CHROME_URL;
222   const isStartup =
223     cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
225   let args;
226   if (!urlOrUrlList) {
227     // Just pass in the defaultArgs directly. We'll use system principal on the other end.
228     args = [gBrowserContentHandler.getArgs(isStartup)];
229   } else if (Array.isArray(urlOrUrlList)) {
230     // There isn't an explicit way to pass a principal here, so we load multiple URLs
231     // with system principal when we get to actually loading them.
232     if (
233       !triggeringPrincipal ||
234       !triggeringPrincipal.equals(lazy.gSystemPrincipal)
235     ) {
236       throw new Error(
237         "Can't open multiple URLs with something other than system principal."
238       );
239     }
240     // Passing an nsIArray for the url disables the "|"-splitting behavior.
241     let uriArray = Cc["@mozilla.org/array;1"].createInstance(
242       Ci.nsIMutableArray
243     );
244     urlOrUrlList.forEach(function(uri) {
245       var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
246         Ci.nsISupportsString
247       );
248       sstring.data = uri;
249       uriArray.appendElement(sstring);
250     });
251     args = [uriArray];
252   } else {
253     let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
254       Ci.nsIWritablePropertyBag2
255     );
256     extraOptions.setPropertyAsBool("fromExternal", true);
258     // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
259     // ie. avoid the loadOneOrMoreURIs function.
260     // Also, we need to pass the triggering principal.
261     args = [
262       urlOrUrlList,
263       extraOptions,
264       null, // refererInfo
265       postData,
266       undefined, // allowThirdPartyFixup; this would be `false` but that
267       // needs a conversion. Hopefully bug 1485961 will fix.
268       undefined, // user context id
269       null, // origin principal
270       null, // origin storage principal
271       triggeringPrincipal,
272     ];
273   }
275   if (isStartup) {
276     let win = Services.wm.getMostRecentWindow("navigator:blank");
277     if (win) {
278       // Remove the windowtype of our blank window so that we don't close it
279       // later on when seeing cmdLine.preventDefault is true.
280       win.document.documentElement.removeAttribute("windowtype");
282       if (forcePrivate) {
283         win.docShell.QueryInterface(
284           Ci.nsILoadContext
285         ).usePrivateBrowsing = true;
286         if (Services.prefs.getBoolPref(PRIVACY_SEGMENTATION_PREF)) {
287           // TODO: Changing this after the Window has been painted causes it to
288           // change Taskbar icons if the original one had a different AUMID.
289           // This must stay pref'ed off until this is resolved.
290           // https://bugzilla.mozilla.org/show_bug.cgi?id=1751010
291           lazy.WinTaskbar.setGroupIdForWindow(
292             win,
293             lazy.WinTaskbar.defaultPrivateGroupId
294           );
295           lazy.WindowsUIUtils.setWindowIconFromExe(
296             win,
297             Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
298             // This corresponds to the definitions in
299             // nsNativeAppSupportWin.h
300             PRIVATE_BROWSING_ICON_INDEX
301           );
302         }
303       }
305       let openTime = win.openTime;
306       win.location = chromeURL;
307       win.arguments = args; // <-- needs to be a plain JS array here.
309       ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
310       return win;
311     }
312   }
314   // We can't provide arguments to openWindow as a JS array.
315   if (!urlOrUrlList) {
316     // If we have a single string guaranteed to not contain '|' we can simply
317     // wrap it in an nsISupportsString object.
318     let [url] = args;
319     args = Cc["@mozilla.org/supports-string;1"].createInstance(
320       Ci.nsISupportsString
321     );
322     args.data = url;
323   } else {
324     // Otherwise, pass an nsIArray.
325     if (args.length > 1) {
326       let string = Cc["@mozilla.org/supports-string;1"].createInstance(
327         Ci.nsISupportsString
328       );
329       string.data = args[0];
330       args[0] = string;
331     }
332     let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
333     args.forEach(a => {
334       array.appendElement(a);
335     });
336     args = array;
337   }
339   let features =
340     "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
341   if (forcePrivate) {
342     features += ",private";
343   }
345   return Services.ww.openWindow(null, chromeURL, "_blank", features, args);
348 function openPreferences(cmdLine, extraArgs) {
349   openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
352 async function doSearch(searchTerm, cmdLine) {
353   // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
354   // preferences, but need nsIBrowserDOMWindow extensions
355   // Open the window immediately as BrowserContentHandler needs to
356   // be handled synchronously. Then load the search URI when the
357   // SearchService has loaded.
358   let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
359   await new Promise(resolve => {
360     Services.obs.addObserver(function observe(subject) {
361       if (subject == win) {
362         Services.obs.removeObserver(
363           observe,
364           "browser-delayed-startup-finished"
365         );
366         resolve();
367       }
368     }, "browser-delayed-startup-finished");
369   });
371   win.BrowserSearch.loadSearchFromCommandLine(
372     searchTerm,
373     lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
374       lazy.PrivateBrowsingUtils.isWindowPrivate(win),
375     lazy.gSystemPrincipal,
376     win.gBrowser.selectedBrowser.csp
377   ).catch(Cu.reportError);
380 function nsBrowserContentHandler() {
381   if (!gBrowserContentHandler) {
382     gBrowserContentHandler = this;
383   }
384   return gBrowserContentHandler;
386 nsBrowserContentHandler.prototype = {
387   /* nsISupports */
388   QueryInterface: ChromeUtils.generateQI([
389     "nsICommandLineHandler",
390     "nsIBrowserHandler",
391     "nsIContentHandler",
392     "nsICommandLineValidator",
393   ]),
395   /* nsICommandLineHandler */
396   handle: function bch_handle(cmdLine) {
397     if (cmdLine.handleFlag("kiosk", false)) {
398       gKiosk = true;
399     }
400     if (cmdLine.handleFlag("disable-pinch", false)) {
401       let defaults = Services.prefs.getDefaultBranch(null);
402       defaults.setBoolPref("apz.allow_zooming", false);
403       Services.prefs.lockPref("apz.allow_zooming");
404       defaults.setCharPref("browser.gesture.pinch.in", "");
405       Services.prefs.lockPref("browser.gesture.pinch.in");
406       defaults.setCharPref("browser.gesture.pinch.in.shift", "");
407       Services.prefs.lockPref("browser.gesture.pinch.in.shift");
408       defaults.setCharPref("browser.gesture.pinch.out", "");
409       Services.prefs.lockPref("browser.gesture.pinch.out");
410       defaults.setCharPref("browser.gesture.pinch.out.shift", "");
411       Services.prefs.lockPref("browser.gesture.pinch.out.shift");
412     }
413     if (cmdLine.handleFlag("browser", false)) {
414       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
415       cmdLine.preventDefault = true;
416     }
418     // In the past, when an instance was not already running, the -remote
419     // option returned an error code. Any script or application invoking the
420     // -remote option is expected to be handling this case, otherwise they
421     // wouldn't be doing anything when there is no Firefox already running.
422     // Making the -remote option always return an error code makes those
423     // scripts or applications handle the situation as if Firefox was not
424     // already running.
425     if (cmdLine.handleFlag("remote", true)) {
426       throw Components.Exception("", Cr.NS_ERROR_ABORT);
427     }
429     var uriparam;
430     try {
431       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
432         let uri = resolveURIInternal(cmdLine, uriparam);
433         if (!shouldLoadURI(uri)) {
434           continue;
435         }
436         openBrowserWindow(cmdLine, lazy.gSystemPrincipal, uri.spec);
437         cmdLine.preventDefault = true;
438       }
439     } catch (e) {
440       Cu.reportError(e);
441     }
443     try {
444       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
445         let uri = resolveURIInternal(cmdLine, uriparam);
446         handURIToExistingBrowser(
447           uri,
448           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
449           cmdLine,
450           false,
451           lazy.gSystemPrincipal
452         );
453         cmdLine.preventDefault = true;
454       }
455     } catch (e) {
456       Cu.reportError(e);
457     }
459     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
460     if (chromeParam) {
461       // Handle old preference dialog URLs.
462       if (
463         chromeParam == "chrome://browser/content/pref/pref.xul" ||
464         chromeParam == "chrome://browser/content/preferences/preferences.xul"
465       ) {
466         openPreferences(cmdLine);
467         cmdLine.preventDefault = true;
468       } else {
469         try {
470           let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
471           let isLocal = uri => {
472             let localSchemes = new Set(["chrome", "file", "resource"]);
473             if (uri instanceof Ci.nsINestedURI) {
474               uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
475             }
476             return localSchemes.has(uri.scheme);
477           };
478           if (isLocal(resolvedURI)) {
479             // If the URI is local, we are sure it won't wrongly inherit chrome privs
480             let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
481             // Provide 1 null argument, as openWindow has a different behavior
482             // when the arg count is 0.
483             let argArray = Cc["@mozilla.org/array;1"].createInstance(
484               Ci.nsIMutableArray
485             );
486             argArray.appendElement(null);
487             Services.ww.openWindow(
488               null,
489               resolvedURI.spec,
490               "_blank",
491               features,
492               argArray
493             );
494             cmdLine.preventDefault = true;
495           } else {
496             dump("*** Preventing load of web URI as chrome\n");
497             dump(
498               "    If you're trying to load a webpage, do not pass --chrome.\n"
499             );
500           }
501         } catch (e) {
502           Cu.reportError(e);
503         }
504       }
505     }
506     if (cmdLine.handleFlag("preferences", false)) {
507       openPreferences(cmdLine);
508       cmdLine.preventDefault = true;
509     }
510     if (cmdLine.handleFlag("silent", false)) {
511       cmdLine.preventDefault = true;
512     }
514     try {
515       var privateWindowParam = cmdLine.handleFlagWithParam(
516         "private-window",
517         false
518       );
519       if (privateWindowParam) {
520         let forcePrivate = true;
521         let resolvedURI;
522         if (!lazy.PrivateBrowsingUtils.enabled) {
523           // Load about:privatebrowsing in a normal tab, which will display an error indicating
524           // access to private browsing has been disabled.
525           forcePrivate = false;
526           resolvedURI = Services.io.newURI("about:privatebrowsing");
527         } else {
528           resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
529         }
530         handURIToExistingBrowser(
531           resolvedURI,
532           Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
533           cmdLine,
534           forcePrivate,
535           lazy.gSystemPrincipal
536         );
537         cmdLine.preventDefault = true;
538       }
539     } catch (e) {
540       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
541         throw e;
542       }
543       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
544       if (cmdLine.handleFlag("private-window", false)) {
545         openBrowserWindow(
546           cmdLine,
547           lazy.gSystemPrincipal,
548           "about:privatebrowsing",
549           null,
550           lazy.PrivateBrowsingUtils.enabled
551         );
552         cmdLine.preventDefault = true;
553       }
554     }
556     var searchParam = cmdLine.handleFlagWithParam("search", false);
557     if (searchParam) {
558       doSearch(searchParam, cmdLine);
559       cmdLine.preventDefault = true;
560     }
562     // The global PB Service consumes this flag, so only eat it in per-window
563     // PB builds.
564     if (
565       cmdLine.handleFlag("private", false) &&
566       lazy.PrivateBrowsingUtils.enabled
567     ) {
568       lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
569       if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
570         let win = Services.wm.getMostRecentWindow("navigator:blank");
571         if (win) {
572           win.docShell.QueryInterface(
573             Ci.nsILoadContext
574           ).usePrivateBrowsing = true;
575         }
576       }
577     }
578     if (cmdLine.handleFlag("setDefaultBrowser", false)) {
579       lazy.ShellService.setDefaultBrowser(true, true);
580     }
582     if (cmdLine.handleFlag("first-startup", false)) {
583       lazy.FirstStartup.init();
584     }
586     var fileParam = cmdLine.handleFlagWithParam("file", false);
587     if (fileParam) {
588       var file = cmdLine.resolveFile(fileParam);
589       var fileURI = Services.io.newFileURI(file);
590       openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
591       cmdLine.preventDefault = true;
592     }
594     if (AppConstants.platform == "win") {
595       // Handle "? searchterm" for Windows Vista start menu integration
596       for (var i = cmdLine.length - 1; i >= 0; --i) {
597         var param = cmdLine.getArgument(i);
598         if (param.match(/^\? /)) {
599           cmdLine.removeArguments(i, i);
600           cmdLine.preventDefault = true;
602           searchParam = param.substr(2);
603           doSearch(searchParam, cmdLine);
604         }
605       }
606     }
607   },
609   get helpInfo() {
610     let info =
611       "  --browser          Open a browser window.\n" +
612       "  --new-window <url> Open <url> in a new window.\n" +
613       "  --new-tab <url>    Open <url> in a new tab.\n" +
614       "  --private-window <url> Open <url> in a new private window.\n";
615     if (AppConstants.platform == "win") {
616       info += "  --preferences      Open Options dialog.\n";
617     } else {
618       info += "  --preferences      Open Preferences dialog.\n";
619     }
620     info +=
621       "  --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
622     info +=
623       "  --window-size width[,height] Width and optionally height of screenshot.\n";
624     info +=
625       "  --search <term>    Search <term> with your default search engine.\n";
626     info += "  --setDefaultBrowser Set this app as the default browser.\n";
627     info +=
628       "  --first-startup    Run post-install actions before opening a new window.\n";
629     info += "  --kiosk            Start the browser in kiosk mode.\n";
630     info +=
631       "  --disable-pinch    Disable touch-screen and touch-pad pinch gestures.\n";
632     return info;
633   },
635   /* nsIBrowserHandler */
637   get defaultArgs() {
638     return this.getArgs();
639   },
641   getArgs(isStartup = false) {
642     var prefb = Services.prefs;
644     if (!gFirstWindow) {
645       gFirstWindow = true;
646       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
647         return "about:privatebrowsing";
648       }
649     }
651     var override;
652     var overridePage = "";
653     var additionalPage = "";
654     var willRestoreSession = false;
655     try {
656       // Read the old value of homepage_override.mstone before
657       // needHomepageOverride updates it, so that we can later add it to the
658       // URL if we do end up showing an overridePage. This makes it possible
659       // to have the overridePage's content vary depending on the version we're
660       // upgrading from.
661       let old_mstone = Services.prefs.getCharPref(
662         "browser.startup.homepage_override.mstone",
663         "unknown"
664       );
665       let old_buildId = Services.prefs.getCharPref(
666         "browser.startup.homepage_override.buildID",
667         "unknown"
668       );
669       override = needHomepageOverride(prefb);
670       if (override != OVERRIDE_NONE) {
671         switch (override) {
672           case OVERRIDE_NEW_PROFILE:
673             // New profile.
674             overridePage = Services.urlFormatter.formatURLPref(
675               "startup.homepage_welcome_url"
676             );
677             additionalPage = Services.urlFormatter.formatURLPref(
678               "startup.homepage_welcome_url.additional"
679             );
680             // Turn on 'later run' pages for new profiles.
681             lazy.LaterRun.enabled = true;
682             break;
683           case OVERRIDE_NEW_MSTONE:
684             // Check whether we will restore a session. If we will, we assume
685             // that this is an "update" session. This does not take crashes
686             // into account because that requires waiting for the session file
687             // to be read. If a crash occurs after updating, before restarting,
688             // we may open the startPage in addition to restoring the session.
689             willRestoreSession = lazy.SessionStartup.isAutomaticRestoreEnabled();
691             overridePage = Services.urlFormatter.formatURLPref(
692               "startup.homepage_override_url"
693             );
694             let update = lazy.UpdateManager.readyUpdate;
695             if (
696               update &&
697               Services.vc.compare(update.appVersion, old_mstone) > 0
698             ) {
699               overridePage = getPostUpdateOverridePage(update, overridePage);
700               // Send the update ping to signal that the update was successful.
701               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
702             }
704             overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
705             break;
706           case OVERRIDE_NEW_BUILD_ID:
707             if (lazy.UpdateManager.readyUpdate) {
708               // Send the update ping to signal that the update was successful.
709               lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
710             }
711             break;
712         }
713       }
714     } catch (ex) {}
716     // formatURLPref might return "about:blank" if getting the pref fails
717     if (overridePage == "about:blank") {
718       overridePage = "";
719     }
721     // Allow showing a one-time startup override if we're not showing one
722     if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
723       try {
724         // Show if we haven't passed the expiration or there's no expiration
725         const { expire, url } = JSON.parse(
726           Services.urlFormatter.formatURLPref(ONCE_PREF)
727         );
728         if (!(Date.now() > expire)) {
729           // Only set allowed urls as override pages
730           overridePage = url
731             .split("|")
732             .map(val => {
733               try {
734                 return new URL(val);
735               } catch (ex) {
736                 // Invalid URL, so filter out below
737                 Cu.reportError(`Invalid once url: ${ex}`);
738                 return null;
739               }
740             })
741             .filter(
742               parsed =>
743                 parsed &&
744                 parsed.protocol == "https:" &&
745                 // Only accept exact hostname or subdomain; without port
746                 ONCE_DOMAINS.includes(
747                   Services.eTLD.getBaseDomainFromHost(parsed.host)
748                 )
749             )
750             .join("|");
752           // Be noisy as properly configured urls should be unchanged
753           if (overridePage != url) {
754             Cu.reportError(`Mismatched once urls: ${url}`);
755           }
756         }
757       } catch (ex) {
758         // Invalid json pref, so ignore (and clear below)
759         Cu.reportError(`Invalid once pref: ${ex}`);
760       } finally {
761         prefb.clearUserPref(ONCE_PREF);
762       }
763     }
765     if (!additionalPage) {
766       additionalPage = lazy.LaterRun.getURL() || "";
767     }
769     if (additionalPage && additionalPage != "about:blank") {
770       if (overridePage) {
771         overridePage += "|" + additionalPage;
772       } else {
773         overridePage = additionalPage;
774       }
775     }
777     var startPage = "";
778     try {
779       var choice = prefb.getIntPref("browser.startup.page");
780       if (choice == 1 || choice == 3) {
781         startPage = lazy.HomePage.get();
782       }
783     } catch (e) {
784       Cu.reportError(e);
785     }
787     if (startPage == "about:blank") {
788       startPage = "";
789     }
791     let skipStartPage =
792       override == OVERRIDE_NEW_PROFILE &&
793       prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
794     // Only show the startPage if we're not restoring an update session and are
795     // not set to skip the start page on this profile
796     if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
797       return overridePage + "|" + startPage;
798     }
800     return overridePage || startPage || "about:blank";
801   },
803   mFeatures: null,
805   getFeatures: function bch_features(cmdLine) {
806     if (this.mFeatures === null) {
807       this.mFeatures = "";
809       if (cmdLine) {
810         try {
811           var width = cmdLine.handleFlagWithParam("width", false);
812           var height = cmdLine.handleFlagWithParam("height", false);
813           var left = cmdLine.handleFlagWithParam("left", false);
814           var top = cmdLine.handleFlagWithParam("top", false);
816           if (width) {
817             this.mFeatures += ",width=" + width;
818           }
819           if (height) {
820             this.mFeatures += ",height=" + height;
821           }
822           if (left) {
823             this.mFeatures += ",left=" + left;
824           }
825           if (top) {
826             this.mFeatures += ",top=" + top;
827           }
828         } catch (e) {}
829       }
831       // The global PB Service consumes this flag, so only eat it in per-window
832       // PB builds.
833       if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
834         this.mFeatures += ",private";
835       }
837       if (
838         Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
839         !Services.wm.getMostRecentWindow("navigator:browser")
840       ) {
841         this.mFeatures += ",suppressanimation";
842       }
843     }
845     return this.mFeatures;
846   },
848   get kiosk() {
849     return gKiosk;
850   },
852   get majorUpgrade() {
853     return gMajorUpgrade;
854   },
856   set majorUpgrade(val) {
857     gMajorUpgrade = val;
858   },
860   /* nsIContentHandler */
862   handleContent: function bch_handleContent(contentType, context, request) {
863     const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
865     try {
866       var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
867         Ci.nsIWebNavigationInfo
868       );
869       if (!webNavInfo.isTypeSupported(contentType)) {
870         throw NS_ERROR_WONT_HANDLE_CONTENT;
871       }
872     } catch (e) {
873       throw NS_ERROR_WONT_HANDLE_CONTENT;
874     }
876     request.QueryInterface(Ci.nsIChannel);
877     handURIToExistingBrowser(
878       request.URI,
879       Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
880       null,
881       false,
882       request.loadInfo.triggeringPrincipal
883     );
884     request.cancel(Cr.NS_BINDING_ABORTED);
885   },
887   /* nsICommandLineValidator */
888   validate: function bch_validate(cmdLine) {
889     var urlFlagIdx = cmdLine.findFlag("url", false);
890     if (
891       urlFlagIdx > -1 &&
892       cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
893     ) {
894       var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
895       if (
896         cmdLine.length != urlFlagIdx + 2 ||
897         /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
898       ) {
899         throw Components.Exception("", Cr.NS_ERROR_ABORT);
900       }
901       var isDefault = false;
902       try {
903         var url =
904           Services.urlFormatter.formatURLPref("app.support.baseURL") +
905           "win10-default-browser";
906         if (urlParam == url) {
907           isDefault = lazy.ShellService.isDefaultBrowser(false, false);
908         }
909       } catch (ex) {}
910       if (isDefault) {
911         // Firefox is already the default HTTP handler.
912         // We don't have to show the instruction page.
913         throw Components.Exception("", Cr.NS_ERROR_ABORT);
914       }
915     }
916   },
918 var gBrowserContentHandler = new nsBrowserContentHandler();
920 function handURIToExistingBrowser(
921   uri,
922   location,
923   cmdLine,
924   forcePrivate,
925   triggeringPrincipal
926 ) {
927   if (!shouldLoadURI(uri)) {
928     return;
929   }
931   // Unless using a private window is forced, open external links in private
932   // windows only if we're in perma-private mode.
933   var allowPrivate =
934     forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
935   var navWin = lazy.BrowserWindowTracker.getTopWindow({
936     private: allowPrivate,
937   });
938   if (!navWin) {
939     // if we couldn't load it in an existing window, open a new one
940     openBrowserWindow(
941       cmdLine,
942       triggeringPrincipal,
943       uri.spec,
944       null,
945       forcePrivate
946     );
947     return;
948   }
950   var bwin = navWin.browserDOMWindow;
951   bwin.openURI(
952     uri,
953     null,
954     location,
955     Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
956     triggeringPrincipal
957   );
961  * If given URI is a file type or a protocol, record telemetry that
962  * Firefox was invoked or launched (if `isLaunch` is truth-y).  If the
963  * file type or protocol is not registered by default, record it as
964  * ".<other extension>" or "<other protocol>".
966  * @param uri
967  *        The URI Firefox was asked to handle.
968  * @param isLaunch
969  *        truth-y if Firefox was launched/started rather than running and invoked.
970  */
971 function maybeRecordToHandleTelemetry(uri, isLaunch) {
972   let scalar = isLaunch
973     ? "os.environment.launched_to_handle"
974     : "os.environment.invoked_to_handle";
976   if (uri instanceof Ci.nsIFileURL) {
977     let extension = "." + uri.fileExtension.toLowerCase();
978     // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
979     // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
980     let registeredExtensions = new Set([
981       ".avif",
982       ".htm",
983       ".html",
984       ".pdf",
985       ".shtml",
986       ".xht",
987       ".xhtml",
988       ".svg",
989       ".webp",
990     ]);
991     if (registeredExtensions.has(extension)) {
992       Services.telemetry.keyedScalarAdd(scalar, extension, 1);
993     } else {
994       Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
995     }
996   } else if (uri) {
997     let scheme = uri.scheme.toLowerCase();
998     let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
999     if (registeredSchemes.has(scheme)) {
1000       Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
1001     } else {
1002       Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1003     }
1004   }
1007 function nsDefaultCommandLineHandler() {}
1009 nsDefaultCommandLineHandler.prototype = {
1010   /* nsISupports */
1011   QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1013   _haveProfile: false,
1015   /* nsICommandLineHandler */
1016   handle: function dch_handle(cmdLine) {
1017     var urilist = [];
1019     if (
1020       cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1021       Services.startup.wasSilentlyStarted
1022     ) {
1023       // If we are starting up in silent mode, don't open a window. We also need
1024       // to make sure that the application doesn't immediately exit, so stay in
1025       // a LastWindowClosingSurvivalArea until a window opens.
1026       Services.startup.enterLastWindowClosingSurvivalArea();
1027       Services.obs.addObserver(function windowOpenObserver() {
1028         Services.startup.exitLastWindowClosingSurvivalArea();
1029         Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
1030       }, "domwindowopened");
1031       return;
1032     }
1034     if (AppConstants.platform == "win") {
1035       // If we don't have a profile selected yet (e.g. the Profile Manager is
1036       // displayed) we will crash if we open an url and then select a profile. To
1037       // prevent this handle all url command line flags and set the command line's
1038       // preventDefault to true to prevent the display of the ui. The initial
1039       // command line will be retained when nsAppRunner calls LaunchChild though
1040       // urls launched after the initial launch will be lost.
1041       if (!this._haveProfile) {
1042         try {
1043           // This will throw when a profile has not been selected.
1044           Services.dirsvc.get("ProfD", Ci.nsIFile);
1045           this._haveProfile = true;
1046         } catch (e) {
1047           // eslint-disable-next-line no-empty
1048           while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1049           cmdLine.preventDefault = true;
1050         }
1051       }
1052     }
1054     // `-osint` and handling registered file types and protocols is Windows-only.
1055     let launchedWithArg_osint =
1056       AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
1057     if (launchedWithArg_osint) {
1058       cmdLine.handleFlag("osint", false);
1059     }
1061     try {
1062       var ar;
1063       while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1064         var uri = resolveURIInternal(cmdLine, ar);
1065         urilist.push(uri);
1067         if (launchedWithArg_osint) {
1068           launchedWithArg_osint = false;
1070           // We use the resolved URI here, even though it can produce
1071           // surprising results where-by `-osint -url test.pdf` resolves to
1072           // a query with search parameter "test.pdf".  But that shouldn't
1073           // happen when Firefox is launched by Windows itself: files should
1074           // exist and be resolved to file URLs.
1075           const isLaunch =
1076             cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1078           maybeRecordToHandleTelemetry(uri, isLaunch);
1079         }
1080       }
1081     } catch (e) {
1082       Cu.reportError(e);
1083     }
1085     if (cmdLine.findFlag("screenshot", true) != -1) {
1086       lazy.HeadlessShell.handleCmdLineArgs(
1087         cmdLine,
1088         urilist.filter(shouldLoadURI).map(u => u.spec)
1089       );
1090       return;
1091     }
1093     for (let i = 0; i < cmdLine.length; ++i) {
1094       var curarg = cmdLine.getArgument(i);
1095       if (curarg.match(/^-/)) {
1096         Cu.reportError(
1097           "Warning: unrecognized command line flag " + curarg + "\n"
1098         );
1099         // To emulate the pre-nsICommandLine behavior, we ignore
1100         // the argument after an unrecognized flag.
1101         ++i;
1102       } else {
1103         try {
1104           urilist.push(resolveURIInternal(cmdLine, curarg));
1105         } catch (e) {
1106           Cu.reportError(
1107             "Error opening URI '" +
1108               curarg +
1109               "' from the command line: " +
1110               e +
1111               "\n"
1112           );
1113         }
1114       }
1115     }
1117     if (urilist.length) {
1118       if (
1119         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1120         urilist.length == 1
1121       ) {
1122         // Try to find an existing window and load our URI into the
1123         // current tab, new tab, or new window as prefs determine.
1124         try {
1125           handURIToExistingBrowser(
1126             urilist[0],
1127             Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1128             cmdLine,
1129             false,
1130             lazy.gSystemPrincipal
1131           );
1132           return;
1133         } catch (e) {}
1134       }
1136       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
1137       if (URLlist.length) {
1138         openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
1139       }
1140     } else if (!cmdLine.preventDefault) {
1141       if (
1142         AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
1143         cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1144         lazy.WindowsUIUtils.inTabletMode
1145       ) {
1146         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1147         let win = lazy.BrowserWindowTracker.getTopWindow();
1148         if (win) {
1149           win.focus();
1150           return;
1151         }
1152       }
1153       openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1154     } else {
1155       // Need a better solution in the future to avoid opening the blank window
1156       // when command line parameters say we are not going to show a browser
1157       // window, but for now the blank window getting closed quickly (and
1158       // causing only a slight flicker) is better than leaving it open.
1159       let win = Services.wm.getMostRecentWindow("navigator:blank");
1160       if (win) {
1161         win.close();
1162       }
1163     }
1164   },
1166   helpInfo: "",