summaryrefslogtreecommitdiffstats
path: root/browser/modules/HomePage.jsm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/modules/HomePage.jsm359
1 files changed, 359 insertions, 0 deletions
diff --git a/browser/modules/HomePage.jsm b/browser/modules/HomePage.jsm
new file mode 100644
index 0000000000..3c4103f14f
--- /dev/null
+++ b/browser/modules/HomePage.jsm
@@ -0,0 +1,359 @@
+/* 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/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["HomePage"];
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
+ ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
+ ExtensionPreferencesManager:
+ "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
+ IgnoreLists: "resource://gre/modules/IgnoreLists.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+});
+
+const kPrefName = "browser.startup.homepage";
+const kDefaultHomePage = "about:home";
+const kExtensionControllerPref =
+ "browser.startup.homepage_override.extensionControlled";
+const kHomePageIgnoreListId = "homepage-urls";
+const kWidgetId = "home-button";
+const kWidgetRemovedPref = "browser.engagement.home-button.has-removed";
+
+function getHomepagePref(useDefault) {
+ let homePage;
+ let prefs = Services.prefs;
+ if (useDefault) {
+ prefs = prefs.getDefaultBranch(null);
+ }
+ try {
+ // Historically, this was a localizable pref, but default Firefox builds
+ // don't use this.
+ // Distributions and local customizations might still use this, so let's
+ // keep it.
+ homePage = prefs.getComplexValue(kPrefName, Ci.nsIPrefLocalizedString).data;
+ } catch (ex) {}
+
+ if (!homePage) {
+ homePage = prefs.getStringPref(kPrefName);
+ }
+
+ // Apparently at some point users ended up with blank home pages somehow.
+ // If that happens, reset the pref and read it again.
+ if (!homePage && !useDefault) {
+ Services.prefs.clearUserPref(kPrefName);
+ homePage = getHomepagePref(true);
+ }
+
+ return homePage;
+}
+
+/**
+ * HomePage provides tools to keep track of the current homepage, and the
+ * applications's default homepage. It includes tools to insure that certain
+ * urls are ignored. As a result, all set/get requests for the homepage
+ * preferences should be routed through here.
+ */
+let HomePage = {
+ // This is an array of strings that should be matched against URLs to see
+ // if they should be ignored or not.
+ _ignoreList: [],
+
+ // A promise that is set when initialization starts and resolved when it
+ // completes.
+ _initializationPromise: null,
+
+ /**
+ * Used to initialise the ignore lists. This may be called later than
+ * the first call to get or set, which may cause a used to get an ignored
+ * homepage, but this is deemed acceptable, as we'll correct it once
+ * initialised.
+ */
+ async delayedStartup() {
+ if (this._initializationPromise) {
+ await this._initializationPromise;
+ return;
+ }
+
+ Services.telemetry.setEventRecordingEnabled("homepage", true);
+
+ // Now we have the values, listen for future updates.
+ this._ignoreListListener = this._handleIgnoreListUpdated.bind(this);
+
+ this._initializationPromise = lazy.IgnoreLists.getAndSubscribe(
+ this._ignoreListListener
+ );
+
+ this._addCustomizableUiListener();
+
+ const current = await this._initializationPromise;
+
+ await this._handleIgnoreListUpdated({ data: { current } });
+ },
+
+ /**
+ * Gets the homepage for the given window.
+ *
+ * @param {DOMWindow} [aWindow]
+ * The window associated with the get, used to check for private browsing
+ * mode. If not supplied, normal mode is assumed.
+ * @returns {string}
+ * Returns the home page value, this could be a single url, or a `|`
+ * separated list of URLs.
+ */
+ get(aWindow) {
+ let homePages = getHomepagePref();
+ if (
+ lazy.PrivateBrowsingUtils.permanentPrivateBrowsing ||
+ (aWindow && lazy.PrivateBrowsingUtils.isWindowPrivate(aWindow))
+ ) {
+ // If an extension controls the setting and does not have private
+ // browsing permission, use the default setting.
+ let extensionControlled = Services.prefs.getBoolPref(
+ kExtensionControllerPref,
+ false
+ );
+ let privateAllowed = Services.prefs.getBoolPref(
+ "browser.startup.homepage_override.privateAllowed",
+ false
+ );
+ // There is a potential on upgrade that the prefs are not set yet, so we double check
+ // for moz-extension.
+ if (
+ !privateAllowed &&
+ (extensionControlled || homePages.includes("moz-extension://"))
+ ) {
+ return this.getDefault();
+ }
+ }
+
+ if (homePages == "about:blank") {
+ homePages = "chrome://browser/content/blanktab.html";
+ }
+
+ return homePages;
+ },
+
+ /**
+ * @returns {string}
+ * Returns the application default homepage.
+ */
+ getDefault() {
+ return getHomepagePref(true);
+ },
+
+ /**
+ * @returns {string}
+ * Returns the original application homepage URL (not from prefs).
+ */
+ getOriginalDefault() {
+ return kDefaultHomePage;
+ },
+
+ /**
+ * @returns {boolean}
+ * Returns true if the homepage has been changed.
+ */
+ get overridden() {
+ return Services.prefs.prefHasUserValue(kPrefName);
+ },
+
+ /**
+ * @returns {boolean}
+ * Returns true if the homepage preference is locked.
+ */
+ get locked() {
+ return Services.prefs.prefIsLocked(kPrefName);
+ },
+
+ /**
+ * @returns {boolean}
+ * Returns true if the current homepage is the application default.
+ */
+ get isDefault() {
+ return HomePage.get() === kDefaultHomePage;
+ },
+
+ /**
+ * Sets the homepage preference to a new page.
+ *
+ * @param {string} value
+ * The new value to set the preference to. This could be a single url, or a
+ * `|` separated list of URLs.
+ */
+ async set(value) {
+ await this.delayedStartup();
+
+ if (await this.shouldIgnore(value)) {
+ console.error(
+ `Ignoring homepage setting for ${value} as it is on the ignore list.`
+ );
+ Services.telemetry.recordEvent(
+ "homepage",
+ "preference",
+ "ignore",
+ "set_blocked"
+ );
+ return false;
+ }
+ Services.prefs.setStringPref(kPrefName, value);
+ this._maybeAddHomeButtonToToolbar(value);
+ return true;
+ },
+
+ /**
+ * Sets the homepage preference to a new page. This is an synchronous version
+ * that should only be used when we know the source is safe as it bypasses the
+ * ignore list, e.g. when setting directly to about:blank or a value not
+ * supplied externally.
+ *
+ * @param {string} value
+ * The new value to set the preference to. This could be a single url, or a
+ * `|` separated list of URLs.
+ */
+ safeSet(value) {
+ Services.prefs.setStringPref(kPrefName, value);
+ },
+
+ /**
+ * Clears the homepage preference if it is not the default. Note that for
+ * policy/locking use, the default homepage might not be about:home after this.
+ */
+ clear() {
+ Services.prefs.clearUserPref(kPrefName);
+ },
+
+ /**
+ * Resets the homepage preference to be about:home.
+ */
+ reset() {
+ Services.prefs.setStringPref(kPrefName, kDefaultHomePage);
+ },
+
+ /**
+ * Determines if a url should be ignored according to the ignore list.
+ *
+ * @param {string} url
+ * A string that is the url or urls to be ignored.
+ * @returns {boolean}
+ * True if the url should be ignored.
+ */
+ async shouldIgnore(url) {
+ await this.delayedStartup();
+
+ const lowerURL = url.toLowerCase();
+ return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
+ },
+
+ /**
+ * Handles updates of the ignore list, checking the existing preference and
+ * correcting it as necessary.
+ *
+ * @param {Object} eventData
+ * The event data as received from RemoteSettings.
+ */
+ async _handleIgnoreListUpdated({ data: { current } }) {
+ for (const entry of current) {
+ if (entry.id == kHomePageIgnoreListId) {
+ this._ignoreList = [...entry.matches];
+ }
+ }
+
+ // Only check if we're overridden as we assume the default value is fine,
+ // or won't be changeable (e.g. enterprise policy).
+ if (this.overridden) {
+ let homePages = getHomepagePref().toLowerCase();
+ if (
+ this._ignoreList.some(code => homePages.includes(code.toLowerCase()))
+ ) {
+ if (Services.prefs.getBoolPref(kExtensionControllerPref, false)) {
+ if (Services.appinfo.inSafeMode) {
+ // Add-ons don't get started in safe mode, so just abort this.
+ // We'll get to remove them when we next start in normal mode.
+ return;
+ }
+ // getSetting does not need the module to be loaded.
+ const item = await lazy.ExtensionPreferencesManager.getSetting(
+ "homepage_override"
+ );
+ if (item && item.id) {
+ // During startup some modules may not be loaded yet, so we load
+ // the setting we need prior to removal.
+ await lazy.ExtensionParent.apiManager.asyncLoadModule(
+ "chrome_settings_overrides"
+ );
+ lazy.ExtensionPreferencesManager.removeSetting(
+ item.id,
+ "homepage_override"
+ ).catch(console.error);
+ } else {
+ // If we don't have a setting for it, we assume the pref has
+ // been incorrectly set somehow.
+ Services.prefs.clearUserPref(kExtensionControllerPref);
+ Services.prefs.clearUserPref(
+ "browser.startup.homepage_override.privateAllowed"
+ );
+ }
+ } else {
+ this.clear();
+ }
+ Services.telemetry.recordEvent(
+ "homepage",
+ "preference",
+ "ignore",
+ "saved_reset"
+ );
+ }
+ }
+ },
+
+ onWidgetRemoved(widgetId, area) {
+ if (widgetId == kWidgetId) {
+ Services.prefs.setBoolPref(kWidgetRemovedPref, true);
+ lazy.CustomizableUI.removeListener(this);
+ }
+ },
+
+ /**
+ * Add the home button to the toolbar if the user just set a custom homepage.
+ *
+ * This should only be done once, so we check HOME_BUTTON_REMOVED_PREF which
+ * gets set to true when the home button is removed from the toolbar.
+ *
+ * If the home button is already on the toolbar it won't be moved.
+ */
+ _maybeAddHomeButtonToToolbar(homePage) {
+ if (
+ homePage !== "about:home" &&
+ homePage !== "about:blank" &&
+ !Services.prefs.getBoolPref(kExtensionControllerPref, false) &&
+ !Services.prefs.getBoolPref(kWidgetRemovedPref, false) &&
+ !lazy.CustomizableUI.getWidget(kWidgetId).areaType
+ ) {
+ // Find a spot for the home button, ideally it will be in its default
+ // position beside the stop/refresh button.
+ // Work backwards from the URL bar since it can't be removed and put
+ // the button after the first non-spring we find.
+ let navbarPlacements = lazy.CustomizableUI.getWidgetIdsInArea("nav-bar");
+ let position = navbarPlacements.indexOf("urlbar-container");
+ for (let i = position - 1; i >= 0; i--) {
+ if (!navbarPlacements[i].startsWith("customizableui-special-spring")) {
+ position = i + 1;
+ break;
+ }
+ }
+ lazy.CustomizableUI.addWidgetToArea(kWidgetId, "nav-bar", position);
+ }
+ },
+
+ _addCustomizableUiListener() {
+ if (!Services.prefs.getBoolPref(kWidgetRemovedPref, false)) {
+ lazy.CustomizableUI.addListener(this);
+ }
+ },
+};