/* 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 BACKUP_STATE_PREF = "sidebar.backupState"; const VISIBILITY_SETTING_PREF = "sidebar.visibility"; const SIDEBAR_TOOLS = "sidebar.main.tools"; const VERTICAL_TABS_PREF = "sidebar.verticalTabs"; const INSTALLED_EXTENSIONS = "sidebar.installed.extensions"; const PINNED_PROMO_PREF = "sidebar.verticalTabs.dragToPinPromo.dismissed"; // New panels that are ready to be introduced to new sidebar users should be added to this list; // ensure your feature flag is enabled at the same time you do this and that its the same value as // what you added to . const DEFAULT_LAUNCHER_TOOLS = "aichat,syncedtabs,history,bookmarks"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", CustomizableUI: "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", SidebarState: "moz-src:///browser/components/sidebar/SidebarState.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarNimbus", "sidebar.nimbus"); XPCOMUtils.defineLazyPreferenceGetter( lazy, "sidebarBackupState", BACKUP_STATE_PREF ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "verticalTabsEnabled", VERTICAL_TABS_PREF, false, (pref, oldVal, newVal) => { sidebarManager.handleVerticalTabsPrefChange(newVal, true); } ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "sidebarRevampEnabled", "sidebar.revamp", false, (pref, oldVal, newVal) => { sidebarManager.updateDefaultTools(); if (!newVal) { // Disable vertical tabs if revamped sidebar is turned off Services.prefs.setBoolPref("sidebar.verticalTabs", false); } else if (newVal && !lazy.verticalTabsEnabled) { // horizontal tabs with sidebar.revamp must have visibility of "hide-sidebar" Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "hide-sidebar"); } } ); XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarTools", SIDEBAR_TOOLS, ""); XPCOMUtils.defineLazyPreferenceGetter( lazy, "sidebarExtensions", INSTALLED_EXTENSIONS, "" ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "newSidebarHasBeenUsed", "sidebar.new-sidebar.has-used", false, () => sidebarManager.updateDefaultTools() ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "dragToPinPromoDismissed", PINNED_PROMO_PREF, false ); class SidebarManager extends EventTarget { /** * SidebarManager is a singleton that handles startup tasks like telemetry, * adding listeners and updating sidebar-related preferences. */ constructor() { super(); this.checkForPinnedTabsComplete = false; } #initialized = false; init() { lazy.CustomizableUI.addListener(this); Services.prefs.addObserver( "sidebar.newTool.migration.", this.updateDefaultTools.bind(this) ); this.updateDefaultTools(); lazy.SessionStore.promiseAllWindowsRestored.then(() => { this.checkForPinnedTabs(); }); // if there's no user visibility pref, we may need to update it to the default value for the tab orientation const shouldResetVisibility = !Services.prefs.prefHasUserValue( VISIBILITY_SETTING_PREF ); this.handleVerticalTabsPrefChange( lazy.verticalTabsEnabled, shouldResetVisibility ); // Handle nimbus feature pref setting updates on init and enrollment lazy.NimbusFeatures.sidebar.onUpdate(() => { if (this.#initialized) { this.onNimbusFeatureUpdate(); } else { // Schedule handling the update after this module has finished initializing Promise.resolve().then(() => this.onNimbusFeatureUpdate()); } }); this.#initialized = true; } onNimbusFeatureUpdate() { const featureId = "sidebar"; // Set prefs only if we have an enrollment that's new const enrollment = lazy.NimbusFeatures[featureId].getEnrollmentMetadata(); if (!enrollment) { return; } const slug = enrollment.slug + ":" + enrollment.branch; if (slug == lazy.sidebarNimbus) { return; } // Enforce minimum version by skipping pref changes until Firefox restarts // with the appropriate version if ( Services.vc.compare( // Support betas, e.g., 132.0b1, instead of MOZ_APP_VERSION AppConstants.MOZ_APP_VERSION_DISPLAY, // Check configured version or compare with unset handled as 0 lazy.NimbusFeatures[featureId].getVariable("minVersion") ) < 0 ) { return; } // Set/override user prefs to persist after experiment end const setPref = (pref, value) => { // Only set prefs with a value (so no clearing) if (value != null) { lazy.PrefUtils.setPref("sidebar." + pref, value); } }; setPref("nimbus", slug); ["revamp", "verticalTabs", "visibility"].forEach(pref => setPref(pref, lazy.NimbusFeatures[featureId].getVariable(pref)) ); } /** * Ensure the drag-to-pin promo card is not displayed to existing users who already have pinned tabs. */ checkForPinnedTabs() { if (!lazy.dragToPinPromoDismissed) { for (let win of lazy.BrowserWindowTracker.getOrderedWindows()) { if (win.gBrowser.pinnedTabCount > 0) { Services.prefs.setBoolPref(PINNED_PROMO_PREF, true); break; } } } this.checkForPinnedTabsComplete = true; this.dispatchEvent(new CustomEvent("checkForPinnedTabsComplete")); } /** * Called when any widget is removed. We're only interested in the sidebar * button. Note that this is also invoked if the button is merely moved * to another area. * * @param {string} aWidgetId * The widget being removed. */ async onWidgetRemoved(aWidgetId) { if (aWidgetId == "sidebar-button") { // Wait for JS to run to completion. Once that has happened, we'll // know if we were _really_ removed or just moved elsewhere. await Promise.resolve(); if (!lazy.CustomizableUI.getPlacementOfWidget(aWidgetId)) { // Removing sidebar button should force horizontal tabs (Bug 1970015). Services.prefs.setBoolPref(VERTICAL_TABS_PREF, false); this.closeAllSidebars(); } } } /** * Convenience method to tell all sidebars to close when the toolbar button * is removed. */ closeAllSidebars() { for (let w of lazy.BrowserWindowTracker.getOrderedWindows()) { if (w.SidebarController.isOpen) { w.SidebarController.hide(); } w.SidebarController._state.loadInitialState({ ...lazy.SidebarState.defaultProperties, }); } } /** * Adjust for a change to the verticalTabs pref. */ handleVerticalTabsPrefChange(isEnabled, resetVisibility = true) { if (!isEnabled) { // horizontal tabs can only have visibility of "hide-sidebar" Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "hide-sidebar"); } else if (resetVisibility) { // only reset visibility pref when switching to vertical tabs and explictly indicated Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "always-show"); } } /** * Has the new sidebar launcher already been visible and "used" in this profile? */ get hasSidebarLauncherBeenVisible() { // Its possible sidebar.revamp was enabled previously, but we can effectively reset if its currently false if (!lazy.sidebarRevampEnabled) { return false; } if (lazy.verticalTabsEnabled) { return true; } // this pref tells us a sidebar panel has been opened, so it implies the launcher has // been visible, but can't reliably indicate that the launcher has *not* been visible. if (Services.prefs.getBoolPref("sidebar.new-sidebar.has-used", false)) { return true; } // check if the launcher has ever been visible (in this session) in any of our open windows, for (let w of lazy.BrowserWindowTracker.getOrderedWindows()) { if (w.SidebarController.launcherEverVisible) { return true; } } return false; } /** * Prepopulates default tools for new sidebar users and appends any new tools defined * on the sidebar.newTool.migration pref branch to the sidebar.main.tools pref. */ updateDefaultTools() { if (!lazy.sidebarRevampEnabled) { return; } let tools = lazy.sidebarTools; // For new sidebar.revamp users, we pre-populate a set of default tools to show in the launcher. if (!tools && !lazy.newSidebarHasBeenUsed) { tools = DEFAULT_LAUNCHER_TOOLS; } for (const pref of Services.prefs.getChildList( "sidebar.newTool.migration." )) { try { let options = JSON.parse(Services.prefs.getStringPref(pref)); let newTool = pref.split(".")[3]; if (options?.alreadyShown) { continue; } if (options?.visibilityPref) { // Will only add the tool to the launcher if the panel governing a panels sidebar visibility // is first enabled let visibilityPrefValue = Services.prefs.getBoolPref( options.visibilityPref ); if (!visibilityPrefValue) { Services.prefs.addObserver( options.visibilityPref, this.updateDefaultTools.bind(this) ); continue; } } // avoid adding a tool from the pref branch where it's already been added to the DEFAULT_LAUNCHER_TOOLS (for new users) if (!tools.includes(newTool)) { tools += "," + newTool; } options.alreadyShown = true; Services.prefs.setStringPref(pref, JSON.stringify(options)); } catch (ex) { console.error("Failed to handle pref " + pref, ex); } } if (tools.length > lazy.sidebarTools.length) { Services.prefs.setStringPref(SIDEBAR_TOOLS, tools); } } updateToolsPref(toolName, remove = null) { const updatedTools = lazy.sidebarTools ? lazy.sidebarTools.split(",") : []; const index = updatedTools.indexOf(toolName); if ((remove && index == -1) || (!remove && index != -1)) { return; } if (remove) { updatedTools.splice(index, 1); } else { updatedTools.push(toolName); } Services.prefs.setStringPref(SIDEBAR_TOOLS, updatedTools.join()); } clearExtensionsPref(toolName) { let installedExtensions = lazy.sidebarExtensions ? lazy.sidebarExtensions.split(",") : []; const index = installedExtensions.indexOf(toolName); if (index != -1) { installedExtensions.splice(index, 1); Services.prefs.setStringPref( INSTALLED_EXTENSIONS, installedExtensions.join() ); } } cleanupPrefs(id) { this.clearExtensionsPref(id); this.updateToolsPref(id, true); } /** * Return a list of tool IDs that have registered a badge for notification. * This reads all prefs under "sidebar.notification.badge." * * @returns {Array} */ getBadgeTools() { const BADGE_PREF_BRANCH = "sidebar.notification.badge."; const badgePrefs = Services.prefs.getChildList(BADGE_PREF_BRANCH); return badgePrefs.map(pref => pref.slice(BADGE_PREF_BRANCH.length)); } /** * Provide a system-level "backup" state to be stored for those using "Never * remember history" or "Clear history when browser closes". * * If it doesn't exist or isn't parsable, return `null`. * * @returns {object} */ getBackupState() { try { return JSON.parse(lazy.sidebarBackupState); } catch (e) { Services.prefs.clearUserPref(BACKUP_STATE_PREF); return null; } } /** * Set the backup state. * * @param {object} state */ setBackupState(state) { if (!state) { return; } Services.prefs.setStringPref(BACKUP_STATE_PREF, JSON.stringify(state)); } } // Initialize on first import const sidebarManager = new SidebarManager(); sidebarManager.init(); export { sidebarManager as SidebarManager };