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";
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",
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"],
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)) {
40 return Cc["@mozilla.org/system-alerts-service;1"]
41 ?.getService(Ci.nsIAlertsService)
42 ?.QueryInterface(Ci.nsIWindowsAlertsService);
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")) {
58 dump("*** Preventing external load of chrome: URI into browser window\n");
59 dump(" Use --chrome <uri> instead\n");
63 function resolveURIInternal(aCmdLine, aArgument) {
64 // If using Firefox protocol handler remove it from URI
65 // at this stage. This is before we would otherwise
66 // record telemetry so do that here.
67 if (aArgument.startsWith("firefox:")) {
68 aArgument = aArgument.substring("firefox:".length);
69 Services.telemetry.keyedScalarAdd(
70 "os.environment.launched_to_handle",
75 if (aArgument.startsWith("firefox-private:")) {
76 aArgument = aArgument.substring("firefox-private:".length);
77 Services.telemetry.keyedScalarAdd(
78 "os.environment.launched_to_handle",
83 var uri = aCmdLine.resolveURI(aArgument);
84 var uriFixup = Services.uriFixup;
86 if (!(uri instanceof Ci.nsIFileURL)) {
87 return Services.uriFixup.getFixupURIInfo(
89 uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
94 if (uri.file.exists()) {
101 // We have interpreted the argument as a relative file URI, but the file
102 // doesn't exist. Try URI fixup heuristics: see bug 290782.
105 uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
114 let gMajorUpgrade = false;
115 let gFirstRunProfile = false;
116 var gFirstWindow = false;
118 const OVERRIDE_NONE = 0;
119 const OVERRIDE_NEW_PROFILE = 1;
120 const OVERRIDE_NEW_MSTONE = 2;
121 const OVERRIDE_NEW_BUILD_ID = 3;
123 * Determines whether a home page override is needed.
125 * OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
126 * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
127 * Gecko milestone (i.e. right after an upgrade).
128 * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
129 * same Gecko milestone (i.e. after a nightly upgrade).
130 * OVERRIDE_NONE otherwise.
132 function needHomepageOverride(prefb) {
133 var savedmstone = prefb.getCharPref(
134 "browser.startup.homepage_override.mstone",
138 if (savedmstone == "ignore") {
139 return OVERRIDE_NONE;
142 var mstone = Services.appinfo.platformVersion;
144 var savedBuildID = prefb.getCharPref(
145 "browser.startup.homepage_override.buildID",
149 var buildID = Services.appinfo.platformBuildID;
151 if (mstone != savedmstone) {
152 // Bug 462254. Previous releases had a default pref to suppress the EULA
153 // agreement if the platform's installer had already shown one. Now with
154 // about:rights we've removed the EULA stuff and default pref, but we need
155 // a way to make existing profiles retain the default that we removed.
157 prefb.setBoolPref("browser.rights.3.shown", true);
159 // Remember that we saw a major version change.
160 gMajorUpgrade = true;
163 prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
164 prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
165 return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
168 if (buildID != savedBuildID) {
169 prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
170 return OVERRIDE_NEW_BUILD_ID;
173 return OVERRIDE_NONE;
177 * Gets the override page for the first run after the application has been
180 * The nsIUpdate for the update that has been applied.
181 * @param defaultOverridePage
182 * The default override page.
183 * @return The override page.
185 function getPostUpdateOverridePage(update, defaultOverridePage) {
186 update = update.QueryInterface(Ci.nsIWritablePropertyBag);
187 let actions = update.getProperty("actions");
188 // When the update doesn't specify actions fallback to the original behavior
189 // of displaying the default override page.
191 return defaultOverridePage;
194 // The existence of silent or the non-existence of showURL in the actions both
195 // mean that an override page should not be displayed.
196 if (actions.includes("silent") || !actions.includes("showURL")) {
200 // If a policy was set to not allow the update.xml-provided
201 // URL to be used, use the default fallback (which will also
202 // be provided by the policy).
203 if (!Services.policies.isAllowed("postUpdateCustomPage")) {
204 return defaultOverridePage;
207 return update.getProperty("openURL") || defaultOverridePage;
211 * Open a browser window. If this is the initial launch, this function will
212 * attempt to use the navigator:blank window opened by BrowserGlue.sys.mjs during
216 * The nsICommandLine object given to nsICommandLineHandler's handle
218 * Used to check if we are processing the command line for the initial launch.
219 * @param triggeringPrincipal
220 * The nsIPrincipal to use as triggering principal for the page load(s).
221 * @param urlOrUrlList (optional)
222 * When omitted, the browser window will be opened with the default
223 * arguments, which will usually load the homepage.
224 * This can be a JS array of urls provided as strings, each url will be
225 * loaded in a tab. postData will be ignored in this case.
226 * This can be a single url to load in the new window, provided as a string.
227 * postData will be used in this case if provided.
228 * @param postData (optional)
229 * An nsIInputStream object to use as POST data when loading the provided
231 * @param forcePrivate (optional)
232 * Boolean. If set to true, the new window will be a private browsing one.
234 * @returns {ChromeWindow}
235 * Returns the top level window opened.
237 function openBrowserWindow(
245 cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
249 // Just pass in the defaultArgs directly. We'll use system principal on the other end.
250 args = [gBrowserContentHandler.getArgs(isStartup)];
251 } else if (Array.isArray(urlOrUrlList)) {
252 // There isn't an explicit way to pass a principal here, so we load multiple URLs
253 // with system principal when we get to actually loading them.
255 !triggeringPrincipal ||
256 !triggeringPrincipal.equals(lazy.gSystemPrincipal)
259 "Can't open multiple URLs with something other than system principal."
262 // Passing an nsIArray for the url disables the "|"-splitting behavior.
263 let uriArray = Cc["@mozilla.org/array;1"].createInstance(
266 urlOrUrlList.forEach(function (uri) {
267 var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
271 uriArray.appendElement(sstring);
275 let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
276 Ci.nsIWritablePropertyBag2
278 extraOptions.setPropertyAsBool("fromExternal", true);
280 // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
281 // ie. avoid the loadOneOrMoreURIs function.
282 // Also, we need to pass the triggering principal.
288 undefined, // allowThirdPartyFixup; this would be `false` but that
289 // needs a conversion. Hopefully bug 1485961 will fix.
290 undefined, // user context id
291 null, // origin principal
292 null, // origin storage principal
298 let win = Services.wm.getMostRecentWindow("navigator:blank");
300 // Remove the windowtype of our blank window so that we don't close it
301 // later on when seeing cmdLine.preventDefault is true.
302 win.document.documentElement.removeAttribute("windowtype");
305 win.docShell.QueryInterface(
307 ).usePrivateBrowsing = true;
310 AppConstants.platform == "win" &&
311 lazy.NimbusFeatures.majorRelease2022.getVariable(
312 "feltPrivacyWindowSeparation"
315 lazy.WinTaskbar.setGroupIdForWindow(
317 lazy.WinTaskbar.defaultPrivateGroupId
319 lazy.WindowsUIUtils.setWindowIconFromExe(
321 Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
322 // This corresponds to the definitions in
323 // nsNativeAppSupportWin.h
324 PRIVATE_BROWSING_ICON_INDEX
329 let openTime = win.openTime;
330 win.location = AppConstants.BROWSER_CHROME_URL;
331 win.arguments = args; // <-- needs to be a plain JS array here.
333 ChromeUtils.addProfilerMarker("earlyBlankWindowVisible", openTime);
334 lazy.BrowserWindowTracker.registerOpeningWindow(win, forcePrivate);
339 // We can't provide arguments to openWindow as a JS array.
341 // If we have a single string guaranteed to not contain '|' we can simply
342 // wrap it in an nsISupportsString object.
344 args = Cc["@mozilla.org/supports-string;1"].createInstance(
349 // Otherwise, pass an nsIArray.
350 if (args.length > 1) {
351 let string = Cc["@mozilla.org/supports-string;1"].createInstance(
354 string.data = args[0];
357 let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
359 array.appendElement(a);
364 return lazy.BrowserWindowTracker.openWindow({
366 features: gBrowserContentHandler.getFeatures(cmdLine),
367 private: forcePrivate,
371 function openPreferences(cmdLine, extraArgs) {
372 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:preferences");
375 async function doSearch(searchTerm, cmdLine) {
376 // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
377 // preferences, but need nsIBrowserDOMWindow extensions
378 // Open the window immediately as BrowserContentHandler needs to
379 // be handled synchronously. Then load the search URI when the
380 // SearchService has loaded.
381 let win = openBrowserWindow(cmdLine, lazy.gSystemPrincipal, "about:blank");
382 await new Promise(resolve => {
383 Services.obs.addObserver(function observe(subject) {
384 if (subject == win) {
385 Services.obs.removeObserver(
387 "browser-delayed-startup-finished"
391 }, "browser-delayed-startup-finished");
394 win.BrowserSearch.loadSearchFromCommandLine(
396 lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode ||
397 lazy.PrivateBrowsingUtils.isWindowPrivate(win),
398 lazy.gSystemPrincipal,
399 win.gBrowser.selectedBrowser.csp
400 ).catch(console.error);
403 export function nsBrowserContentHandler() {
404 if (!gBrowserContentHandler) {
405 gBrowserContentHandler = this;
407 return gBrowserContentHandler;
410 nsBrowserContentHandler.prototype = {
412 QueryInterface: ChromeUtils.generateQI([
413 "nsICommandLineHandler",
416 "nsICommandLineValidator",
419 /* nsICommandLineHandler */
420 handle: function bch_handle(cmdLine) {
422 cmdLine.handleFlag("kiosk", false) ||
423 cmdLine.handleFlagWithParam("kiosk-monitor", false)
427 if (cmdLine.handleFlag("disable-pinch", false)) {
428 let defaults = Services.prefs.getDefaultBranch(null);
429 defaults.setBoolPref("apz.allow_zooming", false);
430 Services.prefs.lockPref("apz.allow_zooming");
431 defaults.setCharPref("browser.gesture.pinch.in", "");
432 Services.prefs.lockPref("browser.gesture.pinch.in");
433 defaults.setCharPref("browser.gesture.pinch.in.shift", "");
434 Services.prefs.lockPref("browser.gesture.pinch.in.shift");
435 defaults.setCharPref("browser.gesture.pinch.out", "");
436 Services.prefs.lockPref("browser.gesture.pinch.out");
437 defaults.setCharPref("browser.gesture.pinch.out.shift", "");
438 Services.prefs.lockPref("browser.gesture.pinch.out.shift");
440 if (cmdLine.handleFlag("browser", false)) {
441 openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
442 cmdLine.preventDefault = true;
447 while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
448 let uri = resolveURIInternal(cmdLine, uriparam);
449 if (!shouldLoadURI(uri)) {
452 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, uri.spec);
453 cmdLine.preventDefault = true;
460 while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
461 let uri = resolveURIInternal(cmdLine, uriparam);
462 handURIToExistingBrowser(
464 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
467 lazy.gSystemPrincipal
469 cmdLine.preventDefault = true;
475 var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
477 // Handle old preference dialog URLs.
479 chromeParam == "chrome://browser/content/pref/pref.xul" ||
480 chromeParam == "chrome://browser/content/preferences/preferences.xul"
482 openPreferences(cmdLine);
483 cmdLine.preventDefault = true;
486 let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
487 let isLocal = uri => {
488 let localSchemes = new Set(["chrome", "file", "resource"]);
489 if (uri instanceof Ci.nsINestedURI) {
490 uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
492 return localSchemes.has(uri.scheme);
494 if (isLocal(resolvedURI)) {
495 // If the URI is local, we are sure it won't wrongly inherit chrome privs
496 let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
497 // Provide 1 null argument, as openWindow has a different behavior
498 // when the arg count is 0.
499 let argArray = Cc["@mozilla.org/array;1"].createInstance(
502 argArray.appendElement(null);
503 Services.ww.openWindow(
510 cmdLine.preventDefault = true;
512 dump("*** Preventing load of web URI as chrome\n");
514 " If you're trying to load a webpage, do not pass --chrome.\n"
522 if (cmdLine.handleFlag("preferences", false)) {
523 openPreferences(cmdLine);
524 cmdLine.preventDefault = true;
526 if (cmdLine.handleFlag("silent", false)) {
527 cmdLine.preventDefault = true;
531 var privateWindowParam = cmdLine.handleFlagWithParam(
535 // Check for Firefox private browsing protocol handler here.
537 let urlFlagIdx = cmdLine.findFlag("url", false);
538 if (urlFlagIdx > -1 && cmdLine.length > 1) {
539 url = cmdLine.getArgument(urlFlagIdx + 1);
541 if (privateWindowParam || url?.startsWith("firefox-private:")) {
542 let forcePrivate = true;
544 if (!lazy.PrivateBrowsingUtils.enabled) {
545 // Load about:privatebrowsing in a normal tab, which will display an error indicating
546 // access to private browsing has been disabled.
547 forcePrivate = false;
548 resolvedURI = Services.io.newURI("about:privatebrowsing");
549 } else if (url?.startsWith("firefox-private:")) {
550 // We can safely remove the flag and parameter now.
551 cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
552 resolvedURI = resolveURIInternal(cmdLine, url);
554 resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
556 handURIToExistingBrowser(
558 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
561 lazy.gSystemPrincipal
563 cmdLine.preventDefault = true;
566 if (e.result != Cr.NS_ERROR_INVALID_ARG) {
569 // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
570 if (cmdLine.handleFlag("private-window", false)) {
573 lazy.gSystemPrincipal,
574 "about:privatebrowsing",
576 lazy.PrivateBrowsingUtils.enabled
578 cmdLine.preventDefault = true;
582 var searchParam = cmdLine.handleFlagWithParam("search", false);
584 doSearch(searchParam, cmdLine);
585 cmdLine.preventDefault = true;
588 // The global PB Service consumes this flag, so only eat it in per-window
591 cmdLine.handleFlag("private", false) &&
592 lazy.PrivateBrowsingUtils.enabled
594 lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
595 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
596 let win = Services.wm.getMostRecentWindow("navigator:blank");
598 win.docShell.QueryInterface(
600 ).usePrivateBrowsing = true;
604 if (cmdLine.handleFlag("setDefaultBrowser", false)) {
605 // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
606 // is an implementation of an interface method and changing it to be async would be complicated
607 // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
609 lazy.ShellService.setDefaultBrowser(true).catch(e => {
610 console.error("setDefaultBrowser failed:", e);
614 if (cmdLine.handleFlag("first-startup", false)) {
615 lazy.FirstStartup.init();
618 var fileParam = cmdLine.handleFlagWithParam("file", false);
620 var file = cmdLine.resolveFile(fileParam);
621 var fileURI = Services.io.newFileURI(file);
622 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
623 cmdLine.preventDefault = true;
626 if (AppConstants.platform == "win") {
627 // Handle "? searchterm" for Windows Vista start menu integration
628 for (var i = cmdLine.length - 1; i >= 0; --i) {
629 var param = cmdLine.getArgument(i);
630 if (param.match(/^\? /)) {
631 cmdLine.removeArguments(i, i);
632 cmdLine.preventDefault = true;
634 searchParam = param.substr(2);
635 doSearch(searchParam, cmdLine);
643 " --browser Open a browser window.\n" +
644 " --new-window <url> Open <url> in a new window.\n" +
645 " --new-tab <url> Open <url> in a new tab.\n" +
646 " --private-window <url> Open <url> in a new private window.\n";
647 if (AppConstants.platform == "win") {
648 info += " --preferences Open Options dialog.\n";
650 info += " --preferences Open Preferences dialog.\n";
653 " --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
655 " --window-size width[,height] Width and optionally height of screenshot.\n";
657 " --search <term> Search <term> with your default search engine.\n";
658 info += " --setDefaultBrowser Set this app as the default browser.\n";
660 " --first-startup Run post-install actions before opening a new window.\n";
661 info += " --kiosk Start the browser in kiosk mode.\n";
663 " --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
665 " --disable-pinch Disable touch-screen and touch-pad pinch gestures.\n";
669 /* nsIBrowserHandler */
672 return this.getArgs();
675 getArgs(isStartup = false) {
676 var prefb = Services.prefs;
680 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
681 return "about:privatebrowsing";
686 var overridePage = "";
687 var additionalPage = "";
688 var willRestoreSession = false;
690 // Read the old value of homepage_override.mstone before
691 // needHomepageOverride updates it, so that we can later add it to the
692 // URL if we do end up showing an overridePage. This makes it possible
693 // to have the overridePage's content vary depending on the version we're
695 let old_mstone = Services.prefs.getCharPref(
696 "browser.startup.homepage_override.mstone",
699 let old_buildId = Services.prefs.getCharPref(
700 "browser.startup.homepage_override.buildID",
703 override = needHomepageOverride(prefb);
704 if (override != OVERRIDE_NONE) {
706 case OVERRIDE_NEW_PROFILE:
708 gFirstRunProfile = true;
709 if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
712 overridePage = Services.urlFormatter.formatURLPref(
713 "startup.homepage_welcome_url"
715 additionalPage = Services.urlFormatter.formatURLPref(
716 "startup.homepage_welcome_url.additional"
718 // Turn on 'later run' pages for new profiles.
719 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
721 case OVERRIDE_NEW_MSTONE:
722 // Check whether we will restore a session. If we will, we assume
723 // that this is an "update" session. This does not take crashes
724 // into account because that requires waiting for the session file
725 // to be read. If a crash occurs after updating, before restarting,
726 // we may open the startPage in addition to restoring the session.
728 lazy.SessionStartup.isAutomaticRestoreEnabled();
730 overridePage = Services.urlFormatter.formatURLPref(
731 "startup.homepage_override_url"
733 let update = lazy.UpdateManager.readyUpdate;
736 Services.vc.compare(update.appVersion, old_mstone) > 0
738 overridePage = getPostUpdateOverridePage(update, overridePage);
739 // Send the update ping to signal that the update was successful.
740 lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
741 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
744 overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
746 case OVERRIDE_NEW_BUILD_ID:
747 if (lazy.UpdateManager.readyUpdate) {
748 // Send the update ping to signal that the update was successful.
749 lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
750 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
757 // formatURLPref might return "about:blank" if getting the pref fails
758 if (overridePage == "about:blank") {
762 // Allow showing a one-time startup override if we're not showing one
763 if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
765 // Show if we haven't passed the expiration or there's no expiration
766 const { expire, url } = JSON.parse(
767 Services.urlFormatter.formatURLPref(ONCE_PREF)
769 if (!(Date.now() > expire)) {
770 // Only set allowed urls as override pages
777 // Invalid URL, so filter out below
778 console.error("Invalid once url:", ex);
785 parsed.protocol == "https:" &&
786 // Only accept exact hostname or subdomain; without port
787 ONCE_DOMAINS.includes(
788 Services.eTLD.getBaseDomainFromHost(parsed.host)
793 // Be noisy as properly configured urls should be unchanged
794 if (overridePage != url) {
795 console.error(`Mismatched once urls: ${url}`);
799 // Invalid json pref, so ignore (and clear below)
800 console.error("Invalid once pref:", ex);
802 prefb.clearUserPref(ONCE_PREF);
806 if (!additionalPage) {
807 additionalPage = lazy.LaterRun.getURL() || "";
810 if (additionalPage && additionalPage != "about:blank") {
812 overridePage += "|" + additionalPage;
814 overridePage = additionalPage;
820 var choice = prefb.getIntPref("browser.startup.page");
821 if (choice == 1 || choice == 3) {
822 startPage = lazy.HomePage.get();
828 if (startPage == "about:blank") {
833 override == OVERRIDE_NEW_PROFILE &&
834 prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
835 // Only show the startPage if we're not restoring an update session and are
836 // not set to skip the start page on this profile
837 if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
838 return overridePage + "|" + startPage;
841 return overridePage || startPage || "about:blank";
846 getFeatures: function bch_features(cmdLine) {
847 if (this.mFeatures === null) {
852 var width = cmdLine.handleFlagWithParam("width", false);
853 var height = cmdLine.handleFlagWithParam("height", false);
854 var left = cmdLine.handleFlagWithParam("left", false);
855 var top = cmdLine.handleFlagWithParam("top", false);
858 this.mFeatures += ",width=" + width;
861 this.mFeatures += ",height=" + height;
864 this.mFeatures += ",left=" + left;
867 this.mFeatures += ",top=" + top;
872 // The global PB Service consumes this flag, so only eat it in per-window
874 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
875 this.mFeatures += ",private";
879 Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
880 !Services.wm.getMostRecentWindow("navigator:browser")
882 this.mFeatures += ",suppressanimation";
886 return this.mFeatures;
894 return gMajorUpgrade;
897 set majorUpgrade(val) {
901 get firstRunProfile() {
902 return gFirstRunProfile;
905 set firstRunProfile(val) {
906 gFirstRunProfile = val;
909 /* nsIContentHandler */
911 handleContent: function bch_handleContent(contentType, context, request) {
912 const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
915 var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
916 Ci.nsIWebNavigationInfo
918 if (!webNavInfo.isTypeSupported(contentType)) {
919 throw NS_ERROR_WONT_HANDLE_CONTENT;
922 throw NS_ERROR_WONT_HANDLE_CONTENT;
925 request.QueryInterface(Ci.nsIChannel);
926 handURIToExistingBrowser(
928 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
931 request.loadInfo.triggeringPrincipal
933 request.cancel(Cr.NS_BINDING_ABORTED);
936 /* nsICommandLineValidator */
937 validate: function bch_validate(cmdLine) {
938 var urlFlagIdx = cmdLine.findFlag("url", false);
941 cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
943 var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
945 cmdLine.length != urlFlagIdx + 2 ||
946 /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
948 throw Components.Exception("", Cr.NS_ERROR_ABORT);
953 var gBrowserContentHandler = new nsBrowserContentHandler();
955 function handURIToExistingBrowser(
962 if (!shouldLoadURI(uri)) {
966 let openInWindow = ({ browserDOMWindow }) => {
967 browserDOMWindow.openURI(
971 Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
976 // Unless using a private window is forced, open external links in private
977 // windows only if we're in perma-private mode.
979 forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
980 let navWin = lazy.BrowserWindowTracker.getTopWindow({
981 private: allowPrivate,
985 openInWindow(navWin);
989 let pending = lazy.BrowserWindowTracker.getPendingWindow({
990 private: allowPrivate,
993 // Note that we cannot make this function async as some callers rely on
994 // catching exceptions it can throw in some cases and some of those callers
995 // cannot be made async.
996 pending.then(openInWindow);
1000 // if we couldn't load it in an existing window, open a new one
1001 openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
1005 * If given URI is a file type or a protocol, record telemetry that
1006 * Firefox was invoked or launched (if `isLaunch` is truth-y). If the
1007 * file type or protocol is not registered by default, record it as
1008 * ".<other extension>" or "<other protocol>".
1011 * The URI Firefox was asked to handle.
1013 * truth-y if Firefox was launched/started rather than running and invoked.
1015 function maybeRecordToHandleTelemetry(uri, isLaunch) {
1016 let scalar = isLaunch
1017 ? "os.environment.launched_to_handle"
1018 : "os.environment.invoked_to_handle";
1020 if (uri instanceof Ci.nsIFileURL) {
1021 let extension = "." + uri.fileExtension.toLowerCase();
1022 // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
1023 // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
1024 let registeredExtensions = new Set([
1035 if (registeredExtensions.has(extension)) {
1036 Services.telemetry.keyedScalarAdd(scalar, extension, 1);
1038 Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
1041 let scheme = uri.scheme.toLowerCase();
1042 let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
1043 if (registeredSchemes.has(scheme)) {
1044 Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
1046 Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1051 export function nsDefaultCommandLineHandler() {}
1053 nsDefaultCommandLineHandler.prototype = {
1055 QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1057 _haveProfile: false,
1059 /* nsICommandLineHandler */
1060 handle: function dch_handle(cmdLine) {
1063 if (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1064 // Since the purpose of this is to record early in startup,
1065 // only record on launches, not already-running invocations.
1066 Services.telemetry.setEventRecordingEnabled("telemetry", true);
1067 Glean.fogValidation.validateEarlyEvent.record();
1070 if (AppConstants.platform == "win") {
1071 // Windows itself does disk I/O when the notification service is
1072 // initialized, so make sure that is lazy.
1074 let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
1079 // All notifications will invoke Firefox with an action. Prior to Bug 1805514,
1080 // this data was extracted from the Windows toast object directly (keyed by the
1081 // notification ID) and not passed over the command line. This is acceptable
1082 // because the data passed is chrome-controlled, but if we implement the `actions`
1083 // part of the DOM Web Notifications API, this will no longer be true:
1084 // content-controlled data might transit over the command line. This could lead
1085 // to escaping bugs and overflows. In the future, we intend to avoid any such
1086 // issue by once again extracting all such data from the Windows toast object.
1087 let notificationData = cmdLine.handleFlagWithParam(
1088 "notification-windowsAction",
1091 if (!notificationData) {
1095 let alertService = lazy.gWindowsAlertsService;
1096 if (!alertService) {
1097 console.error("Windows alert service not available.");
1101 async function handleNotification() {
1102 let { tagWasHandled } = await alertService.handleWindowsTag(tag);
1104 // If the tag was not handled via callback, then the notification was
1105 // from a prior instance of the application and we need to handle
1106 // fallback behavior.
1107 if (!tagWasHandled) {
1109 `Completing Windows notification (tag=${JSON.stringify(
1111 )}, notificationData=${notificationData})`
1114 notificationData = JSON.parse(notificationData);
1117 `Completing Windows notification (tag=${JSON.stringify(
1119 )}, failed to parse (notificationData=${notificationData})`
1124 // This is awkward: the relaunch data set by the caller is _wrapped_
1125 // into a compound object that includes additional notification data,
1126 // and everything is exchanged as strings. Unwrap and parse here.
1127 let opaqueRelaunchData = null;
1128 if (notificationData?.opaqueRelaunchData) {
1130 opaqueRelaunchData = JSON.parse(
1131 notificationData.opaqueRelaunchData
1135 `Completing Windows notification (tag=${JSON.stringify(
1137 )}, failed to parse (opaqueRelaunchData=${
1138 notificationData.opaqueRelaunchData
1144 if (notificationData?.privilegedName) {
1145 Services.telemetry.setEventRecordingEnabled(
1146 "browser.launched_to_handle",
1149 Glean.browserLaunchedToHandle.systemNotification.record({
1150 name: notificationData.privilegedName,
1154 // If we have an action in the notification data, this will be the
1155 // window to perform the action in.
1158 if (notificationData?.launchUrl && !opaqueRelaunchData) {
1159 // Unprivileged Web Notifications contain a launch URL and are handled
1160 // slightly differently than privileged notifications with actions.
1161 let uri = resolveURIInternal(cmdLine, notificationData.launchUrl);
1162 if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1163 // Try to find an existing window and load our URI into the current
1164 // tab, new tab, or new window as prefs determine.
1166 handURIToExistingBrowser(
1168 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1171 lazy.gSystemPrincipal
1177 if (shouldLoadURI(uri)) {
1178 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, [uri.spec]);
1180 } else if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1181 // No URL provided, but notification was interacted with while the
1182 // application was closed. Fall back to opening the browser without url.
1183 winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1184 await new Promise(resolve => {
1185 Services.obs.addObserver(function observe(subject) {
1186 if (subject == winForAction) {
1187 Services.obs.removeObserver(
1189 "browser-delayed-startup-finished"
1193 }, "browser-delayed-startup-finished");
1196 // Relaunch in private windows only if we're in perma-private mode.
1198 lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1199 winForAction = lazy.BrowserWindowTracker.getTopWindow({
1200 private: allowPrivate,
1204 if (opaqueRelaunchData && winForAction) {
1205 // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
1206 Services.tm.dispatchToMainThread(() => {
1207 lazy.SpecialMessageActions.handleAction(
1209 winForAction.gBrowser
1215 // Notification handling occurs asynchronously to prevent blocking the
1216 // main thread. As a result we won't have the information we need to open
1217 // a new tab in the case of notification fallback handling before
1218 // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
1219 // the browser from exiting in case early blank window is pref'd off.
1220 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1221 Services.startup.enterLastWindowClosingSurvivalArea();
1223 handleNotification()
1226 `Error handling Windows notification with tag '${tag}':`,
1231 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1232 Services.startup.exitLastWindowClosingSurvivalArea();
1241 cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1242 Services.startup.wasSilentlyStarted
1244 // If we are starting up in silent mode, don't open a window. We also need
1245 // to make sure that the application doesn't immediately exit, so stay in
1246 // a LastWindowClosingSurvivalArea until a window opens.
1247 Services.startup.enterLastWindowClosingSurvivalArea();
1248 Services.obs.addObserver(function windowOpenObserver() {
1249 Services.startup.exitLastWindowClosingSurvivalArea();
1250 Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
1251 }, "domwindowopened");
1255 if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
1256 // Handle the case where we don't have a profile selected yet (e.g. the
1257 // Profile Manager is displayed).
1258 // On Windows, we will crash if we open an url and then select a profile.
1259 // On macOS, if we open an url we don't experience a crash but a broken
1260 // window is opened.
1261 // To prevent this handle all url command line flags and set the
1262 // command line's preventDefault to true to prevent the display of the ui.
1263 // The initial command line will be retained when nsAppRunner calls
1264 // LaunchChild though urls launched after the initial launch will be lost.
1265 if (!this._haveProfile) {
1267 // This will throw when a profile has not been selected.
1268 Services.dirsvc.get("ProfD", Ci.nsIFile);
1269 this._haveProfile = true;
1271 // eslint-disable-next-line no-empty
1272 while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1273 cmdLine.preventDefault = true;
1278 // `-osint` and handling registered file types and protocols is Windows-only.
1279 let launchedWithArg_osint =
1280 AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
1281 if (launchedWithArg_osint) {
1282 cmdLine.handleFlag("osint", false);
1287 while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1288 var uri = resolveURIInternal(cmdLine, ar);
1291 if (launchedWithArg_osint) {
1292 launchedWithArg_osint = false;
1294 // We use the resolved URI here, even though it can produce
1295 // surprising results where-by `-osint -url test.pdf` resolves to
1296 // a query with search parameter "test.pdf". But that shouldn't
1297 // happen when Firefox is launched by Windows itself: files should
1298 // exist and be resolved to file URLs.
1300 cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1302 maybeRecordToHandleTelemetry(uri, isLaunch);
1310 AppConstants.platform == "win" &&
1311 cmdLine.handleFlag("to-handle-default-browser-agent", false)
1313 // The Default Browser Agent launches Firefox in response to a Windows
1314 // native notification, but it does so in a non-standard manner.
1315 Services.telemetry.setEventRecordingEnabled(
1316 "browser.launched_to_handle",
1319 Glean.browserLaunchedToHandle.systemNotification.record({
1320 name: "default-browser-agent",
1323 let thanksURI = Services.io.newURI(
1324 Services.urlFormatter.formatURLPref(
1325 "browser.shell.defaultBrowserAgent.thanksURL"
1328 urilist.push(thanksURI);
1331 if (cmdLine.findFlag("screenshot", true) != -1) {
1332 lazy.HeadlessShell.handleCmdLineArgs(
1334 urilist.filter(shouldLoadURI).map(u => u.spec)
1339 for (let i = 0; i < cmdLine.length; ++i) {
1340 var curarg = cmdLine.getArgument(i);
1341 if (curarg.match(/^-/)) {
1342 console.error("Warning: unrecognized command line flag", curarg);
1343 // To emulate the pre-nsICommandLine behavior, we ignore
1344 // the argument after an unrecognized flag.
1348 urilist.push(resolveURIInternal(cmdLine, curarg));
1351 `Error opening URI ${curarg} from the command line:`,
1358 if (urilist.length) {
1360 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1363 // Try to find an existing window and load our URI into the
1364 // current tab, new tab, or new window as prefs determine.
1366 handURIToExistingBrowser(
1368 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1371 lazy.gSystemPrincipal
1377 var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
1378 if (URLlist.length) {
1379 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
1381 } else if (!cmdLine.preventDefault) {
1383 AppConstants.platform == "win" &&
1384 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1385 lazy.WindowsUIUtils.inTabletMode
1387 // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1388 let win = lazy.BrowserWindowTracker.getTopWindow();
1394 openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1396 // Need a better solution in the future to avoid opening the blank window
1397 // when command line parameters say we are not going to show a browser
1398 // window, but for now the blank window getting closed quickly (and
1399 // causing only a slight flicker) is better than leaving it open.
1400 let win = Services.wm.getMostRecentWindow("navigator:blank");