tor-browser

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

home.js (34076B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* import-globals-from extensionControlled.js */
      6 /* import-globals-from preferences.js */
      7 /* import-globals-from main.js */
      8 
      9 // HOME PAGE
     10 
     11 ChromeUtils.defineESModuleGetters(this, {
     12  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
     13 });
     14 
     15 /*
     16 * Preferences:
     17 *
     18 * browser.startup.homepage
     19 * - the user's home page, as a string; if the home page is a set of tabs,
     20 *   this will be those URLs separated by the pipe character "|"
     21 * browser.newtabpage.enabled
     22 * - determines that is shown on the user's new tab page.
     23 *   true = Activity Stream is shown,
     24 *   false = about:blank is shown
     25 */
     26 
     27 const DEFAULT_HOMEPAGE_URL = "about:home";
     28 const BLANK_HOMEPAGE_URL = "chrome://browser/content/blanktab.html";
     29 
     30 Preferences.addAll([
     31  { id: "browser.startup.homepage", type: "string" },
     32  { id: "pref.browser.homepage.disable_button.current_page", type: "bool" },
     33  { id: "pref.browser.homepage.disable_button.bookmark_page", type: "bool" },
     34  {
     35    id: "pref.browser.homepage.disable_button.restore_default",
     36    type: "bool",
     37  },
     38  { id: "browser.newtabpage.enabled", type: "bool" },
     39 ]);
     40 
     41 if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) {
     42  // Homepage / New Windows
     43  Preferences.addSetting(
     44    /** @type {{ useCustomHomepage: boolean } & SettingConfig } */ ({
     45      id: "homepageNewWindows",
     46      pref: "browser.startup.homepage",
     47      useCustomHomepage: false,
     48      get(prefVal) {
     49        if (this.useCustomHomepage) {
     50          return "custom";
     51        }
     52        switch (prefVal) {
     53          case DEFAULT_HOMEPAGE_URL:
     54            return "home";
     55          case BLANK_HOMEPAGE_URL:
     56            return "blank";
     57          // Bug 1969951 - Custom value can be any string so leaving it as default value to catch
     58          // non-default/blank entires.
     59          default:
     60            return "custom";
     61        }
     62      },
     63      set(inputVal, _, setting) {
     64        let wasCustomHomepage = this.useCustomHomepage;
     65        this.useCustomHomepage = inputVal == "custom";
     66        if (wasCustomHomepage != this.useCustomHomepage) {
     67          setting.onChange();
     68        }
     69        switch (inputVal) {
     70          case "home":
     71            return DEFAULT_HOMEPAGE_URL;
     72          case "blank":
     73            return BLANK_HOMEPAGE_URL;
     74          case "custom":
     75            // Bug 1969951 - Add values set in subpage here
     76            return setting.pref.value;
     77          default:
     78            throw new Error("No handler for this value");
     79        }
     80      },
     81    })
     82  );
     83 
     84  // Homepage / Choose Custom Homepage URL Button
     85  Preferences.addSetting({
     86    id: "homepageGoToCustomHomepageUrlPanel",
     87    deps: ["homepageNewWindows"],
     88    visible: ({ homepageNewWindows }) => {
     89      return homepageNewWindows.value == "custom";
     90    },
     91    onUserClick: () => {
     92      // Bug 1969951 - Navigate to Custom Homepage Subpage
     93    },
     94  });
     95 
     96  // Homepage / New Tabs
     97  Preferences.addSetting({
     98    id: "homepageNewTabs",
     99    pref: "browser.newtabpage.enabled",
    100    get(prefVal) {
    101      return prefVal.toString();
    102    },
    103    set(inputVal) {
    104      return inputVal === "true";
    105    },
    106  });
    107 
    108  // Homepage / Restore Defaults button
    109  Preferences.addSetting({
    110    id: "homepageRestoreDefaults",
    111    pref: "pref.browser.homepage.disable_button.restore_default",
    112    deps: ["homepageNewWindows", "homepageNewTabs"],
    113    disabled: ({ homepageNewWindows, homepageNewTabs }) => {
    114      return (
    115        homepageNewWindows.value === "home" && homepageNewTabs.value === "true"
    116      );
    117    },
    118    onUserClick: (e, { homepageNewWindows, homepageNewTabs }) => {
    119      e.preventDefault();
    120 
    121      // Bug 1969951 - This is temporary until the custom URL subpage is implemented.
    122      // Once users can set custom URLs in the subpage, this will properly reset those values.
    123      homepageNewWindows.value = "home";
    124      homepageNewTabs.value = "true";
    125    },
    126  });
    127 }
    128 
    129 const HOMEPAGE_OVERRIDE_KEY = "homepage_override";
    130 const URL_OVERRIDES_TYPE = "url_overrides";
    131 const NEW_TAB_KEY = "newTabURL";
    132 
    133 // New Prefs UI: we need to check for this setting before registering prefs
    134 // so that old-style prefs continue working
    135 if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) {
    136  Preferences.addAll([
    137    { id: "browser.newtabpage.activity-stream.showSearch", type: "bool" },
    138    {
    139      id: "browser.newtabpage.activity-stream.system.showWeather",
    140      type: "bool",
    141    },
    142    { id: "browser.newtabpage.activity-stream.showWeather", type: "bool" },
    143    {
    144      id: "browser.newtabpage.activity-stream.widgets.system.enabled",
    145      type: "bool",
    146    },
    147    {
    148      id: "browser.newtabpage.activity-stream.widgets.enabled",
    149      type: "bool",
    150    },
    151    {
    152      id: "browser.newtabpage.activity-stream.widgets.system.lists.enabled",
    153      type: "bool",
    154    },
    155    {
    156      id: "browser.newtabpage.activity-stream.widgets.lists.enabled",
    157      type: "bool",
    158    },
    159    {
    160      id: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled",
    161      type: "bool",
    162    },
    163    {
    164      id: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled",
    165      type: "bool",
    166    },
    167    {
    168      id: "browser.newtabpage.activity-stream.feeds.topsites",
    169      type: "bool",
    170    },
    171    {
    172      id: "browser.newtabpage.activity-stream.topSitesRows",
    173      type: "int",
    174    },
    175    {
    176      id: "browser.newtabpage.activity-stream.feeds.system.topstories",
    177      type: "bool",
    178    },
    179    {
    180      id: "browser.newtabpage.activity-stream.feeds.section.topstories",
    181      type: "bool",
    182    },
    183    {
    184      id: "browser.newtabpage.activity-stream.discoverystream.sections.enabled",
    185      type: "bool",
    186    },
    187    {
    188      id: "browser.newtabpage.activity-stream.discoverystream.topicLabels.enabled",
    189      type: "bool",
    190    },
    191    {
    192      id: "browser.newtabpage.activity-stream.discoverystream.sections.personalization.enabled",
    193      type: "bool",
    194    },
    195    {
    196      id: "browser.newtabpage.activity-stream.discoverystream.sections.customizeMenuPanel.enabled",
    197      type: "bool",
    198    },
    199    {
    200      id: "browser.newtabpage.activity-stream.showSponsoredCheckboxes",
    201      type: "bool",
    202    },
    203    {
    204      id: "browser.newtabpage.activity-stream.showSponsoredTopSites",
    205      type: "bool",
    206    },
    207    {
    208      id: "browser.newtabpage.activity-stream.showSponsored",
    209      type: "bool",
    210    },
    211    {
    212      id: "browser.newtabpage.activity-stream.feeds.section.highlights",
    213      type: "bool",
    214    },
    215    {
    216      id: "browser.newtabpage.activity-stream.section.highlights.rows",
    217      type: "int",
    218    },
    219    {
    220      id: "browser.newtabpage.activity-stream.section.highlights.includeVisited",
    221      type: "bool",
    222    },
    223    {
    224      id: "browser.newtabpage.activity-stream.section.highlights.includeBookmarks",
    225      type: "bool",
    226    },
    227    {
    228      id: "browser.newtabpage.activity-stream.section.highlights.includeDownloads",
    229      type: "bool",
    230    },
    231  ]);
    232 
    233  // Search
    234  Preferences.addSetting({
    235    id: "webSearch",
    236    pref: "browser.newtabpage.activity-stream.showSearch",
    237  });
    238 
    239  // Weather
    240  Preferences.addSetting({
    241    id: "showWeather",
    242    pref: "browser.newtabpage.activity-stream.system.showWeather",
    243  });
    244 
    245  Preferences.addSetting({
    246    id: "weather",
    247    pref: "browser.newtabpage.activity-stream.showWeather",
    248    deps: ["showWeather"],
    249    visible: ({ showWeather }) => showWeather.value,
    250  });
    251 
    252  // Widgets: general
    253  Preferences.addSetting({
    254    id: "widgetsEnabled",
    255    pref: "browser.newtabpage.activity-stream.widgets.system.enabled",
    256  });
    257 
    258  Preferences.addSetting({
    259    id: "widgets",
    260    pref: "browser.newtabpage.activity-stream.widgets.enabled",
    261    deps: ["widgetsEnabled"],
    262    visible: ({ widgetsEnabled }) => widgetsEnabled.value,
    263  });
    264 
    265  // Widgets: lists
    266  Preferences.addSetting({
    267    id: "listsEnabled",
    268    pref: "browser.newtabpage.activity-stream.widgets.system.lists.enabled",
    269  });
    270 
    271  Preferences.addSetting({
    272    id: "lists",
    273    pref: "browser.newtabpage.activity-stream.widgets.lists.enabled",
    274    deps: ["listsEnabled"],
    275    visible: ({ listsEnabled }) => listsEnabled.value,
    276  });
    277 
    278  // Widgets: timer
    279  Preferences.addSetting({
    280    id: "timerEnabled",
    281    pref: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled",
    282  });
    283 
    284  Preferences.addSetting({
    285    id: "timer",
    286    pref: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled",
    287    deps: ["timerEnabled"],
    288    visible: ({ timerEnabled }) => timerEnabled.value,
    289  });
    290 
    291  // Shortcuts
    292  Preferences.addSetting({
    293    id: "shortcuts",
    294    pref: "browser.newtabpage.activity-stream.feeds.topsites",
    295  });
    296  Preferences.addSetting({
    297    id: "shortcutsRows",
    298    pref: "browser.newtabpage.activity-stream.topSitesRows",
    299  });
    300 
    301  // Stories
    302  Preferences.addSetting({
    303    id: "stories",
    304    pref: "browser.newtabpage.activity-stream.feeds.section.topstories",
    305  });
    306  Preferences.addSetting({
    307    id: "sectionsEnabled",
    308    pref: "browser.newtabpage.activity-stream.discoverystream.sections.enabled",
    309  });
    310  Preferences.addSetting({
    311    id: "topicLabelsEnabled",
    312    pref: "browser.newtabpage.activity-stream.discoverystream.topicLabels.enabled",
    313  });
    314  Preferences.addSetting({
    315    id: "sectionsPersonalizationEnabled",
    316    pref: "browser.newtabpage.activity-stream.discoverystream.sections.personalization.enabled",
    317  });
    318  Preferences.addSetting({
    319    id: "sectionsCustomizeMenuPanelEnabled",
    320    pref: "browser.newtabpage.activity-stream.discoverystream.sections.customizeMenuPanel.enabled",
    321  });
    322  Preferences.addSetting({
    323    id: "manageTopics",
    324    deps: [
    325      "sectionsEnabled",
    326      "topicLabelsEnabled",
    327      "sectionsPersonalizationEnabled",
    328      "sectionsCustomizeMenuPanelEnabled",
    329      "sectionTopstories",
    330    ],
    331    visible: ({
    332      sectionsEnabled,
    333      topicLabelsEnabled,
    334      sectionsPersonalizationEnabled,
    335      sectionsCustomizeMenuPanelEnabled,
    336      sectionTopstories,
    337    }) =>
    338      sectionsEnabled.value &&
    339      topicLabelsEnabled.value &&
    340      sectionsPersonalizationEnabled.value &&
    341      sectionsCustomizeMenuPanelEnabled.value &&
    342      sectionTopstories.value,
    343  });
    344 
    345  // Dependency prefs for sponsored stories visibility
    346  Preferences.addSetting({
    347    id: "systemTopstories",
    348    pref: "browser.newtabpage.activity-stream.feeds.system.topstories",
    349  });
    350  Preferences.addSetting({
    351    id: "sectionTopstories",
    352    pref: "browser.newtabpage.activity-stream.feeds.section.topstories",
    353  });
    354 
    355  // Support Firefox: sponsored content
    356  Preferences.addSetting({
    357    id: "supportFirefox",
    358    pref: "browser.newtabpage.activity-stream.showSponsoredCheckboxes",
    359    deps: ["sponsoredShortcuts", "sponsoredStories"],
    360    onUserChange(value, { sponsoredShortcuts, sponsoredStories }) {
    361      // When supportFirefox changes, automatically update child preferences to match
    362      sponsoredShortcuts.value = !!value;
    363      sponsoredStories.value = !!value;
    364    },
    365  });
    366  Preferences.addSetting({
    367    id: "topsitesEnabled",
    368    pref: "browser.newtabpage.activity-stream.feeds.topsites",
    369  });
    370  Preferences.addSetting({
    371    id: "sponsoredShortcuts",
    372    pref: "browser.newtabpage.activity-stream.showSponsoredTopSites",
    373    deps: ["topsitesEnabled"],
    374    disabled: ({ topsitesEnabled }) => !topsitesEnabled.value,
    375  });
    376  Preferences.addSetting({
    377    id: "sponsoredStories",
    378    pref: "browser.newtabpage.activity-stream.showSponsored",
    379    deps: ["systemTopstories", "sectionTopstories"],
    380    visible: ({ systemTopstories }) => !!systemTopstories.value,
    381    disabled: ({ sectionTopstories }) => !sectionTopstories.value,
    382  });
    383  Preferences.addSetting({
    384    id: "supportFirefoxPromo",
    385    deps: ["supportFirefox"],
    386  });
    387 
    388  // Recent activity
    389  Preferences.addSetting({
    390    id: "recentActivity",
    391    pref: "browser.newtabpage.activity-stream.feeds.section.highlights",
    392  });
    393  Preferences.addSetting({
    394    id: "recentActivityRows",
    395    pref: "browser.newtabpage.activity-stream.section.highlights.rows",
    396  });
    397  Preferences.addSetting({
    398    id: "recentActivityVisited",
    399    pref: "browser.newtabpage.activity-stream.section.highlights.includeVisited",
    400  });
    401  Preferences.addSetting({
    402    id: "recentActivityBookmarks",
    403    pref: "browser.newtabpage.activity-stream.section.highlights.includeBookmarks",
    404  });
    405  Preferences.addSetting({
    406    id: "recentActivityDownloads",
    407    pref: "browser.newtabpage.activity-stream.section.highlights.includeDownloads",
    408  });
    409 
    410  Preferences.addSetting({
    411    id: "chooseWallpaper",
    412  });
    413 }
    414 
    415 var gHomePane = {
    416  HOME_MODE_FIREFOX_HOME: "0",
    417  HOME_MODE_BLANK: "1",
    418  HOME_MODE_CUSTOM: "2",
    419  HOMEPAGE_PREF: "browser.startup.homepage",
    420  NEWTAB_ENABLED_PREF: "browser.newtabpage.enabled",
    421  ACTIVITY_STREAM_PREF_BRANCH: "browser.newtabpage.activity-stream.",
    422 
    423  get homePanePrefs() {
    424    return Preferences.getAll().filter(pref =>
    425      pref.id.includes(this.ACTIVITY_STREAM_PREF_BRANCH)
    426    );
    427  },
    428 
    429  get isPocketNewtabEnabled() {
    430    const value = Services.prefs.getStringPref(
    431      "browser.newtabpage.activity-stream.discoverystream.config",
    432      ""
    433    );
    434    if (value) {
    435      try {
    436        return JSON.parse(value).enabled;
    437      } catch (e) {
    438        console.error("Failed to parse Discovery Stream pref.");
    439      }
    440    }
    441 
    442    return false;
    443  },
    444 
    445  async syncToNewTabPref() {
    446    let menulist = document.getElementById("newTabMode");
    447 
    448    if (["0", "1"].includes(menulist.value)) {
    449      let newtabEnabledPref = Services.prefs.getBoolPref(
    450        this.NEWTAB_ENABLED_PREF,
    451        true
    452      );
    453      let newValue = menulist.value !== this.HOME_MODE_BLANK;
    454      // Only set this if the pref has changed, otherwise the pref change will trigger other listeners to repeat.
    455      if (newtabEnabledPref !== newValue) {
    456        Services.prefs.setBoolPref(this.NEWTAB_ENABLED_PREF, newValue);
    457      }
    458      let selectedAddon = ExtensionSettingsStore.getSetting(
    459        URL_OVERRIDES_TYPE,
    460        NEW_TAB_KEY
    461      );
    462      if (selectedAddon) {
    463        ExtensionSettingsStore.select(null, URL_OVERRIDES_TYPE, NEW_TAB_KEY);
    464      }
    465    } else {
    466      let addon = await AddonManager.getAddonByID(menulist.value);
    467      if (addon && addon.isActive) {
    468        ExtensionSettingsStore.select(
    469          addon.id,
    470          URL_OVERRIDES_TYPE,
    471          NEW_TAB_KEY
    472        );
    473      }
    474    }
    475  },
    476 
    477  async syncFromNewTabPref() {
    478    let menulist = document.getElementById("newTabMode");
    479 
    480    // If the new tab url was changed to about:blank or about:newtab
    481    if (
    482      AboutNewTab.newTabURL === "about:newtab" ||
    483      AboutNewTab.newTabURL === "about:blank" ||
    484      AboutNewTab.newTabURL === BLANK_HOMEPAGE_URL
    485    ) {
    486      let newtabEnabledPref = Services.prefs.getBoolPref(
    487        this.NEWTAB_ENABLED_PREF,
    488        true
    489      );
    490      let newValue = newtabEnabledPref
    491        ? this.HOME_MODE_FIREFOX_HOME
    492        : this.HOME_MODE_BLANK;
    493      if (newValue !== menulist.value) {
    494        menulist.value = newValue;
    495      }
    496      menulist.disabled = Preferences.get(this.NEWTAB_ENABLED_PREF).locked;
    497      // If change was triggered by installing an addon we need to update
    498      // the value of the menulist to be that addon.
    499    } else {
    500      let selectedAddon = ExtensionSettingsStore.getSetting(
    501        URL_OVERRIDES_TYPE,
    502        NEW_TAB_KEY
    503      );
    504      if (selectedAddon && menulist.value !== selectedAddon.id) {
    505        menulist.value = selectedAddon.id;
    506      }
    507    }
    508  },
    509 
    510  /**
    511   *  _updateMenuInterface: adds items to or removes them from the menulists
    512   *
    513   * @param {string} selectId Optional Id of the menulist to add or remove items from.
    514   *                          If not included this will update both home and newtab menus.
    515   */
    516  async _updateMenuInterface(selectId) {
    517    let selects;
    518    if (selectId) {
    519      selects = [document.getElementById(selectId)];
    520    } else {
    521      let newTabSelect = document.getElementById("newTabMode");
    522      let homeSelect = document.getElementById("homeMode");
    523      selects = [homeSelect, newTabSelect];
    524    }
    525 
    526    for (let select of selects) {
    527      // Remove addons from the menu popup which are no longer installed, or disabled.
    528      // let menuOptions = select.menupopup.childNodes;
    529      let menuOptions = Array.from(select.menupopup.childNodes);
    530 
    531      for (let option of menuOptions) {
    532        // If the value is not a number, assume it is an addon ID
    533        if (!/^\d+$/.test(option.value)) {
    534          let addon = await AddonManager.getAddonByID(option.value);
    535          if (option && (!addon || !addon.isActive)) {
    536            option.remove();
    537          }
    538        }
    539      }
    540 
    541      let extensionOptions;
    542      await ExtensionSettingsStore.initialize();
    543      if (select.id === "homeMode") {
    544        extensionOptions = ExtensionSettingsStore.getAllSettings(
    545          PREF_SETTING_TYPE,
    546          HOMEPAGE_OVERRIDE_KEY
    547        );
    548      } else {
    549        extensionOptions = ExtensionSettingsStore.getAllSettings(
    550          URL_OVERRIDES_TYPE,
    551          NEW_TAB_KEY
    552        );
    553      }
    554      let addons = await AddonManager.getAddonsByIDs(
    555        extensionOptions.map(a => a.id)
    556      );
    557 
    558      // Add addon options to the menu popups
    559      let menupopup = select.querySelector("menupopup");
    560      for (let addon of addons) {
    561        if (!addon || !addon.id || !addon.isActive) {
    562          continue;
    563        }
    564        let currentOption = select.querySelector(
    565          `[value="${CSS.escape(addon.id)}"]`
    566        );
    567        if (!currentOption) {
    568          let option = document.createXULElement("menuitem");
    569          option.value = addon.id;
    570          option.label = addon.name;
    571          menupopup.append(option);
    572        }
    573        let setting = extensionOptions.find(o => o.id == addon.id);
    574        if (
    575          (select.id === "homeMode" && setting.value == HomePage.get()) ||
    576          (select.id === "newTabMode" && setting.value == AboutNewTab.newTabURL)
    577        ) {
    578          select.value = addon.id;
    579        }
    580      }
    581    }
    582  },
    583 
    584  /**
    585   * watchNewTab: Listen for changes to the new tab url and enable/disable appropriate
    586   * areas of the UI.
    587   */
    588  watchNewTab() {
    589    let newTabObserver = () => {
    590      this.syncFromNewTabPref();
    591      this._updateMenuInterface("newTabMode");
    592    };
    593    Services.obs.addObserver(newTabObserver, "newtab-url-changed");
    594    window.addEventListener("unload", () => {
    595      Services.obs.removeObserver(newTabObserver, "newtab-url-changed");
    596    });
    597  },
    598 
    599  /**
    600   * watchHomePrefChange: Listen for preferences changes on the Home Tab in order to
    601   * show the appropriate home menu selection.
    602   */
    603  watchHomePrefChange() {
    604    const homePrefObserver = (subject, topic, data) => {
    605      // only update this UI if it is exactly the HOMEPAGE_PREF, not other prefs with the same root.
    606      if (data && data != this.HOMEPAGE_PREF) {
    607        return;
    608      }
    609      this._updateUseCurrentButton();
    610      this._renderCustomSettings();
    611      this._handleHomePageOverrides();
    612      this._updateMenuInterface("homeMode");
    613    };
    614 
    615    Services.prefs.addObserver(this.HOMEPAGE_PREF, homePrefObserver);
    616    window.addEventListener("unload", () => {
    617      Services.prefs.removeObserver(this.HOMEPAGE_PREF, homePrefObserver);
    618    });
    619  },
    620 
    621  /**
    622   * Listen extension changes on the New Tab and Home Tab
    623   * in order to update the UI and show or hide the Restore Defaults button.
    624   */
    625  watchExtensionPrefChange() {
    626    const extensionSettingChanged = (evt, setting) => {
    627      if (setting.key == "homepage_override" && setting.type == "prefs") {
    628        this._updateMenuInterface("homeMode");
    629      } else if (
    630        setting.key == "newTabURL" &&
    631        setting.type == "url_overrides"
    632      ) {
    633        this._updateMenuInterface("newTabMode");
    634      }
    635    };
    636 
    637    Management.on("extension-setting-changed", extensionSettingChanged);
    638    window.addEventListener("unload", () => {
    639      Management.off("extension-setting-changed", extensionSettingChanged);
    640    });
    641  },
    642 
    643  /**
    644   * Listen for all preferences changes on the Home Tab in order to show or
    645   * hide the Restore Defaults button.
    646   */
    647  watchHomeTabPrefChange() {
    648    const observer = () => this.toggleRestoreDefaultsBtn();
    649    Services.prefs.addObserver(this.ACTIVITY_STREAM_PREF_BRANCH, observer);
    650    Services.prefs.addObserver(this.HOMEPAGE_PREF, observer);
    651    Services.prefs.addObserver(this.NEWTAB_ENABLED_PREF, observer);
    652 
    653    window.addEventListener("unload", () => {
    654      Services.prefs.removeObserver(this.ACTIVITY_STREAM_PREF_BRANCH, observer);
    655      Services.prefs.removeObserver(this.HOMEPAGE_PREF, observer);
    656      Services.prefs.removeObserver(this.NEWTAB_ENABLED_PREF, observer);
    657    });
    658  },
    659 
    660  /**
    661   * _renderCustomSettings: Hides or shows the UI for setting a custom
    662   * homepage URL
    663   *
    664   * @param {obj} options
    665   * @param {bool} options.shouldShow Should the custom UI be shown?
    666   * @param {bool} options.isControlled Is an extension controlling the home page?
    667   */
    668  _renderCustomSettings(options = {}) {
    669    let { shouldShow, isControlled } = options;
    670    const customSettingsContainerEl = document.getElementById("customSettings");
    671    const customUrlEl = document.getElementById("homePageUrl");
    672    const homePage = HomePage.get();
    673    const isHomePageCustom =
    674      (!this._isHomePageDefaultValue() &&
    675        !this.isHomePageBlank() &&
    676        !isControlled) ||
    677      homePage.locked;
    678 
    679    if (typeof shouldShow === "undefined") {
    680      shouldShow = isHomePageCustom;
    681    }
    682    customSettingsContainerEl.hidden = !shouldShow;
    683 
    684    // We can't use isHomePageDefaultValue and isHomePageBlank here because we want to disregard the blank
    685    // possibility triggered by the browser.startup.page being 0.
    686    // We also skip when HomePage is locked because it might be locked to a default that isn't "about:home"
    687    // (and it makes existing tests happy).
    688    let newValue;
    689    if (
    690      this._isBlankPage(homePage) ||
    691      (HomePage.isDefault && !HomePage.locked)
    692    ) {
    693      newValue = "";
    694    } else {
    695      newValue = homePage;
    696    }
    697    if (customUrlEl.value !== newValue) {
    698      customUrlEl.value = newValue;
    699    }
    700  },
    701 
    702  /**
    703   * _isHomePageDefaultValue
    704   *
    705   * @returns {bool} Is the homepage set to the default pref value?
    706   */
    707  _isHomePageDefaultValue() {
    708    const startupPref = Preferences.get("browser.startup.page");
    709    return (
    710      startupPref.value !== gMainPane.STARTUP_PREF_BLANK && HomePage.isDefault
    711    );
    712  },
    713 
    714  /**
    715   * isHomePageBlank
    716   *
    717   * @returns {bool} Is the homepage set to about:blank?
    718   */
    719  isHomePageBlank() {
    720    const startupPref = Preferences.get("browser.startup.page");
    721    return (
    722      ["about:blank", BLANK_HOMEPAGE_URL, ""].includes(HomePage.get()) ||
    723      startupPref.value === gMainPane.STARTUP_PREF_BLANK
    724    );
    725  },
    726 
    727  /**
    728   * _isTabAboutPreferencesOrSettings: Is a given tab set to about:preferences or about:settings?
    729   *
    730   * @param {Element} aTab A tab element
    731   * @returns {bool} Is the linkedBrowser of aElement set to about:preferences or about:settings?
    732   */
    733  _isTabAboutPreferencesOrSettings(aTab) {
    734    return (
    735      aTab.linkedBrowser.currentURI.spec.startsWith("about:preferences") ||
    736      aTab.linkedBrowser.currentURI.spec.startsWith("about:settings")
    737    );
    738  },
    739 
    740  /**
    741   * _getTabsForHomePage
    742   *
    743   * @returns {Array} An array of current tabs
    744   */
    745  _getTabsForHomePage() {
    746    let tabs = [];
    747    let win = Services.wm.getMostRecentWindow("navigator:browser");
    748 
    749    // We should only include visible & non-pinned tabs
    750    if (
    751      win &&
    752      win.document.documentElement.getAttribute("windowtype") ===
    753        "navigator:browser"
    754    ) {
    755      tabs = win.gBrowser.visibleTabs.slice(win.gBrowser.pinnedTabCount);
    756      tabs = tabs.filter(tab => !this._isTabAboutPreferencesOrSettings(tab));
    757      // XXX: Bug 1441637 - Fix tabbrowser to report tab.closing before it blurs it
    758      tabs = tabs.filter(tab => !tab.closing);
    759    }
    760 
    761    return tabs;
    762  },
    763 
    764  _renderHomepageMode(controllingExtension) {
    765    const isDefault = this._isHomePageDefaultValue();
    766    const isBlank = this.isHomePageBlank();
    767    const el = document.getElementById("homeMode");
    768    let newValue;
    769 
    770    if (controllingExtension && controllingExtension.id) {
    771      newValue = controllingExtension.id;
    772    } else if (isBlank) {
    773      // For base-browser, we want to check isBlank first since the default page
    774      // is also the blank page, but we only have a menu option for
    775      // HOME_MODE_BLANK, rather than HOME_MODE_FIREFOX_HOME.
    776      // See tor-browser#41609.
    777      newValue = this.HOME_MODE_BLANK;
    778    } else if (isDefault) {
    779      newValue = this.HOME_MODE_FIREFOX_HOME;
    780    } else {
    781      newValue = this.HOME_MODE_CUSTOM;
    782    }
    783    if (el.value !== newValue) {
    784      el.value = newValue;
    785    }
    786  },
    787 
    788  _setInputDisabledStates(isControlled) {
    789    let tabCount = this._getTabsForHomePage().length;
    790 
    791    // Disable or enable the inputs based on if this is controlled by an extension.
    792    document
    793      .querySelectorAll(".check-home-page-controlled")
    794      .forEach(element => {
    795        let isDisabled;
    796        let pref =
    797          element.getAttribute("preference") ||
    798          element.getAttribute("data-preference-related");
    799        if (!pref) {
    800          throw new Error(
    801            `Element with id ${element.id} did not have preference or data-preference-related attribute defined.`
    802          );
    803        }
    804 
    805        if (pref === this.HOMEPAGE_PREF) {
    806          isDisabled = HomePage.locked;
    807        } else {
    808          isDisabled = Preferences.get(pref).locked || isControlled;
    809        }
    810 
    811        if (pref === "pref.browser.disable_button.current_page") {
    812          // Special case for current_page to disable it if tabCount is 0
    813          isDisabled = isDisabled || tabCount < 1;
    814        }
    815 
    816        element.disabled = isDisabled;
    817      });
    818  },
    819 
    820  async _handleHomePageOverrides() {
    821    let controllingExtension;
    822    if (HomePage.locked) {
    823      // Disable inputs if they are locked.
    824      this._renderCustomSettings();
    825      this._setInputDisabledStates(false);
    826    } else {
    827      if (ExtensionUtils.isExtensionUrl(HomePage.get())) {
    828        controllingExtension = await getControllingExtension(
    829          PREF_SETTING_TYPE,
    830          HOMEPAGE_OVERRIDE_KEY
    831        );
    832      }
    833      this._setInputDisabledStates();
    834      this._renderCustomSettings({
    835        isControlled: !!controllingExtension,
    836      });
    837    }
    838    this._renderHomepageMode(controllingExtension);
    839  },
    840 
    841  onMenuChange(event) {
    842    const { value } = event.target;
    843    const startupPref = Preferences.get("browser.startup.page");
    844    let selectedAddon = ExtensionSettingsStore.getSetting(
    845      PREF_SETTING_TYPE,
    846      HOMEPAGE_OVERRIDE_KEY
    847    );
    848 
    849    switch (value) {
    850      case this.HOME_MODE_FIREFOX_HOME:
    851        if (startupPref.value === gMainPane.STARTUP_PREF_BLANK) {
    852          startupPref.value = gMainPane.STARTUP_PREF_HOMEPAGE;
    853        }
    854        if (!HomePage.isDefault) {
    855          HomePage.reset();
    856        } else {
    857          this._renderCustomSettings({ shouldShow: false });
    858        }
    859        if (selectedAddon) {
    860          ExtensionSettingsStore.select(
    861            null,
    862            PREF_SETTING_TYPE,
    863            HOMEPAGE_OVERRIDE_KEY
    864          );
    865        }
    866        break;
    867      case this.HOME_MODE_BLANK:
    868        if (!this._isBlankPage(HomePage.get())) {
    869          HomePage.safeSet(BLANK_HOMEPAGE_URL);
    870        } else {
    871          this._renderCustomSettings({ shouldShow: false });
    872        }
    873        if (selectedAddon) {
    874          ExtensionSettingsStore.select(
    875            null,
    876            PREF_SETTING_TYPE,
    877            HOMEPAGE_OVERRIDE_KEY
    878          );
    879        }
    880        break;
    881      case this.HOME_MODE_CUSTOM:
    882        if (startupPref.value === gMainPane.STARTUP_PREF_BLANK) {
    883          Services.prefs.clearUserPref(startupPref.id);
    884        }
    885        if (HomePage.getDefault() != HomePage.getOriginalDefault()) {
    886          HomePage.clear();
    887        }
    888        this._renderCustomSettings({ shouldShow: true });
    889        if (selectedAddon) {
    890          ExtensionSettingsStore.select(
    891            null,
    892            PREF_SETTING_TYPE,
    893            HOMEPAGE_OVERRIDE_KEY
    894          );
    895        }
    896        break;
    897      // extensions will have a variety of values as their ID, so treat it as default
    898      default:
    899        AddonManager.getAddonByID(value).then(addon => {
    900          if (addon && addon.isActive) {
    901            ExtensionPreferencesManager.selectSetting(
    902              addon.id,
    903              HOMEPAGE_OVERRIDE_KEY
    904            );
    905          }
    906          this._renderCustomSettings({ shouldShow: false });
    907        });
    908    }
    909  },
    910 
    911  /**
    912   * Switches the "Use Current Page" button between its singular and plural
    913   * forms.
    914   */
    915  async _updateUseCurrentButton() {
    916    let useCurrent = document.getElementById("useCurrentBtn");
    917    let tabs = this._getTabsForHomePage();
    918    const tabCount = tabs.length;
    919    document.l10n.setAttributes(useCurrent, "use-current-pages", { tabCount });
    920 
    921    // If the homepage is controlled by an extension then you can't use this.
    922    if (
    923      await getControllingExtensionInfo(
    924        PREF_SETTING_TYPE,
    925        HOMEPAGE_OVERRIDE_KEY
    926      )
    927    ) {
    928      return;
    929    }
    930 
    931    // In this case, the button's disabled state is set by preferences.xml.
    932    let prefName = "pref.browser.homepage.disable_button.current_page";
    933    if (Preferences.get(prefName).locked) {
    934      return;
    935    }
    936 
    937    useCurrent.disabled = tabCount < 1;
    938  },
    939 
    940  /**
    941   * Sets the home page to the URL(s) of any currently opened tab(s),
    942   * updating about:preferences#home UI to reflect this.
    943   */
    944  setHomePageToCurrent() {
    945    let tabs = this._getTabsForHomePage();
    946    function getTabURI(t) {
    947      return t.linkedBrowser.currentURI.spec;
    948    }
    949 
    950    // FIXME Bug 244192: using dangerous "|" joiner!
    951    if (tabs.length) {
    952      HomePage.set(tabs.map(getTabURI).join("|")).catch(console.error);
    953    }
    954  },
    955 
    956  _setHomePageToBookmarkClosed(rv, aEvent) {
    957    if (aEvent.detail.button != "accept") {
    958      return;
    959    }
    960    if (rv.urls && rv.names) {
    961      // XXX still using dangerous "|" joiner!
    962      HomePage.set(rv.urls.join("|")).catch(console.error);
    963    }
    964  },
    965 
    966  /**
    967   * Displays a dialog in which the user can select a bookmark to use as home
    968   * page.  If the user selects a bookmark, that bookmark's name is displayed in
    969   * UI and the bookmark's address is stored to the home page preference.
    970   */
    971  setHomePageToBookmark() {
    972    const rv = { urls: null, names: null };
    973    gSubDialog.open(
    974      "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml",
    975      {
    976        features: "resizable=yes, modal=yes",
    977        closingCallback: this._setHomePageToBookmarkClosed.bind(this, rv),
    978      },
    979      rv
    980    );
    981  },
    982 
    983  restoreDefaultHomePage() {
    984    HomePage.reset();
    985    this._handleHomePageOverrides();
    986    Services.prefs.clearUserPref(this.NEWTAB_ENABLED_PREF);
    987    AboutNewTab.resetNewTabURL();
    988  },
    989 
    990  onCustomHomePageChange(event) {
    991    const value = event.target.value || HomePage.getDefault();
    992    HomePage.set(value).catch(console.error);
    993  },
    994 
    995  /**
    996   * Check all Home Tab preferences for user set values.
    997   */
    998  _changedHomeTabDefaultPrefs() {
    999    // If Discovery Stream is enabled Firefox Home Content preference options are hidden
   1000    const homeContentChanged =
   1001      !this.isPocketNewtabEnabled &&
   1002      this.homePanePrefs.some(pref => pref.hasUserValue);
   1003    const newtabPref = Preferences.get(this.NEWTAB_ENABLED_PREF);
   1004    const extensionControlled = Preferences.get(
   1005      "browser.startup.homepage_override.extensionControlled"
   1006    );
   1007 
   1008    return (
   1009      homeContentChanged ||
   1010      HomePage.overridden ||
   1011      newtabPref.hasUserValue ||
   1012      AboutNewTab.newTabURLOverridden ||
   1013      extensionControlled
   1014    );
   1015  },
   1016 
   1017  _isBlankPage(url) {
   1018    return url == "about:blank" || url == BLANK_HOMEPAGE_URL;
   1019  },
   1020 
   1021  /**
   1022   * Show the Restore Defaults button if any preference on the Home tab was
   1023   * changed, or hide it otherwise.
   1024   */
   1025  toggleRestoreDefaultsBtn() {
   1026    const btn = document.getElementById("restoreDefaultHomePageBtn");
   1027    const prefChanged = this._changedHomeTabDefaultPrefs();
   1028    if (prefChanged) {
   1029      btn.style.removeProperty("visibility");
   1030    } else {
   1031      btn.style.visibility = "hidden";
   1032    }
   1033  },
   1034 
   1035  /**
   1036   * Set all prefs on the Home tab back to their default values.
   1037   */
   1038  restoreDefaultPrefsForHome() {
   1039    this.restoreDefaultHomePage();
   1040    // If Discovery Stream is enabled Firefox Home Content preference options are hidden
   1041    if (!this.isPocketNewtabEnabled) {
   1042      this.homePanePrefs.forEach(pref => Services.prefs.clearUserPref(pref.id));
   1043    }
   1044  },
   1045 
   1046  init() {
   1047    initSettingGroup("homepage");
   1048    initSettingGroup("home");
   1049 
   1050    // Event Listeners
   1051    document
   1052      .getElementById("homePageUrl")
   1053      .addEventListener("change", this.onCustomHomePageChange.bind(this));
   1054    document
   1055      .getElementById("useCurrentBtn")
   1056      .addEventListener("command", this.setHomePageToCurrent.bind(this));
   1057    document
   1058      .getElementById("useBookmarkBtn")
   1059      .addEventListener("command", this.setHomePageToBookmark.bind(this));
   1060    document
   1061      .getElementById("restoreDefaultHomePageBtn")
   1062      .addEventListener("command", this.restoreDefaultPrefsForHome.bind(this));
   1063 
   1064    // Setup the add-on options for the new tab section before registering the
   1065    // listener.
   1066    this._updateMenuInterface();
   1067    document
   1068      .getElementById("newTabMode")
   1069      .addEventListener("command", this.syncToNewTabPref.bind(this));
   1070    document
   1071      .getElementById("homeMode")
   1072      .addEventListener("command", this.onMenuChange.bind(this));
   1073 
   1074    this._updateUseCurrentButton();
   1075    this._handleHomePageOverrides();
   1076    this.syncFromNewTabPref();
   1077    window.addEventListener("focus", this._updateUseCurrentButton.bind(this));
   1078 
   1079    // Extension/override-related events
   1080    this.watchNewTab();
   1081    this.watchHomePrefChange();
   1082    this.watchExtensionPrefChange();
   1083    this.watchHomeTabPrefChange();
   1084    // Notify observers that the UI is now ready
   1085    Services.obs.notifyObservers(window, "home-pane-loaded");
   1086  },
   1087 };