tor-browser

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

HomePage.sys.mjs (11406B)


      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 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  CustomizableUI:
      9    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     10  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
     11  ExtensionPreferencesManager:
     12    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
     13  IgnoreLists: "resource://gre/modules/IgnoreLists.sys.mjs",
     14  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     15 });
     16 
     17 const kPrefName = "browser.startup.homepage";
     18 const kDefaultHomePage = "about:tor";
     19 const kExtensionControllerPref =
     20  "browser.startup.homepage_override.extensionControlled";
     21 const kHomePageIgnoreListId = "homepage-urls";
     22 const kWidgetId = "home-button";
     23 const kWidgetRemovedPref = "browser.engagement.home-button.has-removed";
     24 
     25 function getHomepagePref(useDefault) {
     26  let homePage;
     27  let prefs = Services.prefs;
     28  if (useDefault) {
     29    prefs = prefs.getDefaultBranch(null);
     30  }
     31  try {
     32    // Historically, this was a localizable pref, but default Firefox builds
     33    // don't use this.
     34    // Distributions and local customizations might still use this, so let's
     35    // keep it.
     36    homePage = prefs.getComplexValue(kPrefName, Ci.nsIPrefLocalizedString).data;
     37  } catch (ex) {}
     38 
     39  if (!homePage) {
     40    homePage = prefs.getStringPref(kPrefName);
     41  }
     42 
     43  // Apparently at some point users ended up with blank home pages somehow.
     44  // If that happens, reset the pref and read it again.
     45  if (!homePage && !useDefault) {
     46    Services.prefs.clearUserPref(kPrefName);
     47    homePage = getHomepagePref(true);
     48  }
     49 
     50  return homePage;
     51 }
     52 
     53 /**
     54 * HomePage provides tools to keep track of the current homepage, and the
     55 * applications's default homepage. It includes tools to insure that certain
     56 * urls are ignored. As a result, all set/get requests for the homepage
     57 * preferences should be routed through here.
     58 */
     59 export let HomePage = {
     60  // This is an array of strings that should be matched against URLs to see
     61  // if they should be ignored or not.
     62  _ignoreList: [],
     63 
     64  // A promise that is set when initialization starts and resolved when it
     65  // completes.
     66  _initializationPromise: null,
     67 
     68  /**
     69   * Used to initialise the ignore lists. This may be called later than
     70   * the first call to get or set, which may cause a used to get an ignored
     71   * homepage, but this is deemed acceptable, as we'll correct it once
     72   * initialised.
     73   */
     74  async delayedStartup() {
     75    if (this._initializationPromise) {
     76      await this._initializationPromise;
     77      return;
     78    }
     79 
     80    // Now we have the values, listen for future updates.
     81    this._ignoreListListener = this._handleIgnoreListUpdated.bind(this);
     82 
     83    this._initializationPromise = lazy.IgnoreLists.getAndSubscribe(
     84      this._ignoreListListener
     85    );
     86 
     87    this._addCustomizableUiListener();
     88 
     89    const current = await this._initializationPromise;
     90 
     91    await this._handleIgnoreListUpdated({ data: { current } });
     92  },
     93 
     94  /**
     95   * Gets the homepage for the given window.
     96   *
     97   * @param {DOMWindow} [aWindow]
     98   *   The window associated with the get, used to check for private browsing
     99   *   mode. If not supplied, normal mode is assumed.
    100   * @returns {string}
    101   *   Returns the home page value, this could be a single url, or a `|`
    102   *   separated list of URLs.
    103   */
    104  get(aWindow) {
    105    let homePages = getHomepagePref();
    106    if (
    107      lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
    108      (aWindow && lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow))
    109    ) {
    110      // If an extension controls the setting and does not have private
    111      // browsing permission, use the default setting.
    112      let extensionControlled = Services.prefs.getBoolPref(
    113        kExtensionControllerPref,
    114        false
    115      );
    116      let privateAllowed = Services.prefs.getBoolPref(
    117        "browser.startup.homepage_override.privateAllowed",
    118        false
    119      );
    120      // There is a potential on upgrade that the prefs are not set yet, so we double check
    121      // for moz-extension.
    122      if (
    123        !privateAllowed &&
    124        (extensionControlled || homePages.includes("moz-extension://"))
    125      ) {
    126        return this.getDefault();
    127      }
    128    }
    129 
    130    if (homePages == "about:blank") {
    131      homePages = "chrome://browser/content/blanktab.html";
    132    }
    133 
    134    return homePages;
    135  },
    136 
    137  getForErrorPage(win) {
    138    if (lazy.PrivateBrowsingUtils.isWindowPrivate(win)) {
    139      return win.BROWSER_NEW_TAB_URL;
    140    }
    141    let url = this.get(win);
    142    if (url.includes("|")) {
    143      url = url.split("|")[0];
    144    }
    145    return url;
    146  },
    147 
    148  /**
    149   * @returns {string}
    150   *   Returns the application default homepage.
    151   */
    152  getDefault() {
    153    return getHomepagePref(true);
    154  },
    155 
    156  /**
    157   * @returns {string}
    158   *   Returns the original application homepage URL (not from prefs).
    159   */
    160  getOriginalDefault() {
    161    return kDefaultHomePage;
    162  },
    163 
    164  /**
    165   * @returns {boolean}
    166   *   Returns true if the homepage has been changed.
    167   */
    168  get overridden() {
    169    return Services.prefs.prefHasUserValue(kPrefName);
    170  },
    171 
    172  /**
    173   * @returns {boolean}
    174   *   Returns true if the homepage preference is locked.
    175   */
    176  get locked() {
    177    return Services.prefs.prefIsLocked(kPrefName);
    178  },
    179 
    180  /**
    181   * @returns {boolean}
    182   *   Returns true if the current homepage is the application default.
    183   */
    184  get isDefault() {
    185    return HomePage.get() === kDefaultHomePage;
    186  },
    187 
    188  /**
    189   * Sets the homepage preference to a new page.
    190   *
    191   * @param {string} value
    192   *   The new value to set the preference to. This could be a single url, or a
    193   *   `|` separated list of URLs.
    194   */
    195  async set(value) {
    196    await this.delayedStartup();
    197 
    198    if (await this.shouldIgnore(value)) {
    199      console.error(
    200        `Ignoring homepage setting for ${value} as it is on the ignore list.`
    201      );
    202      Glean.homepage.preferenceIgnore.record({ value: "set_blocked" });
    203      return false;
    204    }
    205    Services.prefs.setStringPref(kPrefName, value);
    206    this._maybeAddHomeButtonToToolbar(value);
    207    return true;
    208  },
    209 
    210  /**
    211   * Sets the homepage preference to a new page. This is an synchronous version
    212   * that should only be used when we know the source is safe as it bypasses the
    213   * ignore list, e.g. when setting directly to about:blank or a value not
    214   * supplied externally.
    215   *
    216   * @param {string} value
    217   *   The new value to set the preference to. This could be a single url, or a
    218   *   `|` separated list of URLs.
    219   */
    220  safeSet(value) {
    221    Services.prefs.setStringPref(kPrefName, value);
    222  },
    223 
    224  /**
    225   * Clears the homepage preference if it is not the default. Note that for
    226   * policy/locking use, the default homepage might not be about:home after this.
    227   */
    228  clear() {
    229    Services.prefs.clearUserPref(kPrefName);
    230  },
    231 
    232  /**
    233   * Resets the homepage preference to be about:home.
    234   */
    235  reset() {
    236    Services.prefs.setStringPref(kPrefName, kDefaultHomePage);
    237  },
    238 
    239  /**
    240   * Determines if a url should be ignored according to the ignore list.
    241   *
    242   * @param {string} url
    243   *   A string that is the url or urls to be ignored.
    244   * @returns {boolean}
    245   *   True if the url should be ignored.
    246   */
    247  async shouldIgnore(url) {
    248    await this.delayedStartup();
    249 
    250    const lowerURL = url.toLowerCase();
    251    return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
    252  },
    253 
    254  /**
    255   * Handles updates of the ignore list, checking the existing preference and
    256   * correcting it as necessary.
    257   *
    258   * @param {object} eventData
    259   *   The event data as received from RemoteSettings.
    260   */
    261  async _handleIgnoreListUpdated({ data: { current } }) {
    262    for (const entry of current) {
    263      if (entry.id == kHomePageIgnoreListId) {
    264        this._ignoreList = [...entry.matches];
    265      }
    266    }
    267 
    268    // Only check if we're overridden as we assume the default value is fine,
    269    // or won't be changeable (e.g. enterprise policy).
    270    if (this.overridden) {
    271      let homePages = getHomepagePref().toLowerCase();
    272      if (
    273        this._ignoreList.some(code => homePages.includes(code.toLowerCase()))
    274      ) {
    275        if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) {
    276          if (Services.appinfo.inSafeMode) {
    277            // Add-ons don't get started in safe mode, so just abort this.
    278            // We'll get to remove them when we next start in normal mode.
    279            return;
    280          }
    281          // getSetting does not need the module to be loaded.
    282          const item =
    283            await lazy.ExtensionPreferencesManager.getSetting(
    284              "homepage_override"
    285            );
    286          if (item && item.id) {
    287            // During startup some modules may not be loaded yet, so we load
    288            // the setting we need prior to removal.
    289            await lazy.ExtensionParent.apiManager.asyncLoadModule(
    290              "chrome_settings_overrides"
    291            );
    292            lazy.ExtensionPreferencesManager.removeSetting(
    293              item.id,
    294              "homepage_override"
    295            ).catch(console.error);
    296          } else {
    297            // If we don't have a setting for it, we assume the pref has
    298            // been incorrectly set somehow.
    299            Services.prefs.clearUserPref(kExtensionControllerPref);
    300            Services.prefs.clearUserPref(
    301              "browser.startup.homepage_override.privateAllowed"
    302            );
    303          }
    304        } else {
    305          this.clear();
    306        }
    307        Glean.homepage.preferenceIgnore.record({ value: "saved_reset" });
    308      }
    309    }
    310  },
    311 
    312  onWidgetRemoved(widgetId) {
    313    if (widgetId == kWidgetId) {
    314      Services.prefs.setBoolPref(kWidgetRemovedPref, true);
    315      lazy.CustomizableUI.removeListener(this);
    316    }
    317  },
    318 
    319  /**
    320   * Add the home button to the toolbar if the user just set a custom homepage.
    321   *
    322   * This should only be done once, so we check HOME_BUTTON_REMOVED_PREF which
    323   * gets set to true when the home button is removed from the toolbar.
    324   *
    325   * If the home button is already on the toolbar it won't be moved.
    326   */
    327  _maybeAddHomeButtonToToolbar(homePage) {
    328    if (
    329      homePage !== "about:home" &&
    330      homePage !== "about:blank" &&
    331      !Services.prefs.getBoolPref(kExtensionControllerPref, false) &&
    332      !Services.prefs.getBoolPref(kWidgetRemovedPref, false) &&
    333      !lazy.CustomizableUI.getWidget(kWidgetId).areaType
    334    ) {
    335      // Find a spot for the home button, ideally it will be in its default
    336      // position beside the stop/refresh button.
    337      // Work backwards from the URL bar since it can't be removed and put
    338      // the button after the first non-spring we find.
    339      let navbarPlacements = lazy.CustomizableUI.getWidgetIdsInArea("nav-bar");
    340      let position = navbarPlacements.indexOf("urlbar-container");
    341      for (let i = position - 1; i >= 0; i--) {
    342        if (
    343          !navbarPlacements[i].startsWith("customizableui-special-spring") &&
    344          !navbarPlacements[i].includes("spacer")
    345        ) {
    346          position = i + 1;
    347          break;
    348        }
    349      }
    350      lazy.CustomizableUI.addWidgetToArea(kWidgetId, "nav-bar", position);
    351    }
    352  },
    353 
    354  _addCustomizableUiListener() {
    355    if (!Services.prefs.getBoolPref(kWidgetRemovedPref, false)) {
    356      lazy.CustomizableUI.addListener(this);
    357    }
    358  },
    359 };