Files
tubestation/browser/components/BrowserGlue.sys.mjs
Gijs Kruitbosch 78ebe33ed8 Bug 1962056 - move places initialization out of BrowserGlue, r=places-reviewers,migration-reviewers,firefox-desktop-core-reviewers ,mossop,mak,mconley
Places frontend initialization is surprisingly complex, and disentangling it
from the rest of startup by moving it to its own file helps make some of the
logic a little more obvious, and makes unit-testing a bit easier.

This also removes BrowserGlue from the indirection mechanism used between
MigatorBase instances and Places, by switching to category-manager-based
invocation - this way, migrator code does not need to directly
call places code to tell it it's done, but we don't need BrowserGlue to play
messenger between them.

It would be nice to do the same thing for `places-init-complete` but
that is notified from C++ code so unfortunately that is not easily possible.

Differential Revision: https://phabricator.services.mozilla.com/D244428
2025-05-21 13:54:58 +00:00

1936 lines
62 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AboutHomeStartupCache: "resource:///modules/AboutHomeStartupCache.sys.mjs",
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
AWToolbarButton: "resource:///modules/aboutwelcome/AWToolbarUtils.sys.mjs",
ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
ASRouterDefaultConfig:
"resource:///modules/asrouter/ASRouterDefaultConfig.sys.mjs",
ASRouterNewTabHook: "resource:///modules/asrouter/ASRouterNewTabHook.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
BackupService: "resource:///modules/backup/BackupService.sys.mjs",
BrowserSearchTelemetry:
"moz-src:///browser/components/search/BrowserSearchTelemetry.sys.mjs",
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
CaptchaDetectionPingUtils:
"resource://gre/modules/CaptchaDetectionPingUtils.sys.mjs",
ContentBlockingPrefs:
"moz-src:///browser/components/protections/ContentBlockingPrefs.sys.mjs",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.sys.mjs",
DAPTelemetrySender: "resource://gre/modules/DAPTelemetrySender.sys.mjs",
DAPVisitCounter: "resource://gre/modules/DAPVisitCounter.sys.mjs",
DefaultBrowserCheck:
"moz-src:///browser/components/DefaultBrowserCheck.sys.mjs",
DesktopActorRegistry:
"moz-src:///browser/components/DesktopActorRegistry.sys.mjs",
Discovery: "resource:///modules/Discovery.sys.mjs",
DistributionManagement: "resource:///modules/distribution.sys.mjs",
DoHController: "resource://gre/modules/DoHController.sys.mjs",
DownloadsViewableInternally:
"resource:///modules/DownloadsViewableInternally.sys.mjs",
ExtensionsUI: "resource:///modules/ExtensionsUI.sys.mjs",
// FilePickerCrashed is used by the `listeners` object below.
// eslint-disable-next-line mozilla/valid-lazy
FilePickerCrashed: "resource:///modules/FilePickerCrashed.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
Interactions: "resource:///modules/Interactions.sys.mjs",
LoginBreaches: "resource:///modules/LoginBreaches.sys.mjs",
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
OnboardingMessageProvider:
"resource:///modules/asrouter/OnboardingMessageProvider.sys.mjs",
PageActions: "resource:///modules/PageActions.sys.mjs",
PageDataService: "resource:///modules/pagedata/PageDataService.sys.mjs",
PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
PlacesBrowserStartup:
"moz-src:///browser/components/places/PlacesBrowserStartup.sys.mjs",
// PluginManager is used by the `listeners` object below.
// eslint-disable-next-line mozilla/valid-lazy
PluginManager: "resource:///actors/PluginParent.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.sys.mjs",
ProfileDataUpgrader:
"moz-src:///browser/components/ProfileDataUpgrader.sys.mjs",
ProfilesDatastoreService:
"resource:///modules/profiles/ProfilesDatastoreService.sys.mjs",
RemoteSecuritySettings:
"resource://gre/modules/psm/RemoteSecuritySettings.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs",
Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
SandboxUtils: "resource://gre/modules/SandboxUtils.sys.mjs",
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPTelemetry:
"moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
SelectableProfileService:
"resource:///modules/profiles/SelectableProfileService.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
SpecialMessageActions:
"resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
StartupOSIntegration:
"moz-src:///browser/components/shell/StartupOSIntegration.sys.mjs",
TelemetryReportingPolicy:
"resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
TRRRacer: "resource:///modules/TRRPerformance.sys.mjs",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
WebProtocolHandlerRegistrar:
"resource:///modules/WebProtocolHandlerRegistrar.sys.mjs",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetters(lazy, {
BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
PushService: ["@mozilla.org/push/Service;1", "nsIPushService"],
});
if (AppConstants.ENABLE_WEBDRIVER) {
XPCOMUtils.defineLazyServiceGetter(
lazy,
"Marionette",
"@mozilla.org/remote/marionette;1",
"nsIMarionette"
);
XPCOMUtils.defineLazyServiceGetter(
lazy,
"RemoteAgent",
"@mozilla.org/remote/agent;1",
"nsIRemoteAgent"
);
} else {
lazy.Marionette = { running: false };
lazy.RemoteAgent = { running: false };
}
const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
ChromeUtils.defineLazyGetter(
lazy,
"WeaveService",
() => Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject
);
if (AppConstants.MOZ_CRASHREPORTER) {
ChromeUtils.defineESModuleGetters(lazy, {
UnsubmittedCrashHandler: "resource:///modules/ContentCrashHandlers.sys.mjs",
});
}
ChromeUtils.defineLazyGetter(lazy, "gBrandBundle", function () {
return Services.strings.createBundle(
"chrome://branding/locale/brand.properties"
);
});
ChromeUtils.defineLazyGetter(lazy, "gBrowserBundle", function () {
return Services.strings.createBundle(
"chrome://browser/locale/browser.properties"
);
});
const listeners = {
observers: {
"file-picker-crashed": ["FilePickerCrashed"],
"gmp-plugin-crash": ["PluginManager"],
"plugin-crashed": ["PluginManager"],
},
observe(subject, topic, data) {
for (let module of this.observers[topic]) {
try {
lazy[module].observe(subject, topic, data);
} catch (e) {
console.error(e);
}
}
},
init() {
for (let observer of Object.keys(this.observers)) {
Services.obs.addObserver(this, observer);
}
},
};
if (AppConstants.MOZ_UPDATER) {
ChromeUtils.defineESModuleGetters(lazy, {
// This listeners/observers/lazy indirection is too much for eslint:
// eslint-disable-next-line mozilla/valid-lazy
UpdateListener: "resource://gre/modules/UpdateListener.sys.mjs",
});
listeners.observers["update-downloading"] = ["UpdateListener"];
listeners.observers["update-staged"] = ["UpdateListener"];
listeners.observers["update-downloaded"] = ["UpdateListener"];
listeners.observers["update-available"] = ["UpdateListener"];
listeners.observers["update-error"] = ["UpdateListener"];
listeners.observers["update-swap"] = ["UpdateListener"];
}
// Seconds of idle time before the late idle tasks will be scheduled.
const LATE_TASKS_IDLE_TIME_SEC = 20;
// Time after we stop tracking startup crashes.
const STARTUP_CRASHES_END_DELAY_MS = 30 * 1000;
/*
* OS X has the concept of zero-window sessions and therefore ignores the
* browser-lastwindow-close-* topics.
*/
const OBSERVE_LASTWINDOW_CLOSE_TOPICS = AppConstants.platform != "macosx";
export let BrowserInitState = {};
BrowserInitState.startupIdleTaskPromise = new Promise(resolve => {
BrowserInitState._resolveStartupIdleTask = resolve;
});
// Whether this launch was initiated by the OS. A launch-on-login will contain
// the "os-autostart" flag in the initial launch command line.
BrowserInitState.isLaunchOnLogin = false;
// Whether this launch was initiated by a taskbar tab shortcut. A launch from
// a taskbar tab shortcut will contain the "taskbar-tab" flag.
BrowserInitState.isTaskbarTab = false;
export function BrowserGlue() {
XPCOMUtils.defineLazyServiceGetter(
this,
"_userIdleService",
"@mozilla.org/widget/useridleservice;1",
"nsIUserIdleService"
);
this._init();
}
BrowserGlue.prototype = {
_saveSession: false,
_isNewProfile: undefined,
_defaultCookieBehaviorAtStartup: null,
_setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
if (!this._saveSession && !aForce) {
return;
}
if (!lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) {
Services.prefs.setBoolPref(
"browser.sessionstore.resume_session_once",
true
);
}
// This method can be called via [NSApplication terminate:] on Mac, which
// ends up causing prefs not to be flushed to disk, so we need to do that
// explicitly here. See bug 497652.
Services.prefs.savePrefFile(null);
},
// nsIObserver implementation
observe: async function BG_observe(subject, topic, data) {
switch (topic) {
case "notifications-open-settings":
this._openPreferences("privacy-permissions");
break;
case "final-ui-startup":
this._beforeUIStartup();
break;
case "browser-delayed-startup-finished":
this._onFirstWindowLoaded(subject);
Services.obs.removeObserver(this, "browser-delayed-startup-finished");
break;
case "sessionstore-windows-restored":
this._onWindowsRestored();
break;
case "browser:purge-session-history":
// reset the console service's error buffer
Services.console.logStringMessage(null); // clear the console (in case it's open)
Services.console.reset();
break;
case "restart-in-safe-mode":
this._onSafeModeRestart(subject);
break;
case "quit-application-requested":
this._onQuitRequest(subject, data);
break;
case "quit-application-granted":
this._onQuitApplicationGranted();
break;
case "browser-lastwindow-close-requested":
if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
// The application is not actually quitting, but the last full browser
// window is about to be closed.
this._onQuitRequest(subject, "lastwindow");
}
break;
case "browser-lastwindow-close-granted":
if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
this._setPrefToSaveSession();
}
break;
case "session-save":
this._setPrefToSaveSession(true);
subject.QueryInterface(Ci.nsISupportsPRBool);
subject.data = true;
break;
case "places-init-complete":
Services.obs.removeObserver(this, "places-init-complete");
lazy.PlacesBrowserStartup.backendInitComplete();
break;
case "browser-glue-test": // used by tests
if (data == "force-ui-migration") {
this._migrateUI();
} else if (data == "places-browser-init-complete") {
lazy.PlacesBrowserStartup.notifyIfInitializationComplete();
} else if (data == "add-breaches-sync-handler") {
this._addBreachesSyncHandler();
}
break;
case "handle-xul-text-link": {
let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
if (!linkHandled.data) {
let win = lazy.BrowserWindowTracker.getTopWindow();
if (win) {
data = JSON.parse(data);
let where = lazy.BrowserUtils.whereToOpenLink(data);
// Preserve legacy behavior of non-modifier left-clicks
// opening in a new selected tab.
if (where == "current") {
where = "tab";
}
win.openTrustedLinkIn(data.href, where);
linkHandled.data = true;
}
}
break;
}
case "profile-before-change":
// Any component that doesn't need to act after
// the UI has gone should be finalized in _onQuitApplicationGranted.
this._dispose();
break;
case "keyword-search": {
// This notification is broadcast by the docshell when it "fixes up" a
// URI that it's been asked to load into a keyword search.
let engine = null;
try {
engine = Services.search.getEngineByName(
subject.QueryInterface(Ci.nsISupportsString).data
);
} catch (ex) {
console.error(ex);
}
let win = lazy.BrowserWindowTracker.getTopWindow();
lazy.BrowserSearchTelemetry.recordSearch(
win.gBrowser.selectedBrowser,
engine,
"urlbar"
);
break;
}
case "xpi-signature-changed": {
let disabledAddons = JSON.parse(data).disabled;
let addons = await lazy.AddonManager.getAddonsByIDs(disabledAddons);
if (addons.some(addon => addon)) {
this._notifyUnsignedAddonsDisabled();
}
break;
}
case "handlersvc-store-initialized":
// Initialize PdfJs when running in-process and remote. This only
// happens once since PdfJs registers global hooks. If the PdfJs
// extension is installed the init method below will be overridden
// leaving initialization to the extension.
// parent only: configure default prefs, set up pref observers, register
// pdf content handler, and initializes parent side message manager
// shim for privileged api access.
lazy.PdfJs.init(this._isNewProfile);
// Allow certain viewable internally types to be opened from downloads.
lazy.DownloadsViewableInternally.register();
break;
case "app-startup": {
this._earlyBlankFirstPaint(subject);
// The "taskbar-tab" flag and its param will be handled in
// TaskbarTabCmd.sys.mjs
BrowserInitState.isTaskbarTab =
subject.findFlag("taskbar-tab", false) != -1;
BrowserInitState.isLaunchOnLogin = subject.handleFlag(
"os-autostart",
false
);
if (AppConstants.platform == "win") {
lazy.StartupOSIntegration.checkForLaunchOnLogin();
}
break;
}
}
},
// initialization (called on application startup)
_init: function BG__init() {
let os = Services.obs;
[
"notifications-open-settings",
"final-ui-startup",
"browser-delayed-startup-finished",
"sessionstore-windows-restored",
"browser:purge-session-history",
"quit-application-requested",
"quit-application-granted",
"session-save",
"places-init-complete",
"handle-xul-text-link",
"profile-before-change",
"keyword-search",
"restart-in-safe-mode",
"xpi-signature-changed",
"handlersvc-store-initialized",
].forEach(topic => os.addObserver(this, topic, true));
if (OBSERVE_LASTWINDOW_CLOSE_TOPICS) {
os.addObserver(this, "browser-lastwindow-close-requested", true);
os.addObserver(this, "browser-lastwindow-close-granted", true);
}
lazy.DesktopActorRegistry.init();
},
// cleanup (called on application shutdown)
_dispose: function BG__dispose() {
// AboutHomeStartupCache might write to the cache during
// quit-application-granted, so we defer uninitialization
// until here.
lazy.AboutHomeStartupCache.uninit();
if (this._lateTasksIdleObserver) {
this._userIdleService.removeIdleObserver(
this._lateTasksIdleObserver,
LATE_TASKS_IDLE_TIME_SEC
);
delete this._lateTasksIdleObserver;
}
if (this._gmpInstallManager) {
this._gmpInstallManager.uninit();
delete this._gmpInstallManager;
}
lazy.ContentBlockingPrefs.uninit();
},
// runs on startup, before the first command line handler is invoked
// (i.e. before the first window is opened)
_beforeUIStartup: function BG__beforeUIStartup() {
lazy.SessionStartup.init();
// check if we're in safe mode
if (Services.appinfo.inSafeMode) {
Services.ww.openWindow(
null,
"chrome://browser/content/safeMode.xhtml",
"_blank",
"chrome,centerscreen,modal,resizable=no",
null
);
}
// apply distribution customizations
lazy.DistributionManagement.applyCustomizations();
// handle any UI migration
this._migrateUI();
if (!Services.prefs.prefHasUserValue(PREF_PDFJS_ISDEFAULT_CACHE_STATE)) {
lazy.PdfJs.checkIsDefault(this._isNewProfile);
}
if (!AppConstants.NIGHTLY_BUILD && this._isNewProfile) {
lazy.FormAutofillUtils.setOSAuthEnabled(
lazy.FormAutofillUtils.AUTOFILL_CREDITCARDS_REAUTH_PREF,
false
);
lazy.LoginHelper.setOSAuthEnabled(
lazy.LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF,
false
);
}
listeners.init();
lazy.BrowserUtils.callModulesFromCategory({
categoryName: "browser-before-ui-startup",
});
Services.obs.notifyObservers(null, "browser-ui-startup-complete");
},
_checkForOldBuildUpdates() {
// check for update if our build is old
if (
AppConstants.MOZ_UPDATER &&
Services.prefs.getBoolPref("app.update.checkInstallTime")
) {
let buildID = Services.appinfo.appBuildID;
let today = new Date().getTime();
/* eslint-disable no-multi-spaces */
let buildDate = new Date(
buildID.slice(0, 4), // year
buildID.slice(4, 6) - 1, // months are zero-based.
buildID.slice(6, 8), // day
buildID.slice(8, 10), // hour
buildID.slice(10, 12), // min
buildID.slice(12, 14)
) // ms
.getTime();
/* eslint-enable no-multi-spaces */
const millisecondsIn24Hours = 86400000;
let acceptableAge =
Services.prefs.getIntPref("app.update.checkInstallTime.days") *
millisecondsIn24Hours;
if (buildDate + acceptableAge < today) {
// This is asynchronous, but just kick it off rather than waiting.
Cc["@mozilla.org/updates/update-service;1"]
.getService(Ci.nsIApplicationUpdateService)
.checkForBackgroundUpdates();
}
}
},
async _onSafeModeRestart(window) {
// prompt the user to confirm
let productName = lazy.gBrandBundle.GetStringFromName("brandShortName");
let strings = lazy.gBrowserBundle;
let promptTitle = strings.formatStringFromName(
"troubleshootModeRestartPromptTitle",
[productName]
);
let promptMessage = strings.GetStringFromName(
"troubleshootModeRestartPromptMessage"
);
let restartText = strings.GetStringFromName(
"troubleshootModeRestartButton"
);
let buttonFlags =
Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
Services.prompt.BUTTON_POS_0_DEFAULT;
let rv = await Services.prompt.asyncConfirmEx(
window.browsingContext,
Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
promptTitle,
promptMessage,
buttonFlags,
restartText,
null,
null,
null,
{}
);
if (rv.get("buttonNumClicked") != 0) {
return;
}
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
Ci.nsISupportsPRBool
);
Services.obs.notifyObservers(
cancelQuit,
"quit-application-requested",
"restart"
);
if (!cancelQuit.data) {
Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
}
},
/**
* Show a notification bar offering a reset.
*
* @param reason
* String of either "unused" or "uninstall", specifying the reason
* why a profile reset is offered.
*/
_resetProfileNotification(reason) {
let win = lazy.BrowserWindowTracker.getTopWindow();
if (!win) {
return;
}
const { ResetProfile } = ChromeUtils.importESModule(
"resource://gre/modules/ResetProfile.sys.mjs"
);
if (!ResetProfile.resetSupported()) {
return;
}
let productName = lazy.gBrandBundle.GetStringFromName("brandShortName");
let resetBundle = Services.strings.createBundle(
"chrome://global/locale/resetProfile.properties"
);
let message;
if (reason == "unused") {
message = resetBundle.formatStringFromName("resetUnusedProfile.message", [
productName,
]);
} else if (reason == "uninstall") {
message = resetBundle.formatStringFromName("resetUninstalled.message", [
productName,
]);
} else {
throw new Error(
`Unknown reason (${reason}) given to _resetProfileNotification.`
);
}
let buttons = [
{
label: resetBundle.formatStringFromName(
"refreshProfile.resetButton.label",
[productName]
),
accessKey: resetBundle.GetStringFromName(
"refreshProfile.resetButton.accesskey"
),
callback() {
ResetProfile.openConfirmationDialog(win);
},
},
];
win.gNotificationBox.appendNotification(
"reset-profile-notification",
{
label: message,
image: "chrome://global/skin/icons/question-64.png",
priority: win.gNotificationBox.PRIORITY_INFO_LOW,
},
buttons
);
},
_notifyUnsignedAddonsDisabled() {
let win = lazy.BrowserWindowTracker.getTopWindow();
if (!win) {
return;
}
let message = win.gNavigatorBundle.getString(
"unsignedAddonsDisabled.message"
);
let buttons = [
{
label: win.gNavigatorBundle.getString(
"unsignedAddonsDisabled.learnMore.label"
),
accessKey: win.gNavigatorBundle.getString(
"unsignedAddonsDisabled.learnMore.accesskey"
),
callback() {
win.BrowserAddonUI.openAddonsMgr(
"addons://list/extension?unsigned=true"
);
},
},
];
win.gNotificationBox.appendNotification(
"unsigned-addons-disabled",
{
label: message,
priority: win.gNotificationBox.PRIORITY_WARNING_MEDIUM,
},
buttons
);
},
_verifySandboxUserNamespaces: function BG_verifySandboxUserNamespaces(aWin) {
if (!AppConstants.MOZ_SANDBOX) {
return;
}
lazy.SandboxUtils.maybeWarnAboutMissingUserNamespaces(
aWin.gNotificationBox
);
},
_earlyBlankFirstPaint(cmdLine) {
let startTime = Cu.now();
let shouldCreateWindow = isPrivateWindow => {
if (cmdLine.findFlag("wait-for-jsdebugger", false) != -1) {
return true;
}
if (
AppConstants.platform == "macosx" ||
Services.startup.wasSilentlyStarted ||
!Services.prefs.getBoolPref("browser.startup.blankWindow", false)
) {
return false;
}
// Until bug 1450626 and bug 1488384 are fixed, skip the blank window when
// using a non-default theme.
if (
!Services.startup.showedPreXULSkeletonUI &&
Services.prefs.getCharPref(
"extensions.activeThemeID",
"default-theme@mozilla.org"
) != "default-theme@mozilla.org"
) {
return false;
}
// Bug 1448423: Skip the blank window if the user is resisting fingerprinting
if (
Services.prefs.getBoolPref(
"privacy.resistFingerprinting.skipEarlyBlankFirstPaint",
true
) &&
ChromeUtils.shouldResistFingerprinting(
"RoundWindowSize",
null,
isPrivateWindow ||
Services.prefs.getBoolPref(
"browser.privatebrowsing.autostart",
false
)
)
) {
return false;
}
let width = getValue("width");
let height = getValue("height");
// The clean profile case isn't handled yet. Return early for now.
if (!width || !height) {
return false;
}
return true;
};
let makeWindowPrivate =
cmdLine.findFlag("private-window", false) != -1 &&
lazy.StartupOSIntegration.isPrivateBrowsingAllowedInRegistry();
if (!shouldCreateWindow(makeWindowPrivate)) {
return;
}
let browserWindowFeatures =
"chrome,all,dialog=no,extrachrome,menubar,resizable,scrollbars,status," +
"location,toolbar,personalbar";
// This needs to be set when opening the window to ensure that the AppUserModelID
// is set correctly on Windows. Without it, initial launches with `-private-window`
// will show up under the regular Firefox taskbar icon first, and then switch
// to the Private Browsing icon shortly thereafter.
if (makeWindowPrivate) {
browserWindowFeatures += ",private";
}
let win = Services.ww.openWindow(
null,
"about:blank",
null,
browserWindowFeatures,
null
);
// Hide the titlebar if the actual browser window will draw in it.
let hiddenTitlebar = Services.appinfo.drawInTitlebar;
if (hiddenTitlebar) {
win.windowUtils.setCustomTitlebar(true);
}
let docElt = win.document.documentElement;
docElt.setAttribute("screenX", getValue("screenX"));
docElt.setAttribute("screenY", getValue("screenY"));
// The sizemode="maximized" attribute needs to be set before first paint.
let sizemode = getValue("sizemode");
let width = getValue("width") || 500;
let height = getValue("height") || 500;
if (sizemode == "maximized") {
docElt.setAttribute("sizemode", sizemode);
// Set the size to use when the user leaves the maximized mode.
// The persisted size is the outer size, but the height/width
// attributes set the inner size.
let appWin = win.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow);
height -= appWin.outerToInnerHeightDifferenceInCSSPixels;
width -= appWin.outerToInnerWidthDifferenceInCSSPixels;
docElt.setAttribute("height", height);
docElt.setAttribute("width", width);
} else {
// Setting the size of the window in the features string instead of here
// causes the window to grow by the size of the titlebar.
win.resizeTo(width, height);
}
// Set this before showing the window so that graphics code can use it to
// decide to skip some expensive code paths (eg. starting the GPU process).
docElt.setAttribute("windowtype", "navigator:blank");
// The window becomes visible after OnStopRequest, so make this happen now.
win.stop();
ChromeUtils.addProfilerMarker("earlyBlankFirstPaint", startTime);
win.openTime = Cu.now();
let { TelemetryTimestamps } = ChromeUtils.importESModule(
"resource://gre/modules/TelemetryTimestamps.sys.mjs"
);
TelemetryTimestamps.add("blankWindowShown");
function getValue(attr) {
return Services.xulStore.getValue(
AppConstants.BROWSER_CHROME_URL,
"main-window",
attr
);
}
},
_firstWindowTelemetry(aWindow) {
let scaling = aWindow.devicePixelRatio * 100;
Glean.gfxDisplay.scaling.accumulateSingleSample(scaling);
},
// the first browser window has finished initializing
_onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
lazy.AboutNewTab.init();
lazy.TabCrashHandler.init();
lazy.ProcessHangMonitor.init();
// A channel for "remote troubleshooting" code...
let channel = new lazy.WebChannel(
"remote-troubleshooting",
"remote-troubleshooting"
);
channel.listen((id, data, target) => {
if (data.command == "request") {
let { Troubleshoot } = ChromeUtils.importESModule(
"resource://gre/modules/Troubleshoot.sys.mjs"
);
Troubleshoot.snapshot().then(snapshotData => {
// for privacy we remove crash IDs and all preferences (but bug 1091944
// exists to expose prefs once we are confident of privacy implications)
delete snapshotData.crashes;
delete snapshotData.modifiedPreferences;
delete snapshotData.printingPreferences;
channel.send(snapshotData, target);
});
}
});
this._maybeOfferProfileReset();
this._checkForOldBuildUpdates();
// Check if Sync is configured
if (Services.prefs.prefHasUserValue("services.sync.username")) {
lazy.WeaveService.init();
}
lazy.PageThumbs.init();
lazy.NewTabUtils.init();
lazy.PageActions.init();
lazy.DoHController.init();
lazy.ProfilesDatastoreService.init().catch(console.error);
lazy.SelectableProfileService.init().catch(console.error);
this._firstWindowTelemetry(aWindow);
lazy.ContentBlockingPrefs.init();
lazy.CaptchaDetectionPingUtils.init();
this._verifySandboxUserNamespaces(aWindow);
lazy.BrowserUtils.callModulesFromCategory(
{
categoryName: "browser-first-window-ready",
profilerMarker: "browserFirstWindowReady",
},
aWindow
);
},
_maybeOfferProfileReset() {
// Offer to reset a user's profile if it hasn't been used for 60 days.
const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000;
let lastUse = Services.appinfo.replacedLockTime;
let disableResetPrompt = Services.prefs.getBoolPref(
"browser.disableResetPrompt",
false
);
// Also check prefs.js last modified timestamp as a backstop.
// This helps for cases where the lock file checks don't work,
// e.g. NFS or because the previous time Firefox ran, it ran
// for a very long time. See bug 1054947 and related bugs.
lastUse = Math.max(
lastUse,
Services.prefs.userPrefsFileLastModifiedAtStartup
);
if (
!disableResetPrompt &&
lastUse &&
Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS
) {
this._resetProfileNotification("unused");
} else if (AppConstants.platform == "win" && !disableResetPrompt) {
// Check if we were just re-installed and offer Firefox Reset
let updateChannel;
try {
updateChannel = ChromeUtils.importESModule(
"resource://gre/modules/UpdateUtils.sys.mjs"
).UpdateUtils.UpdateChannel;
} catch (ex) {}
if (updateChannel) {
let uninstalledValue = lazy.WindowsRegistry.readRegKey(
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Mozilla\\Firefox",
`Uninstalled-${updateChannel}`
);
let removalSuccessful = lazy.WindowsRegistry.removeRegKey(
Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
"Software\\Mozilla\\Firefox",
`Uninstalled-${updateChannel}`
);
if (removalSuccessful && uninstalledValue == "True") {
this._resetProfileNotification("uninstall");
}
}
}
},
/**
* Application shutdown handler.
*
* If you need new code to be called on shutdown, please use
* the category manager browser-quit-application-granted category
* instead of adding new manual code to this function.
*/
_onQuitApplicationGranted() {
function failureHandler(ex) {
if (Cu.isInAutomation) {
// This usually happens after the test harness is done collecting
// test errors, thus we can't easily add a failure to it. The only
// noticeable solution we have is crashing.
Cc["@mozilla.org/xpcom/debug;1"]
.getService(Ci.nsIDebug2)
.abort(ex.filename, ex.lineNumber);
}
}
lazy.BrowserUtils.callModulesFromCategory({
categoryName: "browser-quit-application-granted",
failureHandler,
});
let tasks = [
// This pref must be set here because SessionStore will use its value
// on quit-application.
() => this._setPrefToSaveSession(),
// Call trackStartupCrashEnd here in case the delayed call on startup hasn't
// yet occurred (see trackStartupCrashEnd caller in browser.js).
() => Services.startup.trackStartupCrashEnd(),
() => {
// bug 1839426 - The FOG service needs to be instantiated reliably so it
// can perform at-shutdown tasks later in shutdown.
Services.fog;
},
];
for (let task of tasks) {
try {
task();
} catch (ex) {
console.error(`Error during quit-application-granted: ${ex}`);
failureHandler(ex);
}
}
},
_monitorWebcompatReporterPref() {
const PREF = "extensions.webcompat-reporter.enabled";
const ID = "webcompat-reporter@mozilla.org";
Services.prefs.addObserver(PREF, async () => {
let addon = await lazy.AddonManager.getAddonByID(ID);
if (!addon) {
return;
}
let enabled = Services.prefs.getBoolPref(PREF, false);
if (enabled && !addon.isActive) {
await addon.enable({ allowSystemAddons: true });
} else if (!enabled && addon.isActive) {
await addon.disable({ allowSystemAddons: true });
}
});
},
// All initial windows have opened.
_onWindowsRestored: function BG__onWindowsRestored() {
if (this._windowsWereRestored) {
return;
}
this._windowsWereRestored = true;
lazy.BrowserUsageTelemetry.init();
lazy.SearchSERPTelemetry.init();
lazy.Interactions.init();
lazy.PageDataService.init();
lazy.ExtensionsUI.init();
let signingRequired;
if (AppConstants.MOZ_REQUIRE_SIGNING) {
signingRequired = true;
} else {
signingRequired = Services.prefs.getBoolPref(
"xpinstall.signatures.required"
);
}
if (signingRequired) {
let disabledAddons = lazy.AddonManager.getStartupChanges(
lazy.AddonManager.STARTUP_CHANGE_DISABLED
);
lazy.AddonManager.getAddonsByIDs(disabledAddons).then(addons => {
for (let addon of addons) {
if (addon.signedState <= lazy.AddonManager.SIGNEDSTATE_MISSING) {
this._notifyUnsignedAddonsDisabled();
break;
}
}
});
}
if (AppConstants.MOZ_CRASHREPORTER) {
lazy.UnsubmittedCrashHandler.init();
lazy.UnsubmittedCrashHandler.scheduleCheckForUnsubmittedCrashReports();
}
if (AppConstants.ASAN_REPORTER) {
var { AsanReporter } = ChromeUtils.importESModule(
"resource://gre/modules/AsanReporter.sys.mjs"
);
AsanReporter.init();
}
lazy.Sanitizer.onStartup();
this._maybeShowRestoreSessionInfoBar();
this._scheduleStartupIdleTasks();
this._lateTasksIdleObserver = (idleService, topic) => {
if (topic == "idle") {
idleService.removeIdleObserver(
this._lateTasksIdleObserver,
LATE_TASKS_IDLE_TIME_SEC
);
delete this._lateTasksIdleObserver;
this._scheduleBestEffortUserIdleTasks();
}
};
this._userIdleService.addIdleObserver(
this._lateTasksIdleObserver,
LATE_TASKS_IDLE_TIME_SEC
);
this._monitorWebcompatReporterPref();
// Loading the MigrationUtils module does the work of registering the
// migration wizard JSWindowActor pair. In case nothing else has done
// this yet, load the MigrationUtils so that the wizard is ready to be
// used.
lazy.MigrationUtils;
},
/**
* Use this function as an entry point to schedule tasks that
* need to run only once after startup, and can be scheduled
* by using an idle callback.
*
* The functions scheduled here will fire from idle callbacks
* once every window has finished being restored by session
* restore, and it's guaranteed that they will run before
* the equivalent per-window idle tasks
* (from _schedulePerWindowIdleTasks in browser.js).
*
* If you have something that can wait even further than the
* per-window initialization, and is okay with not being run in some
* sessions, please schedule them using
* _scheduleBestEffortUserIdleTasks.
* Don't be fooled by thinking that the use of the timeout parameter
* will delay your function: it will just ensure that it potentially
* happens _earlier_ than expected (when the timeout limit has been reached),
* but it will not make it happen later (and out of order) compared
* to the other ones scheduled together.
*/
_scheduleStartupIdleTasks() {
function runIdleTasks(idleTasks) {
for (let task of idleTasks) {
if ("condition" in task && !task.condition) {
continue;
}
ChromeUtils.idleDispatch(
async () => {
if (!Services.startup.shuttingDown) {
let startTime = Cu.now();
try {
await task.task();
} catch (ex) {
console.error(ex);
} finally {
ChromeUtils.addProfilerMarker(
"startupIdleTask",
startTime,
task.name
);
}
}
},
task.timeout ? { timeout: task.timeout } : undefined
);
}
}
// Note: unless you need a timeout, please do not add new tasks here, and
// instead use the category manager. You can do this in a manifest file in
// the component that needs to run code, or in BrowserComponents.manifest
// in this folder. The callModulesFromCategory call below will call them.
const earlyTasks = [
// It's important that SafeBrowsing is initialized reasonably
// early, so we use a maximum timeout for it.
{
name: "SafeBrowsing.init",
task: () => {
lazy.SafeBrowsing.init();
},
timeout: 5000,
},
{
name: "ContextualIdentityService.load",
task: async () => {
await lazy.ContextualIdentityService.load();
lazy.Discovery.update();
},
},
];
runIdleTasks(earlyTasks);
lazy.BrowserUtils.callModulesFromCategory({
categoryName: "browser-idle-startup",
profilerMarker: "startupIdleTask",
idleDispatch: true,
});
const lateTasks = [
// Begin listening for incoming push messages.
{
name: "PushService.ensureReady",
task: () => {
try {
lazy.PushService.wrappedJSObject.ensureReady();
} catch (ex) {
// NS_ERROR_NOT_AVAILABLE will get thrown for the PushService
// getter if the PushService is disabled.
if (ex.result != Cr.NS_ERROR_NOT_AVAILABLE) {
throw ex;
}
}
},
},
// Load the Login Manager data from disk off the main thread, some time
// after startup. If the data is required before this runs, for example
// because a restored page contains a password field, it will be loaded on
// the main thread, and this initialization request will be ignored.
{
name: "Services.logins",
task: () => {
try {
Services.logins;
} catch (ex) {
console.error(ex);
}
},
timeout: 3000,
},
// Add breach alerts pref observer reasonably early so the pref flip works
{
name: "_addBreachAlertsPrefObserver",
task: () => {
this._addBreachAlertsPrefObserver();
},
},
{
name: "BrowserGlue._maybeShowDefaultBrowserPrompt",
task: () => {
this._maybeShowDefaultBrowserPrompt();
},
},
{
name: "BrowserGlue._monitorScreenshotsPref",
task: () => {
lazy.ScreenshotsUtils.monitorScreenshotsPref();
},
},
{
name: "trackStartupCrashEndSetTimeout",
task: () => {
lazy.setTimeout(function () {
Services.tm.idleDispatchToMainThread(
Services.startup.trackStartupCrashEnd
);
}, STARTUP_CRASHES_END_DELAY_MS);
},
},
{
name: "handlerService.asyncInit",
task: () => {
let handlerService = Cc[
"@mozilla.org/uriloader/handler-service;1"
].getService(Ci.nsIHandlerService);
handlerService.asyncInit();
},
},
{
name: "webProtocolHandlerService.asyncInit",
task: () => {
lazy.WebProtocolHandlerRegistrar.prototype.init(true);
},
},
// Run TRR performance measurements for DoH.
{
name: "doh-rollout.trrRacer.run",
task: () => {
let enabledPref = "doh-rollout.trrRace.enabled";
let completePref = "doh-rollout.trrRace.complete";
if (Services.prefs.getBoolPref(enabledPref, false)) {
if (!Services.prefs.getBoolPref(completePref, false)) {
new lazy.TRRRacer().run(() => {
Services.prefs.setBoolPref(completePref, true);
});
}
} else {
Services.prefs.addObserver(enabledPref, function observer() {
if (Services.prefs.getBoolPref(enabledPref, false)) {
Services.prefs.removeObserver(enabledPref, observer);
if (!Services.prefs.getBoolPref(completePref, false)) {
new lazy.TRRRacer().run(() => {
Services.prefs.setBoolPref(completePref, true);
});
}
}
});
}
},
},
// Add the setup button if this is the first startup
{
name: "AWToolbarButton.SetupButton",
task: async () => {
if (
// Not in automation: the button changes CUI state,
// breaking tests. Check this first, so that the module
// doesn't load if it doesn't have to.
!Cu.isInAutomation &&
lazy.AWToolbarButton.hasToolbarButtonEnabled
) {
await lazy.AWToolbarButton.maybeAddSetupButton();
}
},
},
{
name: "ASRouterNewTabHook.createInstance",
task: () => {
lazy.ASRouterNewTabHook.createInstance(lazy.ASRouterDefaultConfig());
},
},
{
name: "BackgroundUpdate",
condition: AppConstants.MOZ_UPDATE_AGENT && AppConstants.MOZ_UPDATER,
task: async () => {
let updateServiceStub = Cc[
"@mozilla.org/updates/update-service-stub;1"
].getService(Ci.nsIApplicationUpdateServiceStub);
// Never in automation!
if (!updateServiceStub.updateDisabledForTesting) {
let { BackgroundUpdate } = ChromeUtils.importESModule(
"resource://gre/modules/BackgroundUpdate.sys.mjs"
);
try {
await BackgroundUpdate.scheduleFirefoxMessagingSystemTargetingSnapshotting();
} catch (e) {
console.error(
"There was an error scheduling Firefox Messaging System targeting snapshotting: ",
e
);
}
await BackgroundUpdate.maybeScheduleBackgroundUpdateTask();
}
},
},
// Login detection service is used in fission to identify high value sites.
{
name: "LoginDetection.init",
task: () => {
let loginDetection = Cc[
"@mozilla.org/login-detection-service;1"
].createInstance(Ci.nsILoginDetectionService);
loginDetection.init();
},
},
// Schedule a sync (if enabled) after we've loaded
{
name: "WeaveService",
task: async () => {
if (lazy.WeaveService.enabled) {
await lazy.WeaveService.whenLoaded();
lazy.WeaveService.Weave.Service.scheduler.autoConnect();
}
},
},
{
name: "unblock-untrusted-modules-thread",
condition: AppConstants.platform == "win",
task: () => {
Services.obs.notifyObservers(
null,
"unblock-untrusted-modules-thread"
);
},
},
{
name: "DAPTelemetrySender.startup",
condition: AppConstants.MOZ_TELEMETRY_REPORTING,
task: async () => {
await lazy.DAPTelemetrySender.startup();
await lazy.DAPVisitCounter.startup();
},
},
{
// Starts the JSOracle process for ORB JavaScript validation, if it hasn't started already.
name: "start-orb-javascript-oracle",
task: () => {
ChromeUtils.ensureJSOracleStarted();
},
},
{
name: "BackupService initialization",
condition: Services.prefs.getBoolPref("browser.backup.enabled", false),
task: () => {
lazy.BackupService.init();
},
},
{
name: "Init hasSSD for SystemInfo",
condition: AppConstants.platform == "win",
// Initializes diskInfo to be able to get hasSSD which is part
// of the PageLoad event. Only runs on windows, since diskInfo
// is a no-op on other platforms
task: () => Services.sysinfo.diskInfo,
},
{
name: "browser-startup-idle-tasks-finished",
task: () => {
// Use idleDispatch a second time to run this after the per-window
// idle tasks.
ChromeUtils.idleDispatch(() => {
Services.obs.notifyObservers(
null,
"browser-startup-idle-tasks-finished"
);
BrowserInitState._resolveStartupIdleTask();
});
},
},
// Do NOT add anything after idle tasks finished.
];
runIdleTasks(lateTasks);
},
/**
* Use this function as an entry point to schedule tasks that we hope
* to run once per session, at any arbitrary point in time, and which we
* are okay with sometimes not running at all.
*
* This function will be called from an idle observer. Check the value of
* LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
* observer.
*
* Note: this function may never be called if the user is never idle for the
* requisite time (LATE_TASKS_IDLE_TIME_SEC). Be certain before adding
* something here that it's okay that it never be run.
*/
_scheduleBestEffortUserIdleTasks() {
const idleTasks = [
function GMPInstallManagerSimpleCheckAndInstall() {
let { GMPInstallManager } = ChromeUtils.importESModule(
"resource://gre/modules/GMPInstallManager.sys.mjs"
);
this._gmpInstallManager = new GMPInstallManager();
// We don't really care about the results, if someone is interested they
// can check the log.
this._gmpInstallManager.simpleCheckAndInstall().catch(() => {});
}.bind(this),
function RemoteSettingsInit() {
lazy.RemoteSettings.init();
this._addBreachesSyncHandler();
}.bind(this),
function RemoteSecuritySettingsInit() {
lazy.RemoteSecuritySettings.init();
},
function searchBackgroundChecks() {
Services.search.runBackgroundChecks();
},
];
for (let task of idleTasks) {
ChromeUtils.idleDispatch(async () => {
if (!Services.startup.shuttingDown) {
let startTime = Cu.now();
try {
await task();
} catch (ex) {
console.error(ex);
} finally {
ChromeUtils.addProfilerMarker(
"startupLateIdleTask",
startTime,
task.name
);
}
}
});
}
lazy.BrowserUtils.callModulesFromCategory({
categoryName: "browser-best-effort-idle-startup",
idleDispatch: true,
profilerMarker: "startupLateIdleTask",
});
},
_addBreachesSyncHandler() {
if (
Services.prefs.getBoolPref(
"signon.management.page.breach-alerts.enabled",
false
)
) {
lazy
.RemoteSettings(lazy.LoginBreaches.REMOTE_SETTINGS_COLLECTION)
.on("sync", async event => {
await lazy.LoginBreaches.update(event.data.current);
});
}
},
_addBreachAlertsPrefObserver() {
const BREACH_ALERTS_PREF = "signon.management.page.breach-alerts.enabled";
const clearVulnerablePasswordsIfBreachAlertsDisabled = async function () {
if (!Services.prefs.getBoolPref(BREACH_ALERTS_PREF)) {
await lazy.LoginBreaches.clearAllPotentiallyVulnerablePasswords();
}
};
clearVulnerablePasswordsIfBreachAlertsDisabled();
Services.prefs.addObserver(
BREACH_ALERTS_PREF,
clearVulnerablePasswordsIfBreachAlertsDisabled
);
},
_quitSource: "unknown",
_registerQuitSource(source) {
this._quitSource = source;
},
_onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
// If user has already dismissed quit request, then do nothing
if (aCancelQuit instanceof Ci.nsISupportsPRBool && aCancelQuit.data) {
return;
}
// There are several cases where we won't show a dialog here:
// 1. There is only 1 tab open in 1 window
// 2. browser.warnOnQuit == false
// 3. The browser is currently in Private Browsing mode
// 4. The browser will be restarted.
// 5. The user has automatic session restore enabled and
// browser.sessionstore.warnOnQuit is not set to true.
// 6. The user doesn't have automatic session restore enabled
// and browser.tabs.warnOnClose is not set to true.
//
// Otherwise, we will show the "closing multiple tabs" dialog.
//
// aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate
// "the last window is closing but we're not quitting (a non-browser window is open)"
// and also "we're quitting by closing the last window".
if (aQuitType == "restart" || aQuitType == "os-restart") {
return;
}
// browser.warnOnQuit is a hidden global boolean to override all quit prompts.
if (!Services.prefs.getBoolPref("browser.warnOnQuit")) {
return;
}
let windowcount = 0;
let pagecount = 0;
for (let win of lazy.BrowserWindowTracker.orderedWindows) {
if (win.closed) {
continue;
}
windowcount++;
let tabbrowser = win.gBrowser;
if (tabbrowser) {
pagecount += tabbrowser.visibleTabs.length - tabbrowser.pinnedTabCount;
}
}
// No windows open so no need for a warning.
if (!windowcount) {
return;
}
// browser.warnOnQuitShortcut is checked when quitting using the shortcut key.
// The warning will appear even when only one window/tab is open. For other
// methods of quitting, the warning only appears when there is more than one
// window or tab open.
let shouldWarnForShortcut =
this._quitSource == "shortcut" &&
Services.prefs.getBoolPref("browser.warnOnQuitShortcut");
let shouldWarnForTabs =
pagecount >= 2 && Services.prefs.getBoolPref("browser.tabs.warnOnClose");
if (!shouldWarnForTabs && !shouldWarnForShortcut) {
return;
}
if (!aQuitType) {
aQuitType = "quit";
}
let win = lazy.BrowserWindowTracker.getTopWindow();
// Our prompt for quitting is most important, so replace others.
win.gDialogBox.replaceDialogIfOpen();
let titleId = {
id: "tabbrowser-confirm-close-tabs-title",
args: { tabCount: pagecount },
};
let quitButtonLabelId = "tabbrowser-confirm-close-tabs-button";
let closeTabButtonLabelId = "tabbrowser-confirm-close-tab-only-button";
let showCloseCurrentTabOption = false;
if (windowcount > 1) {
// More than 1 window. Compose our own message based on whether
// the shortcut warning is on or not.
if (shouldWarnForShortcut) {
showCloseCurrentTabOption = true;
titleId = "tabbrowser-confirm-close-warn-shortcut-title";
quitButtonLabelId =
"tabbrowser-confirm-close-windows-warn-shortcut-button";
} else {
titleId = {
id: "tabbrowser-confirm-close-windows-title",
args: { windowCount: windowcount },
};
quitButtonLabelId = "tabbrowser-confirm-close-windows-button";
}
} else if (shouldWarnForShortcut) {
if (win.gBrowser.visibleTabs.length > 1) {
showCloseCurrentTabOption = true;
titleId = "tabbrowser-confirm-close-warn-shortcut-title";
quitButtonLabelId = "tabbrowser-confirm-close-tabs-with-key-button";
} else {
titleId = "tabbrowser-confirm-close-tabs-with-key-title";
quitButtonLabelId = "tabbrowser-confirm-close-tabs-with-key-button";
}
}
// The checkbox label is different depending on whether the shortcut
// was used to quit or not.
let checkboxLabelId;
if (shouldWarnForShortcut) {
const quitKeyElement = win.document.getElementById("key_quitApplication");
const quitKey = lazy.ShortcutUtils.prettifyShortcut(quitKeyElement);
checkboxLabelId = {
id: "tabbrowser-ask-close-tabs-with-key-checkbox",
args: { quitKey },
};
} else {
checkboxLabelId = "tabbrowser-ask-close-tabs-checkbox";
}
const [title, quitButtonLabel, checkboxLabel] =
win.gBrowser.tabLocalization.formatMessagesSync([
titleId,
quitButtonLabelId,
checkboxLabelId,
]);
// Only format the "close current tab" message if needed
let closeTabButtonLabel;
if (showCloseCurrentTabOption) {
[closeTabButtonLabel] = win.gBrowser.tabLocalization.formatMessagesSync([
closeTabButtonLabelId,
]);
}
let warnOnClose = { value: true };
let flags;
if (showCloseCurrentTabOption) {
// Adds buttons for quit (BUTTON_POS_0), cancel (BUTTON_POS_1), and close current tab (BUTTON_POS_2).
// Also sets a flag to reorder dialog buttons so that cancel is reordered on Unix platforms.
flags =
(Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
Services.prompt.BUTTON_TITLE_IS_STRING *
Services.prompt.BUTTON_POS_2) |
Services.prompt.BUTTON_POS_1_IS_SECONDARY;
Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
} else {
// Adds quit and cancel buttons
flags =
Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
}
// buttonPressed will be 0 for close all, 1 for cancel (don't close/quit), 2 for close current tab
let buttonPressed = Services.prompt.confirmEx(
win,
title.value,
null,
flags,
quitButtonLabel.value,
null,
showCloseCurrentTabOption ? closeTabButtonLabel.value : null,
checkboxLabel.value,
warnOnClose
);
// If the user has unticked the box, and has confirmed closing, stop showing
// the warning.
if (buttonPressed == 0 && !warnOnClose.value) {
if (shouldWarnForShortcut) {
Services.prefs.setBoolPref("browser.warnOnQuitShortcut", false);
} else {
Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
}
}
// Close the current tab if user selected BUTTON_POS_2
if (buttonPressed === 2) {
win.gBrowser.removeTab(win.gBrowser.selectedTab);
}
this._quitSource = "unknown";
aCancelQuit.data = buttonPressed != 0;
},
_migrateUI() {
// Use an increasing number to keep track of the current state of the user's
// profile, so we can move data around as needed as the browser evolves.
// Completely unrelated to the current Firefox release number.
const APP_DATA_VERSION = 155;
const PREF = "browser.migration.version";
let profileDataVersion = Services.prefs.getIntPref(PREF, -1);
this._isNewProfile = profileDataVersion == -1;
if (this._isNewProfile) {
// This is a new profile, nothing to upgrade.
Services.prefs.setIntPref(PREF, APP_DATA_VERSION);
} else if (profileDataVersion < APP_DATA_VERSION) {
lazy.ProfileDataUpgrader.upgrade(profileDataVersion, APP_DATA_VERSION);
}
},
async _showUpgradeDialog() {
const data = await lazy.OnboardingMessageProvider.getUpgradeMessage();
const { gBrowser } = lazy.BrowserWindowTracker.getTopWindow();
// We'll be adding a new tab open the tab-modal dialog in.
let tab;
const upgradeTabsProgressListener = {
onLocationChange(aBrowser) {
if (aBrowser === tab.linkedBrowser) {
lazy.setTimeout(() => {
// We're now far enough along in the load that we no longer have to
// worry about a call to onLocationChange triggering SubDialog.abort,
// so display the dialog
const config = {
type: "SHOW_SPOTLIGHT",
data,
};
lazy.SpecialMessageActions.handleAction(config, tab.linkedBrowser);
gBrowser.removeTabsProgressListener(upgradeTabsProgressListener);
}, 0);
}
},
};
// Make sure we're ready to show the dialog once onLocationChange gets
// called.
gBrowser.addTabsProgressListener(upgradeTabsProgressListener);
tab = gBrowser.addTrustedTab("about:home", {
relatedToCurrent: true,
});
gBrowser.selectedTab = tab;
},
async _showSetToDefaultSpotlight(message, browser) {
const config = {
type: "SHOW_SPOTLIGHT",
data: message,
};
try {
lazy.SpecialMessageActions.handleAction(config, browser);
} catch (e) {
console.error("Couldn't render spotlight", message, e);
}
},
async _maybeShowDefaultBrowserPrompt() {
// Ensuring the user is notified arranges the following ordering. Highest
// priority is datareporting policy modal, if present. Second highest
// priority is the upgrade dialog, which can include a "primary browser"
// request and is limited in various ways, e.g., major upgrades.
await lazy.TelemetryReportingPolicy.ensureUserIsNotified();
const dialogVersion = 106;
const dialogVersionPref = "browser.startup.upgradeDialog.version";
const dialogReason = await (async () => {
if (!lazy.BrowserHandler.majorUpgrade) {
return "not-major";
}
const lastVersion = Services.prefs.getIntPref(dialogVersionPref, 0);
if (lastVersion > dialogVersion) {
return "newer-shown";
}
if (lastVersion === dialogVersion) {
return "already-shown";
}
// Check the default branch as enterprise policies can set prefs there.
const defaultPrefs = Services.prefs.getDefaultBranch("");
if (!defaultPrefs.getBoolPref("browser.aboutwelcome.enabled", true)) {
return "no-welcome";
}
if (!Services.policies.isAllowed("postUpdateCustomPage")) {
return "disallow-postUpdate";
}
const showUpgradeDialog =
lazy.NimbusFeatures.upgradeDialog.getVariable("enabled");
return showUpgradeDialog ? "" : "disabled";
})();
// Record why the dialog is showing or not.
Glean.upgradeDialog.triggerReason.record({
value: dialogReason || "satisfied",
});
// Show the upgrade dialog if allowed and remember the version.
if (!dialogReason) {
Services.prefs.setIntPref(dialogVersionPref, dialogVersion);
this._showUpgradeDialog();
return;
}
const willPrompt = await lazy.DefaultBrowserCheck.willCheckDefaultBrowser(
/* isStartupCheck */ true
);
if (willPrompt) {
let win = lazy.BrowserWindowTracker.getTopWindow();
let setToDefaultFeature = lazy.NimbusFeatures.setToDefaultPrompt;
// Send exposure telemetry if user will see default prompt or experimental
// message
await setToDefaultFeature.ready();
await setToDefaultFeature.recordExposureEvent();
const { showSpotlightPrompt, message } =
setToDefaultFeature.getAllVariables();
if (showSpotlightPrompt && message) {
// Show experimental message
this._showSetToDefaultSpotlight(message, win.gBrowser.selectedBrowser);
return;
}
lazy.DefaultBrowserCheck.prompt(win);
}
await lazy.ASRouter.waitForInitialized;
lazy.ASRouter.sendTriggerMessage({
browser:
lazy.BrowserWindowTracker.getTopWindow()?.gBrowser.selectedBrowser,
// triggerId and triggerContext
id: "defaultBrowserCheck",
context: { willShowDefaultPrompt: willPrompt, source: "startup" },
});
},
/**
* Only show the infobar when canRestoreLastSession and the pref value == 1
*/
async _maybeShowRestoreSessionInfoBar() {
let count = Services.prefs.getIntPref(
"browser.startup.couldRestoreSession.count",
0
);
if (count < 0 || count >= 2) {
return;
}
if (count == 0) {
// We don't show the infobar right after the update which establishes this pref
// Increment the counter so we can consider it next time
Services.prefs.setIntPref(
"browser.startup.couldRestoreSession.count",
++count
);
return;
}
const win = lazy.BrowserWindowTracker.getTopWindow();
// We've restarted at least once; we will show the notification if possible.
// We can't do that if there's no session to restore, or this is a private window.
if (
!lazy.SessionStore.canRestoreLastSession ||
lazy.PrivateBrowsingUtils.isWindowPrivate(win)
) {
return;
}
Services.prefs.setIntPref(
"browser.startup.couldRestoreSession.count",
++count
);
const messageFragment = win.document.createDocumentFragment();
const message = win.document.createElement("span");
const icon = win.document.createElement("img");
icon.src = "chrome://browser/skin/menu.svg";
icon.setAttribute("data-l10n-name", "icon");
icon.className = "inline-icon";
message.appendChild(icon);
messageFragment.appendChild(message);
win.document.l10n.setAttributes(
message,
"restore-session-startup-suggestion-message"
);
const buttons = [
{
"l10n-id": "restore-session-startup-suggestion-button",
primary: true,
callback: () => {
win.PanelUI.selectAndMarkItem([
"appMenu-history-button",
"appMenu-restoreSession",
]);
},
},
];
const notifyBox = win.gBrowser.getNotificationBox();
const notification = await notifyBox.appendNotification(
"startup-restore-session-suggestion",
{
label: messageFragment,
priority: notifyBox.PRIORITY_INFO_MEDIUM,
},
buttons
);
// Don't allow it to be immediately hidden:
notification.timeout = Date.now() + 3000;
},
/**
* Open preferences even if there are no open windows.
*/
_openPreferences(...args) {
let chromeWindow = lazy.BrowserWindowTracker.getTopWindow();
if (chromeWindow) {
chromeWindow.openPreferences(...args);
return;
}
if (AppConstants.platform == "macosx") {
Services.appShell.hiddenDOMWindow.openPreferences(...args);
}
},
QueryInterface: ChromeUtils.generateQI([
"nsIObserver",
"nsISupportsWeakReference",
]),
};