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 validateFirefoxProtocol(aCmdLine, launchedWithArg_osint) {
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("-")) {
74 // `-osint` and handling registered file types and protocols is Windows-only.
75 return AppConstants.platform != "win" || launchedWithArg_osint;
78 function resolveURIInternal(
81 launchedWithArg_osint = false
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)) {
93 "Invalid use of Firefox-bridge and Firefox-private-bridge protocols."
96 aArgument = aArgument.substring(protocolWithColon.length);
99 !aArgument.startsWith("http://") &&
100 !aArgument.startsWith("https://")
103 "Firefox-bridge and Firefox-private-bridge protocols can only be used in conjunction with http and https urls."
107 principal = Services.scriptSecurityManager.createNullPrincipal({});
108 Services.telemetry.keyedScalarAdd(
109 "os.environment.launched_to_handle",
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(
125 uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
127 return { uri: prefURI, principal };
131 if (uri.file.exists()) {
132 return { uri, principal };
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.
142 uri = Services.uriFixup.getFixupURIInfo(aArgument).preferredURI;
147 return { uri, principal };
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.
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).
180 function needHomepageOverride(updateMilestones = true) {
181 var savedmstone = Services.prefs.getCharPref(
182 "browser.startup.homepage_override.mstone",
186 if (savedmstone == "ignore") {
187 return OVERRIDE_NONE;
190 var mstone = Services.appinfo.platformVersion;
192 var savedBuildID = Services.prefs.getCharPref(
193 "browser.startup.homepage_override.buildID",
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.
205 Services.prefs.setBoolPref("browser.rights.3.shown", true);
207 // Remember that we saw a major version change.
208 gMajorUpgrade = true;
211 if (updateMilestones) {
212 Services.prefs.setCharPref(
213 "browser.startup.homepage_override.mstone",
216 Services.prefs.setCharPref(
217 "browser.startup.homepage_override.buildID",
221 return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE;
224 if (buildID != savedBuildID) {
225 if (updateMilestones) {
226 Services.prefs.setCharPref(
227 "browser.startup.homepage_override.buildID",
231 return OVERRIDE_NEW_BUILD_ID;
234 return OVERRIDE_NONE;
238 * Gets the override page for the first run after the application has been
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.
248 function getPostUpdateOverridePage(
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.
258 return defaultOverridePage;
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")) {
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;
273 if (nimbusOverridePage) {
274 return nimbusOverridePage;
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
286 * The nsICommandLine object given to nsICommandLineHandler's handle
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
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.
307 function openBrowserWindow(
315 cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
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.
325 !triggeringPrincipal ||
326 !triggeringPrincipal.equals(lazy.gSystemPrincipal)
329 "Can't open multiple URLs with something other than system principal."
332 // Passing an nsIArray for the url disables the "|"-splitting behavior.
333 let uriArray = Cc["@mozilla.org/array;1"].createInstance(
336 urlOrUrlList.forEach(function (uri) {
337 var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
341 uriArray.appendElement(sstring);
345 let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
346 Ci.nsIWritablePropertyBag2
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.
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
368 let win = Services.wm.getMostRecentWindow("navigator:blank");
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");
375 win.docShell.QueryInterface(
377 ).usePrivateBrowsing = true;
380 AppConstants.platform == "win" &&
381 lazy.NimbusFeatures.majorRelease2022.getVariable(
382 "feltPrivacyWindowSeparation"
385 lazy.WinTaskbar.setGroupIdForWindow(
387 lazy.WinTaskbar.defaultPrivateGroupId
389 lazy.WindowsUIUtils.setWindowIconFromExe(
391 Services.dirsvc.get("XREExeF", Ci.nsIFile).path,
392 // This corresponds to the definitions in
393 // nsNativeAppSupportWin.h
394 PRIVATE_BROWSING_ICON_INDEX
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);
409 // We can't provide arguments to openWindow as a JS array.
411 // If we have a single string guaranteed to not contain '|' we can simply
412 // wrap it in an nsISupportsString object.
414 args = Cc["@mozilla.org/supports-string;1"].createInstance(
419 // Otherwise, pass an nsIArray.
420 if (args.length > 1) {
421 let string = Cc["@mozilla.org/supports-string;1"].createInstance(
424 string.data = args[0];
427 let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
429 array.appendElement(a);
434 return lazy.BrowserWindowTracker.openWindow({
436 features: gBrowserContentHandler.getFeatures(cmdLine),
437 private: forcePrivate,
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(
457 "browser-delayed-startup-finished"
461 }, "browser-delayed-startup-finished");
464 win.BrowserSearch.loadSearchFromCommandLine(
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;
477 return gBrowserContentHandler;
480 nsBrowserContentHandler.prototype = {
482 QueryInterface: ChromeUtils.generateQI([
483 "nsICommandLineHandler",
486 "nsICommandLineValidator",
489 /* nsICommandLineHandler */
490 handle: function bch_handle(cmdLine) {
492 cmdLine.handleFlag("kiosk", false) ||
493 cmdLine.handleFlagWithParam("kiosk-monitor", false)
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");
510 if (cmdLine.handleFlag("browser", false)) {
511 openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
512 cmdLine.preventDefault = true;
517 while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
518 let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
519 if (!shouldLoadURI(uri)) {
522 openBrowserWindow(cmdLine, principal, uri.spec);
523 cmdLine.preventDefault = true;
530 while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
531 let { uri, principal } = resolveURIInternal(cmdLine, uriparam);
532 handURIToExistingBrowser(
534 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
539 cmdLine.preventDefault = true;
545 var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
547 // Handle old preference dialog URLs.
549 chromeParam == "chrome://browser/content/pref/pref.xul" ||
550 chromeParam == "chrome://browser/content/preferences/preferences.xul"
552 openPreferences(cmdLine);
553 cmdLine.preventDefault = true;
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;
562 return localSchemes.has(uri.scheme);
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(
572 argArray.appendElement(null);
573 Services.ww.openWindow(
580 cmdLine.preventDefault = true;
582 dump("*** Preventing load of web URI as chrome\n");
584 " If you're trying to load a webpage, do not pass --chrome.\n"
592 if (cmdLine.handleFlag("preferences", false)) {
593 openPreferences(cmdLine);
594 cmdLine.preventDefault = true;
596 if (cmdLine.handleFlag("silent", false)) {
597 cmdLine.preventDefault = true;
601 var privateWindowParam = cmdLine.handleFlagWithParam(
605 // Check for Firefox private browsing protocol handler here.
607 let urlFlagIdx = cmdLine.findFlag("url", false);
608 if (urlFlagIdx > -1 && cmdLine.length > 1) {
609 url = cmdLine.getArgument(urlFlagIdx + 1);
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;
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;
623 uri: Services.io.newURI("about:privatebrowsing"),
624 principal: lazy.gSystemPrincipal,
626 } else if (url?.startsWith("firefox-private-bridge:")) {
627 cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
628 resolvedInfo = resolveURIInternal(
631 launchedWithArg_osint
634 resolvedInfo = resolveURIInternal(
637 launchedWithArg_osint
640 handURIToExistingBrowser(
642 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
645 resolvedInfo.principal
647 cmdLine.preventDefault = true;
650 if (e.result != Cr.NS_ERROR_INVALID_ARG) {
653 // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
654 if (cmdLine.handleFlag("private-window", false)) {
657 lazy.gSystemPrincipal,
658 "about:privatebrowsing",
660 lazy.PrivateBrowsingUtils.enabled
662 cmdLine.preventDefault = true;
666 var searchParam = cmdLine.handleFlagWithParam("search", false);
668 doSearch(searchParam, cmdLine);
669 cmdLine.preventDefault = true;
672 // The global PB Service consumes this flag, so only eat it in per-window
675 cmdLine.handleFlag("private", false) &&
676 lazy.PrivateBrowsingUtils.enabled
678 lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
679 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
680 let win = Services.wm.getMostRecentWindow("navigator:blank");
682 win.docShell.QueryInterface(
684 ).usePrivateBrowsing = true;
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
693 lazy.ShellService.setDefaultBrowser(true).catch(e => {
694 console.error("setDefaultBrowser failed:", e);
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
703 let override = needHomepageOverride(false /* updateMilestones */);
704 lazy.FirstStartup.init(override == OVERRIDE_NEW_PROFILE /* newProfile */);
707 var fileParam = cmdLine.handleFlagWithParam("file", false);
709 var file = cmdLine.resolveFile(fileParam);
710 var fileURI = Services.io.newFileURI(file);
711 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
712 cmdLine.preventDefault = true;
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);
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";
739 info += " --preferences Open Preferences dialog.\n";
742 " --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
744 " --window-size width[,height] Width and optionally height of screenshot.\n";
746 " --search <term> Search <term> with your default search engine.\n";
747 info += " --setDefaultBrowser Set this app as the default browser.\n";
749 " --first-startup Run post-install actions before opening a new window.\n";
750 info += " --kiosk Start the browser in kiosk mode.\n";
752 " --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
754 " --disable-pinch Disable touch-screen and touch-pad pinch gestures.\n";
758 /* nsIBrowserHandler */
761 return this.getArgs();
764 getArgs(isStartup = false) {
765 var prefb = Services.prefs;
769 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
770 return "about:privatebrowsing";
775 var overridePage = "";
776 var additionalPage = "";
777 var willRestoreSession = false;
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
784 let old_mstone = Services.prefs.getCharPref(
785 "browser.startup.homepage_override.mstone",
788 let old_buildId = Services.prefs.getCharPref(
789 "browser.startup.homepage_override.buildID",
792 override = needHomepageOverride();
793 if (override != OVERRIDE_NONE) {
795 case OVERRIDE_NEW_PROFILE:
797 gFirstRunProfile = true;
798 if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
801 overridePage = Services.urlFormatter.formatURLPref(
802 "startup.homepage_welcome_url"
804 additionalPage = Services.urlFormatter.formatURLPref(
805 "startup.homepage_welcome_url.additional"
807 // Turn on 'later run' pages for new profiles.
808 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
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.
817 lazy.SessionStartup.isAutomaticRestoreEnabled();
819 overridePage = Services.urlFormatter.formatURLPref(
820 "startup.homepage_override_url"
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"
833 const maxVersion = Services.prefs.getCharPref(
834 "startup.homepage_override_nimbus_maxVersion",
839 // Update version should be less than or equal to maxVersion set by
843 Services.vc.compare(update.appVersion, maxVersion) <= 0
846 let uri = Services.io.newURI(nimbusOverrideUrl);
847 // Only allow https://www.mozilla.org and https://www.mozilla.com
849 uri.scheme === "https" &&
850 ["www.mozilla.org", "www.mozilla.com"].includes(uri.host)
852 nimbusWNP = uri.spec;
854 throw new Error("Bad URL");
857 console.error("Invalid WNP URL: ", nimbusOverrideUrl);
863 Services.vc.compare(update.appVersion, old_mstone) > 0
865 overridePage = getPostUpdateOverridePage(
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.
881 let nimbusWNPFeature = lazy.NimbusFeatures.whatsNewPage;
884 .then(() => nimbusWNPFeature.recordExposureEvent());
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);
892 overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
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);
906 // formatURLPref might return "about:blank" if getting the pref fails
907 if (overridePage == "about:blank") {
911 // Allow showing a one-time startup override if we're not showing one
912 if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
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)
918 if (!(Date.now() > expire)) {
919 // Only set allowed urls as override pages
926 // Invalid URL, so filter out below
927 console.error("Invalid once url:", ex);
934 parsed.protocol == "https:" &&
935 // Only accept exact hostname or subdomain; without port
936 ONCE_DOMAINS.includes(
937 Services.eTLD.getBaseDomainFromHost(parsed.host)
942 // Be noisy as properly configured urls should be unchanged
943 if (overridePage != url) {
944 console.error(`Mismatched once urls: ${url}`);
948 // Invalid json pref, so ignore (and clear below)
949 console.error("Invalid once pref:", ex);
951 prefb.clearUserPref(ONCE_PREF);
955 if (!additionalPage) {
956 additionalPage = lazy.LaterRun.getURL() || "";
959 if (additionalPage && additionalPage != "about:blank") {
961 overridePage += "|" + additionalPage;
963 overridePage = additionalPage;
969 var choice = prefb.getIntPref("browser.startup.page");
970 if (choice == 1 || choice == 3) {
971 startPage = lazy.HomePage.get();
977 if (startPage == "about:blank") {
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;
990 return overridePage || startPage || "about:blank";
995 getFeatures: function bch_features(cmdLine) {
996 if (this.mFeatures === null) {
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);
1007 this.mFeatures += ",width=" + width;
1010 this.mFeatures += ",height=" + height;
1013 this.mFeatures += ",left=" + left;
1016 this.mFeatures += ",top=" + top;
1021 // The global PB Service consumes this flag, so only eat it in per-window
1023 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
1024 this.mFeatures += ",private";
1028 Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
1029 !Services.wm.getMostRecentWindow("navigator:browser")
1031 this.mFeatures += ",suppressanimation";
1035 return this.mFeatures;
1042 get majorUpgrade() {
1043 return gMajorUpgrade;
1046 set majorUpgrade(val) {
1047 gMajorUpgrade = val;
1050 get firstRunProfile() {
1051 return gFirstRunProfile;
1054 set firstRunProfile(val) {
1055 gFirstRunProfile = val;
1058 /* nsIContentHandler */
1060 handleContent: function bch_handleContent(contentType, context, request) {
1061 const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
1064 var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
1065 Ci.nsIWebNavigationInfo
1067 if (!webNavInfo.isTypeSupported(contentType)) {
1068 throw NS_ERROR_WONT_HANDLE_CONTENT;
1071 throw NS_ERROR_WONT_HANDLE_CONTENT;
1074 request.QueryInterface(Ci.nsIChannel);
1075 handURIToExistingBrowser(
1077 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1080 request.loadInfo.triggeringPrincipal
1082 request.cancel(Cr.NS_BINDING_ABORTED);
1085 /* nsICommandLineValidator */
1086 validate: function bch_validate(cmdLine) {
1087 var urlFlagIdx = cmdLine.findFlag("url", false);
1090 cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
1092 var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
1094 cmdLine.length != urlFlagIdx + 2 ||
1095 /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
1097 throw Components.Exception("", Cr.NS_ERROR_ABORT);
1102 var gBrowserContentHandler = new nsBrowserContentHandler();
1104 function handURIToExistingBrowser(
1111 if (!shouldLoadURI(uri)) {
1115 let openInWindow = ({ browserDOMWindow }) => {
1116 browserDOMWindow.openURI(
1120 Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
1125 // Unless using a private window is forced, open external links in private
1126 // windows only if we're in perma-private mode.
1128 forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1129 let navWin = lazy.BrowserWindowTracker.getTopWindow({
1130 private: allowPrivate,
1134 openInWindow(navWin);
1138 let pending = lazy.BrowserWindowTracker.getPendingWindow({
1139 private: allowPrivate,
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);
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>".
1160 * The URI Firefox was asked to handle.
1162 * truth-y if Firefox was launched/started rather than running and invoked.
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([
1184 if (registeredExtensions.has(extension)) {
1185 Services.telemetry.keyedScalarAdd(scalar, extension, 1);
1187 Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
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);
1195 Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1200 export function nsDefaultCommandLineHandler() {}
1202 nsDefaultCommandLineHandler.prototype = {
1204 QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1206 _haveProfile: false,
1208 /* nsICommandLineHandler */
1209 handle: function dch_handle(cmdLine) {
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.
1217 let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
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",
1234 if (!notificationData) {
1238 let alertService = lazy.gWindowsAlertsService;
1239 if (!alertService) {
1240 console.error("Windows alert service not available.");
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) {
1252 `Completing Windows notification (tag=${JSON.stringify(
1254 )}, notificationData=${notificationData})`
1257 notificationData = JSON.parse(notificationData);
1260 `Completing Windows notification (tag=${JSON.stringify(
1262 )}, failed to parse (notificationData=${notificationData})`
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) {
1273 opaqueRelaunchData = JSON.parse(
1274 notificationData.opaqueRelaunchData
1278 `Completing Windows notification (tag=${JSON.stringify(
1280 )}, failed to parse (opaqueRelaunchData=${
1281 notificationData.opaqueRelaunchData
1287 if (notificationData?.privilegedName) {
1288 Services.telemetry.setEventRecordingEnabled(
1289 "browser.launched_to_handle",
1292 Glean.browserLaunchedToHandle.systemNotification.record({
1293 name: notificationData.privilegedName,
1297 // If we have an action in the notification data, this will be the
1298 // window to perform the action in.
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(
1306 notificationData.launchUrl
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.
1312 handURIToExistingBrowser(
1314 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1323 if (shouldLoadURI(uri)) {
1324 openBrowserWindow(cmdLine, principal, [uri.spec]);
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(
1335 "browser-delayed-startup-finished"
1339 }, "browser-delayed-startup-finished");
1342 // Relaunch in private windows only if we're in perma-private mode.
1344 lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1345 winForAction = lazy.BrowserWindowTracker.getTopWindow({
1346 private: allowPrivate,
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(
1355 winForAction.gBrowser
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();
1369 handleNotification()
1372 `Error handling Windows notification with tag '${tag}':`,
1377 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1378 Services.startup.exitLastWindowClosingSurvivalArea();
1387 cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1388 Services.startup.wasSilentlyStarted
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");
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) {
1413 // This will throw when a profile has not been selected.
1414 Services.dirsvc.get("ProfD", Ci.nsIFile);
1415 this._haveProfile = true;
1417 // eslint-disable-next-line no-empty
1418 while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1419 cmdLine.preventDefault = true;
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);
1433 while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1434 let { uri, principal } = resolveURIInternal(
1437 launchedWithArg_osint
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.
1451 cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1453 maybeRecordToHandleTelemetry(uri, isLaunch);
1461 AppConstants.platform == "win" &&
1462 cmdLine.handleFlag("to-handle-default-browser-agent", false)
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",
1470 Glean.browserLaunchedToHandle.systemNotification.record({
1471 name: "default-browser-agent",
1474 let thanksURI = Services.io.newURI(
1475 Services.urlFormatter.formatURLPref(
1476 "browser.shell.defaultBrowserAgent.thanksURL"
1479 urilist.push(thanksURI);
1480 principalList.push(lazy.gSystemPrincipal);
1483 if (cmdLine.findFlag("screenshot", true) != -1) {
1484 // Shouldn't have to push principal here with the screenshot flag
1485 lazy.HeadlessShell.handleCmdLineArgs(
1487 urilist.filter(shouldLoadURI).map(u => u.spec)
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.
1501 let { uri, principal } = resolveURIInternal(cmdLine, curarg);
1503 principalList.push(principal);
1506 `Error opening URI ${curarg} from the command line:`,
1513 if (urilist.length) {
1515 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1518 // Try to find an existing window and load our URI into the
1519 // current tab, new tab, or new window as prefs determine.
1521 handURIToExistingBrowser(
1523 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1526 principalList[0] ?? lazy.gSystemPrincipal
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);
1540 } else if (!cmdLine.preventDefault) {
1542 AppConstants.platform == "win" &&
1543 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1544 lazy.WindowsUIUtils.inTabletMode
1546 // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1547 let win = lazy.BrowserWindowTracker.getTopWindow();
1553 openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
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");