tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SidebarManager.sys.mjs (12526B)


      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/. */
      4 
      5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      7 
      8 const BACKUP_STATE_PREF = "sidebar.backupState";
      9 const VISIBILITY_SETTING_PREF = "sidebar.visibility";
     10 const SIDEBAR_TOOLS = "sidebar.main.tools";
     11 const VERTICAL_TABS_PREF = "sidebar.verticalTabs";
     12 const INSTALLED_EXTENSIONS = "sidebar.installed.extensions";
     13 const PINNED_PROMO_PREF = "sidebar.verticalTabs.dragToPinPromo.dismissed";
     14 
     15 // New panels that are ready to be introduced to new sidebar users should be added to this list;
     16 // ensure your feature flag is enabled at the same time you do this and that its the same value as
     17 // what you added to .
     18 const DEFAULT_LAUNCHER_TOOLS = "aichat,syncedtabs,history,bookmarks";
     19 const lazy = {};
     20 ChromeUtils.defineESModuleGetters(lazy, {
     21  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
     22  CustomizableUI:
     23    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     24  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     25  PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs",
     26  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
     27  SidebarState: "moz-src:///browser/components/sidebar/SidebarState.sys.mjs",
     28 });
     29 XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarNimbus", "sidebar.nimbus");
     30 
     31 XPCOMUtils.defineLazyPreferenceGetter(
     32  lazy,
     33  "sidebarBackupState",
     34  BACKUP_STATE_PREF
     35 );
     36 
     37 XPCOMUtils.defineLazyPreferenceGetter(
     38  lazy,
     39  "verticalTabsEnabled",
     40  VERTICAL_TABS_PREF,
     41  false,
     42  (pref, oldVal, newVal) => {
     43    sidebarManager.handleVerticalTabsPrefChange(newVal, true);
     44  }
     45 );
     46 
     47 XPCOMUtils.defineLazyPreferenceGetter(
     48  lazy,
     49  "sidebarRevampEnabled",
     50  "sidebar.revamp",
     51  false,
     52  (pref, oldVal, newVal) => {
     53    sidebarManager.updateDefaultTools();
     54 
     55    if (!newVal) {
     56      // Disable vertical tabs if revamped sidebar is turned off
     57      Services.prefs.setBoolPref("sidebar.verticalTabs", false);
     58    } else if (newVal && !lazy.verticalTabsEnabled) {
     59      // horizontal tabs with sidebar.revamp must have visibility of "hide-sidebar"
     60      Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "hide-sidebar");
     61    }
     62  }
     63 );
     64 
     65 XPCOMUtils.defineLazyPreferenceGetter(lazy, "sidebarTools", SIDEBAR_TOOLS, "");
     66 XPCOMUtils.defineLazyPreferenceGetter(
     67  lazy,
     68  "sidebarExtensions",
     69  INSTALLED_EXTENSIONS,
     70  ""
     71 );
     72 
     73 XPCOMUtils.defineLazyPreferenceGetter(
     74  lazy,
     75  "newSidebarHasBeenUsed",
     76  "sidebar.new-sidebar.has-used",
     77  false,
     78  () => sidebarManager.updateDefaultTools()
     79 );
     80 
     81 XPCOMUtils.defineLazyPreferenceGetter(
     82  lazy,
     83  "dragToPinPromoDismissed",
     84  PINNED_PROMO_PREF,
     85  false
     86 );
     87 
     88 class SidebarManager extends EventTarget {
     89  /**
     90   * SidebarManager is a singleton that handles startup tasks like telemetry,
     91   * adding listeners and updating sidebar-related preferences.
     92   */
     93  constructor() {
     94    super();
     95    this.checkForPinnedTabsComplete = false;
     96  }
     97  #initialized = false;
     98  init() {
     99    lazy.CustomizableUI.addListener(this);
    100 
    101    Services.prefs.addObserver(
    102      "sidebar.newTool.migration.",
    103      this.updateDefaultTools.bind(this)
    104    );
    105    this.updateDefaultTools();
    106    lazy.SessionStore.promiseAllWindowsRestored.then(() => {
    107      this.checkForPinnedTabs();
    108    });
    109 
    110    // if there's no user visibility pref, we may need to update it to the default value for the tab orientation
    111    const shouldResetVisibility = !Services.prefs.prefHasUserValue(
    112      VISIBILITY_SETTING_PREF
    113    );
    114    this.handleVerticalTabsPrefChange(
    115      lazy.verticalTabsEnabled,
    116      shouldResetVisibility
    117    );
    118 
    119    // Handle nimbus feature pref setting updates on init and enrollment
    120    lazy.NimbusFeatures.sidebar.onUpdate(() => {
    121      if (this.#initialized) {
    122        this.onNimbusFeatureUpdate();
    123      } else {
    124        // Schedule handling the update after this module has finished initializing
    125        Promise.resolve().then(() => this.onNimbusFeatureUpdate());
    126      }
    127    });
    128    this.#initialized = true;
    129  }
    130 
    131  onNimbusFeatureUpdate() {
    132    const featureId = "sidebar";
    133    // Set prefs only if we have an enrollment that's new
    134    const enrollment = lazy.NimbusFeatures[featureId].getEnrollmentMetadata();
    135    if (!enrollment) {
    136      return;
    137    }
    138    const slug = enrollment.slug + ":" + enrollment.branch;
    139    if (slug == lazy.sidebarNimbus) {
    140      return;
    141    }
    142 
    143    // Enforce minimum version by skipping pref changes until Firefox restarts
    144    // with the appropriate version
    145    if (
    146      Services.vc.compare(
    147        // Support betas, e.g., 132.0b1, instead of MOZ_APP_VERSION
    148        AppConstants.MOZ_APP_VERSION_DISPLAY,
    149        // Check configured version or compare with unset handled as 0
    150        lazy.NimbusFeatures[featureId].getVariable("minVersion")
    151      ) < 0
    152    ) {
    153      return;
    154    }
    155 
    156    // Set/override user prefs to persist after experiment end
    157    const setPref = (pref, value) => {
    158      // Only set prefs with a value (so no clearing)
    159      if (value != null) {
    160        lazy.PrefUtils.setPref("sidebar." + pref, value);
    161      }
    162    };
    163    setPref("nimbus", slug);
    164    ["revamp", "verticalTabs", "visibility"].forEach(pref =>
    165      setPref(pref, lazy.NimbusFeatures[featureId].getVariable(pref))
    166    );
    167  }
    168 
    169  /**
    170   * Ensure the drag-to-pin promo card is not displayed to existing users who already have pinned tabs.
    171   */
    172  checkForPinnedTabs() {
    173    if (!lazy.dragToPinPromoDismissed) {
    174      for (let win of lazy.BrowserWindowTracker.getOrderedWindows()) {
    175        if (win.gBrowser.pinnedTabCount > 0) {
    176          Services.prefs.setBoolPref(PINNED_PROMO_PREF, true);
    177          break;
    178        }
    179      }
    180    }
    181    this.checkForPinnedTabsComplete = true;
    182    this.dispatchEvent(new CustomEvent("checkForPinnedTabsComplete"));
    183  }
    184 
    185  /**
    186   * Called when any widget is removed. We're only interested in the sidebar
    187   * button. Note that this is also invoked if the button is merely moved
    188   * to another area.
    189   *
    190   * @param {string} aWidgetId
    191   *   The widget being removed.
    192   */
    193  async onWidgetRemoved(aWidgetId) {
    194    if (aWidgetId == "sidebar-button") {
    195      // Wait for JS to run to completion. Once that has happened, we'll
    196      // know if we were _really_ removed or just moved elsewhere.
    197      await Promise.resolve();
    198      if (!lazy.CustomizableUI.getPlacementOfWidget(aWidgetId)) {
    199        // Removing sidebar button should force horizontal tabs (Bug 1970015).
    200        Services.prefs.setBoolPref(VERTICAL_TABS_PREF, false);
    201        this.closeAllSidebars();
    202      }
    203    }
    204  }
    205 
    206  /**
    207   * Convenience method to tell all sidebars to close when the toolbar button
    208   * is removed.
    209   */
    210  closeAllSidebars() {
    211    for (let w of lazy.BrowserWindowTracker.getOrderedWindows()) {
    212      if (w.SidebarController.isOpen) {
    213        w.SidebarController.hide();
    214      }
    215      w.SidebarController._state.loadInitialState({
    216        ...lazy.SidebarState.defaultProperties,
    217      });
    218    }
    219  }
    220 
    221  /**
    222   * Adjust for a change to the verticalTabs pref.
    223   */
    224  handleVerticalTabsPrefChange(isEnabled, resetVisibility = true) {
    225    if (!isEnabled) {
    226      // horizontal tabs can only have visibility of "hide-sidebar"
    227      Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "hide-sidebar");
    228    } else if (resetVisibility) {
    229      // only reset visibility pref when switching to vertical tabs and explictly indicated
    230      Services.prefs.setStringPref(VISIBILITY_SETTING_PREF, "always-show");
    231    }
    232  }
    233 
    234  /**
    235   * Has the new sidebar launcher already been visible and "used" in this profile?
    236   */
    237  get hasSidebarLauncherBeenVisible() {
    238    // Its possible sidebar.revamp was enabled previously, but we can effectively reset if its currently false
    239    if (!lazy.sidebarRevampEnabled) {
    240      return false;
    241    }
    242    if (lazy.verticalTabsEnabled) {
    243      return true;
    244    }
    245    // this pref tells us a sidebar panel has been opened, so it implies the launcher has
    246    // been visible, but can't reliably indicate that the launcher has *not* been visible.
    247    if (Services.prefs.getBoolPref("sidebar.new-sidebar.has-used", false)) {
    248      return true;
    249    }
    250    // check if the launcher has ever been visible (in this session) in any of our open windows,
    251    for (let w of lazy.BrowserWindowTracker.getOrderedWindows()) {
    252      if (w.SidebarController.launcherEverVisible) {
    253        return true;
    254      }
    255    }
    256    return false;
    257  }
    258 
    259  /**
    260   * Prepopulates default tools for new sidebar users and appends any new tools defined
    261   * on the sidebar.newTool.migration pref branch to the sidebar.main.tools pref.
    262   */
    263  updateDefaultTools() {
    264    if (!lazy.sidebarRevampEnabled) {
    265      return;
    266    }
    267    let tools = lazy.sidebarTools;
    268 
    269    // For new sidebar.revamp users, we pre-populate a set of default tools to show in the launcher.
    270    if (!tools && !lazy.newSidebarHasBeenUsed) {
    271      tools = DEFAULT_LAUNCHER_TOOLS;
    272    }
    273 
    274    for (const pref of Services.prefs.getChildList(
    275      "sidebar.newTool.migration."
    276    )) {
    277      try {
    278        let options = JSON.parse(Services.prefs.getStringPref(pref));
    279        let newTool = pref.split(".")[3];
    280 
    281        if (options?.alreadyShown) {
    282          continue;
    283        }
    284 
    285        if (options?.visibilityPref) {
    286          // Will only add the tool to the launcher if the panel governing a panels sidebar visibility
    287          // is first enabled
    288          let visibilityPrefValue = Services.prefs.getBoolPref(
    289            options.visibilityPref
    290          );
    291          if (!visibilityPrefValue) {
    292            Services.prefs.addObserver(
    293              options.visibilityPref,
    294              this.updateDefaultTools.bind(this)
    295            );
    296            continue;
    297          }
    298        }
    299        // avoid adding a tool from the pref branch where it's already been added to the DEFAULT_LAUNCHER_TOOLS (for new users)
    300        if (!tools.includes(newTool)) {
    301          tools += "," + newTool;
    302        }
    303        options.alreadyShown = true;
    304        Services.prefs.setStringPref(pref, JSON.stringify(options));
    305      } catch (ex) {
    306        console.error("Failed to handle pref " + pref, ex);
    307      }
    308    }
    309    if (tools.length > lazy.sidebarTools.length) {
    310      Services.prefs.setStringPref(SIDEBAR_TOOLS, tools);
    311    }
    312  }
    313 
    314  updateToolsPref(toolName, remove = null) {
    315    const updatedTools = lazy.sidebarTools ? lazy.sidebarTools.split(",") : [];
    316    const index = updatedTools.indexOf(toolName);
    317 
    318    if ((remove && index == -1) || (!remove && index != -1)) {
    319      return;
    320    }
    321 
    322    if (remove) {
    323      updatedTools.splice(index, 1);
    324    } else {
    325      updatedTools.push(toolName);
    326    }
    327 
    328    Services.prefs.setStringPref(SIDEBAR_TOOLS, updatedTools.join());
    329  }
    330 
    331  clearExtensionsPref(toolName) {
    332    let installedExtensions = lazy.sidebarExtensions
    333      ? lazy.sidebarExtensions.split(",")
    334      : [];
    335    const index = installedExtensions.indexOf(toolName);
    336    if (index != -1) {
    337      installedExtensions.splice(index, 1);
    338      Services.prefs.setStringPref(
    339        INSTALLED_EXTENSIONS,
    340        installedExtensions.join()
    341      );
    342    }
    343  }
    344 
    345  cleanupPrefs(id) {
    346    this.clearExtensionsPref(id);
    347    this.updateToolsPref(id, true);
    348  }
    349 
    350  /**
    351   * Return a list of tool IDs that have registered a badge for notification.
    352   * This reads all prefs under "sidebar.notification.badge."
    353   *
    354   * @returns {Array}
    355   */
    356  getBadgeTools() {
    357    const BADGE_PREF_BRANCH = "sidebar.notification.badge.";
    358    const badgePrefs = Services.prefs.getChildList(BADGE_PREF_BRANCH);
    359 
    360    return badgePrefs.map(pref => pref.slice(BADGE_PREF_BRANCH.length));
    361  }
    362 
    363  /**
    364   * Provide a system-level "backup" state to be stored for those using "Never
    365   * remember history" or "Clear history when browser closes".
    366   *
    367   * If it doesn't exist or isn't parsable, return `null`.
    368   *
    369   * @returns {object}
    370   */
    371  getBackupState() {
    372    try {
    373      return JSON.parse(lazy.sidebarBackupState);
    374    } catch (e) {
    375      Services.prefs.clearUserPref(BACKUP_STATE_PREF);
    376      return null;
    377    }
    378  }
    379 
    380  /**
    381   * Set the backup state.
    382   *
    383   * @param {object} state
    384   */
    385  setBackupState(state) {
    386    if (!state) {
    387      return;
    388    }
    389    Services.prefs.setStringPref(BACKUP_STATE_PREF, JSON.stringify(state));
    390  }
    391 }
    392 
    393 // Initialize on first import
    394 const sidebarManager = new SidebarManager();
    395 sidebarManager.init();
    396 export { sidebarManager as SidebarManager };