diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/preferences/home.js | 694 |
1 files changed, 694 insertions, 0 deletions
diff --git a/browser/components/preferences/home.js b/browser/components/preferences/home.js new file mode 100644 index 0000000000..54d960ec95 --- /dev/null +++ b/browser/components/preferences/home.js @@ -0,0 +1,694 @@ +/* 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-globals-from extensionControlled.js */ +/* import-globals-from preferences.js */ +/* import-globals-from main.js */ + +// HOME PAGE + +/* + * Preferences: + * + * browser.startup.homepage + * - the user's home page, as a string; if the home page is a set of tabs, + * this will be those URLs separated by the pipe character "|" + * browser.newtabpage.enabled + * - determines that is shown on the user's new tab page. + * true = Activity Stream is shown, + * false = about:blank is shown + */ + +Preferences.addAll([ + { id: "browser.startup.homepage", type: "wstring" }, + { id: "pref.browser.homepage.disable_button.current_page", type: "bool" }, + { id: "pref.browser.homepage.disable_button.bookmark_page", type: "bool" }, + { id: "pref.browser.homepage.disable_button.restore_default", type: "bool" }, + { id: "browser.newtabpage.enabled", type: "bool" }, +]); + +const HOMEPAGE_OVERRIDE_KEY = "homepage_override"; +const URL_OVERRIDES_TYPE = "url_overrides"; +const NEW_TAB_KEY = "newTabURL"; + +const BLANK_HOMEPAGE_URL = "chrome://browser/content/blanktab.html"; + +var gHomePane = { + HOME_MODE_FIREFOX_HOME: "0", + HOME_MODE_BLANK: "1", + HOME_MODE_CUSTOM: "2", + HOMEPAGE_PREF: "browser.startup.homepage", + NEWTAB_ENABLED_PREF: "browser.newtabpage.enabled", + ACTIVITY_STREAM_PREF_BRANCH: "browser.newtabpage.activity-stream.", + + get homePanePrefs() { + return Preferences.getAll().filter(pref => + pref.id.includes(this.ACTIVITY_STREAM_PREF_BRANCH) + ); + }, + + get isPocketNewtabEnabled() { + const value = Services.prefs.getStringPref( + "browser.newtabpage.activity-stream.discoverystream.config", + "" + ); + if (value) { + try { + return JSON.parse(value).enabled; + } catch (e) { + console.error("Failed to parse Discovery Stream pref."); + } + } + + return false; + }, + + async syncToNewTabPref() { + let menulist = document.getElementById("newTabMode"); + + if (["0", "1"].includes(menulist.value)) { + let newtabEnabledPref = Services.prefs.getBoolPref( + this.NEWTAB_ENABLED_PREF, + true + ); + let newValue = menulist.value !== this.HOME_MODE_BLANK; + // Only set this if the pref has changed, otherwise the pref change will trigger other listeners to repeat. + if (newtabEnabledPref !== newValue) { + Services.prefs.setBoolPref(this.NEWTAB_ENABLED_PREF, newValue); + } + let selectedAddon = ExtensionSettingsStore.getSetting( + URL_OVERRIDES_TYPE, + NEW_TAB_KEY + ); + if (selectedAddon) { + ExtensionSettingsStore.select(null, URL_OVERRIDES_TYPE, NEW_TAB_KEY); + } + } else { + let addon = await AddonManager.getAddonByID(menulist.value); + if (addon && addon.isActive) { + ExtensionSettingsStore.select( + addon.id, + URL_OVERRIDES_TYPE, + NEW_TAB_KEY + ); + } + } + }, + + async syncFromNewTabPref() { + let menulist = document.getElementById("newTabMode"); + + // If the new tab url was changed to about:blank or about:newtab + if ( + AboutNewTab.newTabURL === "about:newtab" || + AboutNewTab.newTabURL === "about:blank" || + AboutNewTab.newTabURL === BLANK_HOMEPAGE_URL + ) { + let newtabEnabledPref = Services.prefs.getBoolPref( + this.NEWTAB_ENABLED_PREF, + true + ); + let newValue = newtabEnabledPref + ? this.HOME_MODE_FIREFOX_HOME + : this.HOME_MODE_BLANK; + if (newValue !== menulist.value) { + menulist.value = newValue; + } + menulist.disabled = Preferences.get(this.NEWTAB_ENABLED_PREF).locked; + // If change was triggered by installing an addon we need to update + // the value of the menulist to be that addon. + } else { + let selectedAddon = ExtensionSettingsStore.getSetting( + URL_OVERRIDES_TYPE, + NEW_TAB_KEY + ); + if (selectedAddon && menulist.value !== selectedAddon.id) { + menulist.value = selectedAddon.id; + } + } + }, + + /** + * _updateMenuInterface: adds items to or removes them from the menulists + * @param {string} selectId Optional Id of the menulist to add or remove items from. + * If not included this will update both home and newtab menus. + */ + async _updateMenuInterface(selectId) { + let selects; + if (selectId) { + selects = [document.getElementById(selectId)]; + } else { + let newTabSelect = document.getElementById("newTabMode"); + let homeSelect = document.getElementById("homeMode"); + selects = [homeSelect, newTabSelect]; + } + + for (let select of selects) { + // Remove addons from the menu popup which are no longer installed, or disabled. + // let menuOptions = select.menupopup.childNodes; + let menuOptions = Array.from(select.menupopup.childNodes); + + for (let option of menuOptions) { + // If the value is not a number, assume it is an addon ID + if (!/^\d+$/.test(option.value)) { + let addon = await AddonManager.getAddonByID(option.value); + if (option && (!addon || !addon.isActive)) { + option.remove(); + } + } + } + + let extensionOptions; + if (select.id === "homeMode") { + extensionOptions = await ExtensionSettingsStore.getAllSettings( + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + } else { + extensionOptions = await ExtensionSettingsStore.getAllSettings( + URL_OVERRIDES_TYPE, + NEW_TAB_KEY + ); + } + let addons = await AddonManager.getAddonsByIDs( + extensionOptions.map(a => a.id) + ); + + // Add addon options to the menu popups + let menupopup = select.querySelector("menupopup"); + for (let addon of addons) { + if (!addon || !addon.id || !addon.isActive) { + continue; + } + let currentOption = select.querySelector( + `[value="${CSS.escape(addon.id)}"]` + ); + if (!currentOption) { + let option = document.createXULElement("menuitem"); + option.classList.add("addon-with-favicon"); + option.value = addon.id; + option.label = addon.name; + menupopup.append(option); + option.querySelector("image").src = addon.iconURL; + } + let setting = extensionOptions.find(o => o.id == addon.id); + if ( + (select.id === "homeMode" && setting.value == HomePage.get()) || + (select.id === "newTabMode" && setting.value == AboutNewTab.newTabURL) + ) { + select.value = addon.id; + } + } + } + }, + + /** + * watchNewTab: Listen for changes to the new tab url and enable/disable appropriate + * areas of the UI. + */ + watchNewTab() { + let newTabObserver = () => { + this.syncFromNewTabPref(); + this._updateMenuInterface("newTabMode"); + }; + Services.obs.addObserver(newTabObserver, "newtab-url-changed"); + window.addEventListener("unload", () => { + Services.obs.removeObserver(newTabObserver, "newtab-url-changed"); + }); + }, + + /** + * watchHomePrefChange: Listen for preferences changes on the Home Tab in order to + * show the appropriate home menu selection. + */ + watchHomePrefChange() { + const homePrefObserver = (subject, topic, data) => { + // only update this UI if it is exactly the HOMEPAGE_PREF, not other prefs with the same root. + if (data && data != this.HOMEPAGE_PREF) { + return; + } + this._updateUseCurrentButton(); + this._renderCustomSettings(); + this._handleHomePageOverrides(); + this._updateMenuInterface("homeMode"); + }; + + Services.prefs.addObserver(this.HOMEPAGE_PREF, homePrefObserver); + window.addEventListener("unload", () => { + Services.prefs.removeObserver(this.HOMEPAGE_PREF, homePrefObserver); + }); + }, + + /** + * Listen extension changes on the New Tab and Home Tab + * in order to update the UI and show or hide the Restore Defaults button. + */ + watchExtensionPrefChange() { + const extensionSettingChanged = (evt, setting) => { + if (setting.key == "homepage_override" && setting.type == "prefs") { + this._updateMenuInterface("homeMode"); + } else if ( + setting.key == "newTabURL" && + setting.type == "url_overrides" + ) { + this._updateMenuInterface("newTabMode"); + } + }; + + Management.on("extension-setting-changed", extensionSettingChanged); + window.addEventListener("unload", () => { + Management.off("extension-setting-changed", extensionSettingChanged); + }); + }, + + /** + * Listen for all preferences changes on the Home Tab in order to show or + * hide the Restore Defaults button. + */ + watchHomeTabPrefChange() { + const observer = () => this.toggleRestoreDefaultsBtn(); + Services.prefs.addObserver(this.ACTIVITY_STREAM_PREF_BRANCH, observer); + Services.prefs.addObserver(this.HOMEPAGE_PREF, observer); + Services.prefs.addObserver(this.NEWTAB_ENABLED_PREF, observer); + + window.addEventListener("unload", () => { + Services.prefs.removeObserver(this.ACTIVITY_STREAM_PREF_BRANCH, observer); + Services.prefs.removeObserver(this.HOMEPAGE_PREF, observer); + Services.prefs.removeObserver(this.NEWTAB_ENABLED_PREF, observer); + }); + }, + + /** + * _renderCustomSettings: Hides or shows the UI for setting a custom + * homepage URL + * @param {obj} options + * @param {bool} options.shouldShow Should the custom UI be shown? + * @param {bool} options.isControlled Is an extension controlling the home page? + */ + _renderCustomSettings(options = {}) { + let { shouldShow, isControlled } = options; + const customSettingsContainerEl = document.getElementById("customSettings"); + const customUrlEl = document.getElementById("homePageUrl"); + const homePage = HomePage.get(); + const isHomePageCustom = + (!this._isHomePageDefaultValue() && + !this.isHomePageBlank() && + !isControlled) || + homePage.locked; + + if (typeof shouldShow === "undefined") { + shouldShow = isHomePageCustom; + } + customSettingsContainerEl.hidden = !shouldShow; + + // We can't use isHomePageDefaultValue and isHomePageBlank here because we want to disregard the blank + // possibility triggered by the browser.startup.page being 0. + // We also skip when HomePage is locked because it might be locked to a default that isn't "about:home" + // (and it makes existing tests happy). + let newValue; + if ( + this._isBlankPage(homePage) || + (HomePage.isDefault && !HomePage.locked) + ) { + newValue = ""; + } else { + newValue = homePage; + } + if (customUrlEl.value !== newValue) { + customUrlEl.value = newValue; + } + }, + + /** + * _isHomePageDefaultValue + * @returns {bool} Is the homepage set to the default pref value? + */ + _isHomePageDefaultValue() { + const startupPref = Preferences.get("browser.startup.page"); + return ( + startupPref.value !== gMainPane.STARTUP_PREF_BLANK && HomePage.isDefault + ); + }, + + /** + * isHomePageBlank + * @returns {bool} Is the homepage set to about:blank? + */ + isHomePageBlank() { + const startupPref = Preferences.get("browser.startup.page"); + return ( + ["about:blank", BLANK_HOMEPAGE_URL, ""].includes(HomePage.get()) || + startupPref.value === gMainPane.STARTUP_PREF_BLANK + ); + }, + + /** + * _isTabAboutPreferences: Is a given tab set to about:preferences? + * @param {Element} aTab A tab element + * @returns {bool} Is the linkedBrowser of aElement set to about:preferences? + */ + _isTabAboutPreferences(aTab) { + return aTab.linkedBrowser.currentURI.spec.startsWith("about:preferences"); + }, + + /** + * _getTabsForHomePage + * @returns {Array} An array of current tabs + */ + _getTabsForHomePage() { + let tabs = []; + let win = Services.wm.getMostRecentWindow("navigator:browser"); + + // We should only include visible & non-pinned tabs + if ( + win && + win.document.documentElement.getAttribute("windowtype") === + "navigator:browser" + ) { + tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs); + tabs = tabs.filter(tab => !this._isTabAboutPreferences(tab)); + // XXX: Bug 1441637 - Fix tabbrowser to report tab.closing before it blurs it + tabs = tabs.filter(tab => !tab.closing); + } + + return tabs; + }, + + _renderHomepageMode(controllingExtension) { + const isDefault = this._isHomePageDefaultValue(); + const isBlank = this.isHomePageBlank(); + const el = document.getElementById("homeMode"); + let newValue; + + if (controllingExtension && controllingExtension.id) { + newValue = controllingExtension.id; + } else if (isDefault) { + newValue = this.HOME_MODE_FIREFOX_HOME; + } else if (isBlank) { + newValue = this.HOME_MODE_BLANK; + } else { + newValue = this.HOME_MODE_CUSTOM; + } + if (el.value !== newValue) { + el.value = newValue; + } + }, + + _setInputDisabledStates(isControlled) { + let tabCount = this._getTabsForHomePage().length; + + // Disable or enable the inputs based on if this is controlled by an extension. + document + .querySelectorAll(".check-home-page-controlled") + .forEach(element => { + let isDisabled; + let pref = + element.getAttribute("preference") || + element.getAttribute("data-preference-related"); + if (!pref) { + throw new Error( + `Element with id ${element.id} did not have preference or data-preference-related attribute defined.` + ); + } + + if (pref === this.HOMEPAGE_PREF) { + isDisabled = HomePage.locked; + } else { + isDisabled = Preferences.get(pref).locked || isControlled; + } + + if (pref === "pref.browser.disable_button.current_page") { + // Special case for current_page to disable it if tabCount is 0 + isDisabled = isDisabled || tabCount < 1; + } + + element.disabled = isDisabled; + }); + }, + + async _handleHomePageOverrides() { + let controllingExtension; + if (HomePage.locked) { + // Disable inputs if they are locked. + this._renderCustomSettings(); + this._setInputDisabledStates(false); + } else { + if (HomePage.get().startsWith("moz-extension:")) { + controllingExtension = await getControllingExtension( + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + } + this._setInputDisabledStates(); + this._renderCustomSettings({ + isControlled: !!controllingExtension, + }); + } + this._renderHomepageMode(controllingExtension); + }, + + onMenuChange(event) { + const { value } = event.target; + const startupPref = Preferences.get("browser.startup.page"); + let selectedAddon = ExtensionSettingsStore.getSetting( + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + + switch (value) { + case this.HOME_MODE_FIREFOX_HOME: + if (startupPref.value === gMainPane.STARTUP_PREF_BLANK) { + startupPref.value = gMainPane.STARTUP_PREF_HOMEPAGE; + } + if (!HomePage.isDefault) { + HomePage.reset(); + } else { + this._renderCustomSettings({ shouldShow: false }); + } + if (selectedAddon) { + ExtensionSettingsStore.select( + null, + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + } + break; + case this.HOME_MODE_BLANK: + if (!this._isBlankPage(HomePage.get())) { + HomePage.safeSet(BLANK_HOMEPAGE_URL); + } else { + this._renderCustomSettings({ shouldShow: false }); + } + if (selectedAddon) { + ExtensionSettingsStore.select( + null, + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + } + break; + case this.HOME_MODE_CUSTOM: + if (startupPref.value === gMainPane.STARTUP_PREF_BLANK) { + Services.prefs.clearUserPref(startupPref.id); + } + if (HomePage.getDefault() != HomePage.getOriginalDefault()) { + HomePage.clear(); + } + this._renderCustomSettings({ shouldShow: true }); + if (selectedAddon) { + ExtensionSettingsStore.select( + null, + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ); + } + break; + // extensions will have a variety of values as their ID, so treat it as default + default: + AddonManager.getAddonByID(value).then(addon => { + if (addon && addon.isActive) { + ExtensionPreferencesManager.selectSetting( + addon.id, + HOMEPAGE_OVERRIDE_KEY + ); + } + this._renderCustomSettings({ shouldShow: false }); + }); + } + }, + + /** + * Switches the "Use Current Page" button between its singular and plural + * forms. + */ + async _updateUseCurrentButton() { + let useCurrent = document.getElementById("useCurrentBtn"); + let tabs = this._getTabsForHomePage(); + const tabCount = tabs.length; + document.l10n.setAttributes(useCurrent, "use-current-pages", { tabCount }); + + // If the homepage is controlled by an extension then you can't use this. + if ( + await getControllingExtensionInfo( + PREF_SETTING_TYPE, + HOMEPAGE_OVERRIDE_KEY + ) + ) { + return; + } + + // In this case, the button's disabled state is set by preferences.xml. + let prefName = "pref.browser.homepage.disable_button.current_page"; + if (Preferences.get(prefName).locked) { + return; + } + + useCurrent.disabled = tabCount < 1; + }, + + /** + * Sets the home page to the URL(s) of any currently opened tab(s), + * updating about:preferences#home UI to reflect this. + */ + setHomePageToCurrent() { + let tabs = this._getTabsForHomePage(); + function getTabURI(t) { + return t.linkedBrowser.currentURI.spec; + } + + // FIXME Bug 244192: using dangerous "|" joiner! + if (tabs.length) { + HomePage.set(tabs.map(getTabURI).join("|")).catch(Cu.reportError); + } + }, + + _setHomePageToBookmarkClosed(rv, aEvent) { + if (aEvent.detail.button != "accept") { + return; + } + if (rv.urls && rv.names) { + // XXX still using dangerous "|" joiner! + HomePage.set(rv.urls.join("|")).catch(Cu.reportError); + } + }, + + /** + * Displays a dialog in which the user can select a bookmark to use as home + * page. If the user selects a bookmark, that bookmark's name is displayed in + * UI and the bookmark's address is stored to the home page preference. + */ + setHomePageToBookmark() { + const rv = { urls: null, names: null }; + gSubDialog.open( + "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml", + { + features: "resizable=yes, modal=yes", + closingCallback: this._setHomePageToBookmarkClosed.bind(this, rv), + }, + rv + ); + }, + + restoreDefaultHomePage() { + HomePage.reset(); + this._handleHomePageOverrides(); + Services.prefs.clearUserPref(this.NEWTAB_ENABLED_PREF); + AboutNewTab.resetNewTabURL(); + }, + + onCustomHomePageChange(event) { + const value = event.target.value || HomePage.getDefault(); + HomePage.set(value).catch(Cu.reportError); + }, + + /** + * Check all Home Tab preferences for user set values. + */ + _changedHomeTabDefaultPrefs() { + // If Discovery Stream is enabled Firefox Home Content preference options are hidden + const homeContentChanged = + !this.isPocketNewtabEnabled && + this.homePanePrefs.some(pref => pref.hasUserValue); + const newtabPref = Preferences.get(this.NEWTAB_ENABLED_PREF); + const extensionControlled = Preferences.get( + "browser.startup.homepage_override.extensionControlled" + ); + + return ( + homeContentChanged || + HomePage.overridden || + newtabPref.hasUserValue || + AboutNewTab.newTabURLOverridden || + extensionControlled + ); + }, + + _isBlankPage(url) { + return url == "about:blank" || url == BLANK_HOMEPAGE_URL; + }, + + /** + * Show the Restore Defaults button if any preference on the Home tab was + * changed, or hide it otherwise. + */ + toggleRestoreDefaultsBtn() { + const btn = document.getElementById("restoreDefaultHomePageBtn"); + const prefChanged = this._changedHomeTabDefaultPrefs(); + if (prefChanged) { + btn.style.removeProperty("visibility"); + } else { + btn.style.visibility = "hidden"; + } + }, + + /** + * Set all prefs on the Home tab back to their default values. + */ + restoreDefaultPrefsForHome() { + this.restoreDefaultHomePage(); + // If Discovery Stream is enabled Firefox Home Content preference options are hidden + if (!this.isPocketNewtabEnabled) { + this.homePanePrefs.forEach(pref => Services.prefs.clearUserPref(pref.id)); + } + }, + + init() { + // Event Listeners + document + .getElementById("homePageUrl") + .addEventListener("change", this.onCustomHomePageChange.bind(this)); + document + .getElementById("useCurrentBtn") + .addEventListener("command", this.setHomePageToCurrent.bind(this)); + document + .getElementById("useBookmarkBtn") + .addEventListener("command", this.setHomePageToBookmark.bind(this)); + document + .getElementById("restoreDefaultHomePageBtn") + .addEventListener("command", this.restoreDefaultPrefsForHome.bind(this)); + + // Setup the add-on options for the new tab section before registering the + // listener. + this._updateMenuInterface(); + document + .getElementById("newTabMode") + .addEventListener("command", this.syncToNewTabPref.bind(this)); + document + .getElementById("homeMode") + .addEventListener("command", this.onMenuChange.bind(this)); + + this._updateUseCurrentButton(); + this._handleHomePageOverrides(); + this.syncFromNewTabPref(); + window.addEventListener("focus", this._updateUseCurrentButton.bind(this)); + + // Extension/override-related events + this.watchNewTab(); + this.watchHomePrefChange(); + this.watchExtensionPrefChange(); + this.watchHomeTabPrefChange(); + // Notify observers that the UI is now ready + Services.obs.notifyObservers(window, "home-pane-loaded"); + }, +}; |