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;
445 // In the past, when an instance was not already running, the -remote
446 // option returned an error code. Any script or application invoking the
447 // -remote option is expected to be handling this case, otherwise they
448 // wouldn't be doing anything when there is no Firefox already running.
449 // Making the -remote option always return an error code makes those
450 // scripts or applications handle the situation as if Firefox was not
452 if (cmdLine.handleFlag("remote", true)) {
453 throw Components.Exception("", Cr.NS_ERROR_ABORT);
458 while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
459 let uri = resolveURIInternal(cmdLine, uriparam);
460 if (!shouldLoadURI(uri)) {
463 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, uri.spec);
464 cmdLine.preventDefault = true;
471 while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
472 let uri = resolveURIInternal(cmdLine, uriparam);
473 handURIToExistingBrowser(
475 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
478 lazy.gSystemPrincipal
480 cmdLine.preventDefault = true;
486 var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
488 // Handle old preference dialog URLs.
490 chromeParam == "chrome://browser/content/pref/pref.xul" ||
491 chromeParam == "chrome://browser/content/preferences/preferences.xul"
493 openPreferences(cmdLine);
494 cmdLine.preventDefault = true;
497 let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
498 let isLocal = uri => {
499 let localSchemes = new Set(["chrome", "file", "resource"]);
500 if (uri instanceof Ci.nsINestedURI) {
501 uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
503 return localSchemes.has(uri.scheme);
505 if (isLocal(resolvedURI)) {
506 // If the URI is local, we are sure it won't wrongly inherit chrome privs
507 let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
508 // Provide 1 null argument, as openWindow has a different behavior
509 // when the arg count is 0.
510 let argArray = Cc["@mozilla.org/array;1"].createInstance(
513 argArray.appendElement(null);
514 Services.ww.openWindow(
521 cmdLine.preventDefault = true;
523 dump("*** Preventing load of web URI as chrome\n");
525 " If you're trying to load a webpage, do not pass --chrome.\n"
533 if (cmdLine.handleFlag("preferences", false)) {
534 openPreferences(cmdLine);
535 cmdLine.preventDefault = true;
537 if (cmdLine.handleFlag("silent", false)) {
538 cmdLine.preventDefault = true;
542 var privateWindowParam = cmdLine.handleFlagWithParam(
546 // Check for Firefox private browsing protocol handler here.
548 let urlFlagIdx = cmdLine.findFlag("url", false);
549 if (urlFlagIdx > -1 && cmdLine.length > 1) {
550 url = cmdLine.getArgument(urlFlagIdx + 1);
552 if (privateWindowParam || url?.startsWith("firefox-private:")) {
553 let forcePrivate = true;
555 if (!lazy.PrivateBrowsingUtils.enabled) {
556 // Load about:privatebrowsing in a normal tab, which will display an error indicating
557 // access to private browsing has been disabled.
558 forcePrivate = false;
559 resolvedURI = Services.io.newURI("about:privatebrowsing");
560 } else if (url?.startsWith("firefox-private:")) {
561 // We can safely remove the flag and parameter now.
562 cmdLine.removeArguments(urlFlagIdx, urlFlagIdx + 1);
563 resolvedURI = resolveURIInternal(cmdLine, url);
565 resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
567 handURIToExistingBrowser(
569 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
572 lazy.gSystemPrincipal
574 cmdLine.preventDefault = true;
577 if (e.result != Cr.NS_ERROR_INVALID_ARG) {
580 // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
581 if (cmdLine.handleFlag("private-window", false)) {
584 lazy.gSystemPrincipal,
585 "about:privatebrowsing",
587 lazy.PrivateBrowsingUtils.enabled
589 cmdLine.preventDefault = true;
593 var searchParam = cmdLine.handleFlagWithParam("search", false);
595 doSearch(searchParam, cmdLine);
596 cmdLine.preventDefault = true;
599 // The global PB Service consumes this flag, so only eat it in per-window
602 cmdLine.handleFlag("private", false) &&
603 lazy.PrivateBrowsingUtils.enabled
605 lazy.PrivateBrowsingUtils.enterTemporaryAutoStartMode();
606 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
607 let win = Services.wm.getMostRecentWindow("navigator:blank");
609 win.docShell.QueryInterface(
611 ).usePrivateBrowsing = true;
615 if (cmdLine.handleFlag("setDefaultBrowser", false)) {
616 // Note that setDefaultBrowser is an async function, but "handle" (the method being executed)
617 // is an implementation of an interface method and changing it to be async would be complicated
618 // and ultimately nothing here needs the result of setDefaultBrowser, so we do not bother doing
620 lazy.ShellService.setDefaultBrowser(true).catch(e => {
621 console.error("setDefaultBrowser failed:", e);
625 if (cmdLine.handleFlag("first-startup", false)) {
626 lazy.FirstStartup.init();
629 var fileParam = cmdLine.handleFlagWithParam("file", false);
631 var file = cmdLine.resolveFile(fileParam);
632 var fileURI = Services.io.newFileURI(file);
633 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, fileURI.spec);
634 cmdLine.preventDefault = true;
637 if (AppConstants.platform == "win") {
638 // Handle "? searchterm" for Windows Vista start menu integration
639 for (var i = cmdLine.length - 1; i >= 0; --i) {
640 var param = cmdLine.getArgument(i);
641 if (param.match(/^\? /)) {
642 cmdLine.removeArguments(i, i);
643 cmdLine.preventDefault = true;
645 searchParam = param.substr(2);
646 doSearch(searchParam, cmdLine);
654 " --browser Open a browser window.\n" +
655 " --new-window <url> Open <url> in a new window.\n" +
656 " --new-tab <url> Open <url> in a new tab.\n" +
657 " --private-window <url> Open <url> in a new private window.\n";
658 if (AppConstants.platform == "win") {
659 info += " --preferences Open Options dialog.\n";
661 info += " --preferences Open Preferences dialog.\n";
664 " --screenshot [<path>] Save screenshot to <path> or in working directory.\n";
666 " --window-size width[,height] Width and optionally height of screenshot.\n";
668 " --search <term> Search <term> with your default search engine.\n";
669 info += " --setDefaultBrowser Set this app as the default browser.\n";
671 " --first-startup Run post-install actions before opening a new window.\n";
672 info += " --kiosk Start the browser in kiosk mode.\n";
674 " --kiosk-monitor <num> Place kiosk browser window on given monitor.\n";
676 " --disable-pinch Disable touch-screen and touch-pad pinch gestures.\n";
680 /* nsIBrowserHandler */
683 return this.getArgs();
686 getArgs(isStartup = false) {
687 var prefb = Services.prefs;
691 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
692 return "about:privatebrowsing";
697 var overridePage = "";
698 var additionalPage = "";
699 var willRestoreSession = false;
701 // Read the old value of homepage_override.mstone before
702 // needHomepageOverride updates it, so that we can later add it to the
703 // URL if we do end up showing an overridePage. This makes it possible
704 // to have the overridePage's content vary depending on the version we're
706 let old_mstone = Services.prefs.getCharPref(
707 "browser.startup.homepage_override.mstone",
710 let old_buildId = Services.prefs.getCharPref(
711 "browser.startup.homepage_override.buildID",
714 override = needHomepageOverride(prefb);
715 if (override != OVERRIDE_NONE) {
717 case OVERRIDE_NEW_PROFILE:
719 gFirstRunProfile = true;
720 if (lazy.NimbusFeatures.aboutwelcome.getVariable("showModal")) {
723 overridePage = Services.urlFormatter.formatURLPref(
724 "startup.homepage_welcome_url"
726 additionalPage = Services.urlFormatter.formatURLPref(
727 "startup.homepage_welcome_url.additional"
729 // Turn on 'later run' pages for new profiles.
730 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_NEW_PROFILE);
732 case OVERRIDE_NEW_MSTONE:
733 // Check whether we will restore a session. If we will, we assume
734 // that this is an "update" session. This does not take crashes
735 // into account because that requires waiting for the session file
736 // to be read. If a crash occurs after updating, before restarting,
737 // we may open the startPage in addition to restoring the session.
739 lazy.SessionStartup.isAutomaticRestoreEnabled();
741 overridePage = Services.urlFormatter.formatURLPref(
742 "startup.homepage_override_url"
744 let update = lazy.UpdateManager.readyUpdate;
747 Services.vc.compare(update.appVersion, old_mstone) > 0
749 overridePage = getPostUpdateOverridePage(update, overridePage);
750 // Send the update ping to signal that the update was successful.
751 lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
752 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
755 overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
757 case OVERRIDE_NEW_BUILD_ID:
758 if (lazy.UpdateManager.readyUpdate) {
759 // Send the update ping to signal that the update was successful.
760 lazy.UpdatePing.handleUpdateSuccess(old_mstone, old_buildId);
761 lazy.LaterRun.enable(lazy.LaterRun.ENABLE_REASON_UPDATE_APPLIED);
768 // formatURLPref might return "about:blank" if getting the pref fails
769 if (overridePage == "about:blank") {
773 // Allow showing a one-time startup override if we're not showing one
774 if (isStartup && overridePage == "" && prefb.prefHasUserValue(ONCE_PREF)) {
776 // Show if we haven't passed the expiration or there's no expiration
777 const { expire, url } = JSON.parse(
778 Services.urlFormatter.formatURLPref(ONCE_PREF)
780 if (!(Date.now() > expire)) {
781 // Only set allowed urls as override pages
788 // Invalid URL, so filter out below
789 console.error("Invalid once url:", ex);
796 parsed.protocol == "https:" &&
797 // Only accept exact hostname or subdomain; without port
798 ONCE_DOMAINS.includes(
799 Services.eTLD.getBaseDomainFromHost(parsed.host)
804 // Be noisy as properly configured urls should be unchanged
805 if (overridePage != url) {
806 console.error(`Mismatched once urls: ${url}`);
810 // Invalid json pref, so ignore (and clear below)
811 console.error("Invalid once pref:", ex);
813 prefb.clearUserPref(ONCE_PREF);
817 if (!additionalPage) {
818 additionalPage = lazy.LaterRun.getURL() || "";
821 if (additionalPage && additionalPage != "about:blank") {
823 overridePage += "|" + additionalPage;
825 overridePage = additionalPage;
831 var choice = prefb.getIntPref("browser.startup.page");
832 if (choice == 1 || choice == 3) {
833 startPage = lazy.HomePage.get();
839 if (startPage == "about:blank") {
844 override == OVERRIDE_NEW_PROFILE &&
845 prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
846 // Only show the startPage if we're not restoring an update session and are
847 // not set to skip the start page on this profile
848 if (overridePage && startPage && !willRestoreSession && !skipStartPage) {
849 return overridePage + "|" + startPage;
852 return overridePage || startPage || "about:blank";
857 getFeatures: function bch_features(cmdLine) {
858 if (this.mFeatures === null) {
863 var width = cmdLine.handleFlagWithParam("width", false);
864 var height = cmdLine.handleFlagWithParam("height", false);
865 var left = cmdLine.handleFlagWithParam("left", false);
866 var top = cmdLine.handleFlagWithParam("top", false);
869 this.mFeatures += ",width=" + width;
872 this.mFeatures += ",height=" + height;
875 this.mFeatures += ",left=" + left;
878 this.mFeatures += ",top=" + top;
883 // The global PB Service consumes this flag, so only eat it in per-window
885 if (lazy.PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
886 this.mFeatures += ",private";
890 Services.prefs.getBoolPref("browser.suppress_first_window_animation") &&
891 !Services.wm.getMostRecentWindow("navigator:browser")
893 this.mFeatures += ",suppressanimation";
897 return this.mFeatures;
905 return gMajorUpgrade;
908 set majorUpgrade(val) {
912 get firstRunProfile() {
913 return gFirstRunProfile;
916 set firstRunProfile(val) {
917 gFirstRunProfile = val;
920 /* nsIContentHandler */
922 handleContent: function bch_handleContent(contentType, context, request) {
923 const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
926 var webNavInfo = Cc["@mozilla.org/webnavigation-info;1"].getService(
927 Ci.nsIWebNavigationInfo
929 if (!webNavInfo.isTypeSupported(contentType)) {
930 throw NS_ERROR_WONT_HANDLE_CONTENT;
933 throw NS_ERROR_WONT_HANDLE_CONTENT;
936 request.QueryInterface(Ci.nsIChannel);
937 handURIToExistingBrowser(
939 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
942 request.loadInfo.triggeringPrincipal
944 request.cancel(Cr.NS_BINDING_ABORTED);
947 /* nsICommandLineValidator */
948 validate: function bch_validate(cmdLine) {
949 var urlFlagIdx = cmdLine.findFlag("url", false);
952 cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
954 var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
956 cmdLine.length != urlFlagIdx + 2 ||
957 /firefoxurl(-[a-f0-9]+)?:/i.test(urlParam)
959 throw Components.Exception("", Cr.NS_ERROR_ABORT);
964 var gBrowserContentHandler = new nsBrowserContentHandler();
966 function handURIToExistingBrowser(
973 if (!shouldLoadURI(uri)) {
977 let openInWindow = ({ browserDOMWindow }) => {
978 browserDOMWindow.openURI(
982 Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL,
987 // Unless using a private window is forced, open external links in private
988 // windows only if we're in perma-private mode.
990 forcePrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
991 let navWin = lazy.BrowserWindowTracker.getTopWindow({
992 private: allowPrivate,
996 openInWindow(navWin);
1000 let pending = lazy.BrowserWindowTracker.getPendingWindow({
1001 private: allowPrivate,
1004 // Note that we cannot make this function async as some callers rely on
1005 // catching exceptions it can throw in some cases and some of those callers
1006 // cannot be made async.
1007 pending.then(openInWindow);
1011 // if we couldn't load it in an existing window, open a new one
1012 openBrowserWindow(cmdLine, triggeringPrincipal, uri.spec, null, forcePrivate);
1016 * If given URI is a file type or a protocol, record telemetry that
1017 * Firefox was invoked or launched (if `isLaunch` is truth-y). If the
1018 * file type or protocol is not registered by default, record it as
1019 * ".<other extension>" or "<other protocol>".
1022 * The URI Firefox was asked to handle.
1024 * truth-y if Firefox was launched/started rather than running and invoked.
1026 function maybeRecordToHandleTelemetry(uri, isLaunch) {
1027 let scalar = isLaunch
1028 ? "os.environment.launched_to_handle"
1029 : "os.environment.invoked_to_handle";
1031 if (uri instanceof Ci.nsIFileURL) {
1032 let extension = "." + uri.fileExtension.toLowerCase();
1033 // Keep synchronized with https://searchfox.org/mozilla-central/source/browser/installer/windows/nsis/shared.nsh
1034 // and https://searchfox.org/mozilla-central/source/browser/installer/windows/msix/AppxManifest.xml.in.
1035 let registeredExtensions = new Set([
1046 if (registeredExtensions.has(extension)) {
1047 Services.telemetry.keyedScalarAdd(scalar, extension, 1);
1049 Services.telemetry.keyedScalarAdd(scalar, ".<other extension>", 1);
1052 let scheme = uri.scheme.toLowerCase();
1053 let registeredSchemes = new Set(["about", "http", "https", "mailto"]);
1054 if (registeredSchemes.has(scheme)) {
1055 Services.telemetry.keyedScalarAdd(scalar, scheme, 1);
1057 Services.telemetry.keyedScalarAdd(scalar, "<other protocol>", 1);
1062 export function nsDefaultCommandLineHandler() {}
1064 nsDefaultCommandLineHandler.prototype = {
1066 QueryInterface: ChromeUtils.generateQI(["nsICommandLineHandler"]),
1068 _haveProfile: false,
1070 /* nsICommandLineHandler */
1071 handle: function dch_handle(cmdLine) {
1074 if (cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1075 // Since the purpose of this is to record early in startup,
1076 // only record on launches, not already-running invocations.
1077 Services.telemetry.setEventRecordingEnabled("telemetry", true);
1078 Glean.fogValidation.validateEarlyEvent.record();
1081 if (AppConstants.platform == "win") {
1082 // Windows itself does disk I/O when the notification service is
1083 // initialized, so make sure that is lazy.
1085 let tag = cmdLine.handleFlagWithParam("notification-windowsTag", false);
1090 // All notifications will invoke Firefox with an action. Prior to Bug 1805514,
1091 // this data was extracted from the Windows toast object directly (keyed by the
1092 // notification ID) and not passed over the command line. This is acceptable
1093 // because the data passed is chrome-controlled, but if we implement the `actions`
1094 // part of the DOM Web Notifications API, this will no longer be true:
1095 // content-controlled data might transit over the command line. This could lead
1096 // to escaping bugs and overflows. In the future, we intend to avoid any such
1097 // issue by once again extracting all such data from the Windows toast object.
1098 let notificationData = cmdLine.handleFlagWithParam(
1099 "notification-windowsAction",
1102 if (!notificationData) {
1106 let alertService = lazy.gWindowsAlertsService;
1107 if (!alertService) {
1108 console.error("Windows alert service not available.");
1112 async function handleNotification() {
1113 let { tagWasHandled } = await alertService.handleWindowsTag(tag);
1115 // If the tag was not handled via callback, then the notification was
1116 // from a prior instance of the application and we need to handle
1117 // fallback behavior.
1118 if (!tagWasHandled) {
1120 `Completing Windows notification (tag=${JSON.stringify(
1122 )}, notificationData=${notificationData})`
1125 notificationData = JSON.parse(notificationData);
1128 `Completing Windows notification (tag=${JSON.stringify(
1130 )}, failed to parse (notificationData=${notificationData})`
1135 // This is awkward: the relaunch data set by the caller is _wrapped_
1136 // into a compound object that includes additional notification data,
1137 // and everything is exchanged as strings. Unwrap and parse here.
1138 let opaqueRelaunchData = null;
1139 if (notificationData?.opaqueRelaunchData) {
1141 opaqueRelaunchData = JSON.parse(
1142 notificationData.opaqueRelaunchData
1146 `Completing Windows notification (tag=${JSON.stringify(
1148 )}, failed to parse (opaqueRelaunchData=${
1149 notificationData.opaqueRelaunchData
1155 if (notificationData?.privilegedName) {
1156 Services.telemetry.setEventRecordingEnabled(
1157 "browser.launched_to_handle",
1160 Glean.browserLaunchedToHandle.systemNotification.record({
1161 name: notificationData.privilegedName,
1165 // If we have an action in the notification data, this will be the
1166 // window to perform the action in.
1169 if (notificationData?.launchUrl && !opaqueRelaunchData) {
1170 // Unprivileged Web Notifications contain a launch URL and are handled
1171 // slightly differently than privileged notifications with actions.
1172 let uri = resolveURIInternal(cmdLine, notificationData.launchUrl);
1173 if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1174 // Try to find an existing window and load our URI into the current
1175 // tab, new tab, or new window as prefs determine.
1177 handURIToExistingBrowser(
1179 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1182 lazy.gSystemPrincipal
1188 if (shouldLoadURI(uri)) {
1189 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, [uri.spec]);
1191 } else if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1192 // No URL provided, but notification was interacted with while the
1193 // application was closed. Fall back to opening the browser without url.
1194 winForAction = openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1195 await new Promise(resolve => {
1196 Services.obs.addObserver(function observe(subject) {
1197 if (subject == winForAction) {
1198 Services.obs.removeObserver(
1200 "browser-delayed-startup-finished"
1204 }, "browser-delayed-startup-finished");
1207 // Relaunch in private windows only if we're in perma-private mode.
1209 lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;
1210 winForAction = lazy.BrowserWindowTracker.getTopWindow({
1211 private: allowPrivate,
1215 if (opaqueRelaunchData && winForAction) {
1216 // Without dispatch, `OPEN_URL` with `where: "tab"` does not work on relaunch.
1217 Services.tm.dispatchToMainThread(() => {
1218 lazy.SpecialMessageActions.handleAction(
1220 winForAction.gBrowser
1226 // Notification handling occurs asynchronously to prevent blocking the
1227 // main thread. As a result we won't have the information we need to open
1228 // a new tab in the case of notification fallback handling before
1229 // returning. We call `enterLastWindowClosingSurvivalArea` to prevent
1230 // the browser from exiting in case early blank window is pref'd off.
1231 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1232 Services.startup.enterLastWindowClosingSurvivalArea();
1234 handleNotification()
1237 `Error handling Windows notification with tag '${tag}':`,
1242 if (cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
1243 Services.startup.exitLastWindowClosingSurvivalArea();
1252 cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1253 Services.startup.wasSilentlyStarted
1255 // If we are starting up in silent mode, don't open a window. We also need
1256 // to make sure that the application doesn't immediately exit, so stay in
1257 // a LastWindowClosingSurvivalArea until a window opens.
1258 Services.startup.enterLastWindowClosingSurvivalArea();
1259 Services.obs.addObserver(function windowOpenObserver() {
1260 Services.startup.exitLastWindowClosingSurvivalArea();
1261 Services.obs.removeObserver(windowOpenObserver, "domwindowopened");
1262 }, "domwindowopened");
1266 if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
1267 // Handle the case where we don't have a profile selected yet (e.g. the
1268 // Profile Manager is displayed).
1269 // On Windows, we will crash if we open an url and then select a profile.
1270 // On macOS, if we open an url we don't experience a crash but a broken
1271 // window is opened.
1272 // To prevent this handle all url command line flags and set the
1273 // command line's preventDefault to true to prevent the display of the ui.
1274 // The initial command line will be retained when nsAppRunner calls
1275 // LaunchChild though urls launched after the initial launch will be lost.
1276 if (!this._haveProfile) {
1278 // This will throw when a profile has not been selected.
1279 Services.dirsvc.get("ProfD", Ci.nsIFile);
1280 this._haveProfile = true;
1282 // eslint-disable-next-line no-empty
1283 while ((ar = cmdLine.handleFlagWithParam("url", false))) {}
1284 cmdLine.preventDefault = true;
1289 // `-osint` and handling registered file types and protocols is Windows-only.
1290 let launchedWithArg_osint =
1291 AppConstants.platform == "win" && cmdLine.findFlag("osint", false) == 0;
1292 if (launchedWithArg_osint) {
1293 cmdLine.handleFlag("osint", false);
1298 while ((ar = cmdLine.handleFlagWithParam("url", false))) {
1299 var uri = resolveURIInternal(cmdLine, ar);
1302 if (launchedWithArg_osint) {
1303 launchedWithArg_osint = false;
1305 // We use the resolved URI here, even though it can produce
1306 // surprising results where-by `-osint -url test.pdf` resolves to
1307 // a query with search parameter "test.pdf". But that shouldn't
1308 // happen when Firefox is launched by Windows itself: files should
1309 // exist and be resolved to file URLs.
1311 cmdLine && cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
1313 maybeRecordToHandleTelemetry(uri, isLaunch);
1321 AppConstants.platform == "win" &&
1322 cmdLine.handleFlag("to-handle-default-browser-agent", false)
1324 // The Default Browser Agent launches Firefox in response to a Windows
1325 // native notification, but it does so in a non-standard manner.
1326 Services.telemetry.setEventRecordingEnabled(
1327 "browser.launched_to_handle",
1330 Glean.browserLaunchedToHandle.systemNotification.record({
1331 name: "default-browser-agent",
1334 let thanksURI = Services.io.newURI(
1335 Services.urlFormatter.formatURLPref(
1336 "browser.shell.defaultBrowserAgent.thanksURL"
1339 urilist.push(thanksURI);
1342 if (cmdLine.findFlag("screenshot", true) != -1) {
1343 lazy.HeadlessShell.handleCmdLineArgs(
1345 urilist.filter(shouldLoadURI).map(u => u.spec)
1350 for (let i = 0; i < cmdLine.length; ++i) {
1351 var curarg = cmdLine.getArgument(i);
1352 if (curarg.match(/^-/)) {
1353 console.error("Warning: unrecognized command line flag", curarg);
1354 // To emulate the pre-nsICommandLine behavior, we ignore
1355 // the argument after an unrecognized flag.
1359 urilist.push(resolveURIInternal(cmdLine, curarg));
1362 `Error opening URI ${curarg} from the command line:`,
1369 if (urilist.length) {
1371 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1374 // Try to find an existing window and load our URI into the
1375 // current tab, new tab, or new window as prefs determine.
1377 handURIToExistingBrowser(
1379 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
1382 lazy.gSystemPrincipal
1388 var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
1389 if (URLlist.length) {
1390 openBrowserWindow(cmdLine, lazy.gSystemPrincipal, URLlist);
1392 } else if (!cmdLine.preventDefault) {
1394 AppConstants.platform == "win" &&
1395 cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH &&
1396 lazy.WindowsUIUtils.inTabletMode
1398 // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
1399 let win = lazy.BrowserWindowTracker.getTopWindow();
1405 openBrowserWindow(cmdLine, lazy.gSystemPrincipal);
1407 // Need a better solution in the future to avoid opening the blank window
1408 // when command line parameters say we are not going to show a browser
1409 // window, but for now the blank window getting closed quickly (and
1410 // causing only a slight flicker) is better than leaving it open.
1411 let win = Services.wm.getMostRecentWindow("navigator:blank");