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/. */
5 /* import-globals-from extensionControlled.js */
6 /* import-globals-from preferences.js */
7 /* import-globals-from main.js */
14 * browser.startup.homepage
15 * - the user's home page, as a string; if the home page is a set of tabs,
16 * this will be those URLs separated by the pipe character "|"
17 * browser.newtabpage.enabled
18 * - determines that is shown on the user's new tab page.
19 * true = Activity Stream is shown,
20 * false = about:blank is shown
24 { id
: "browser.startup.homepage", type
: "wstring" },
25 { id
: "pref.browser.homepage.disable_button.current_page", type
: "bool" },
26 { id
: "pref.browser.homepage.disable_button.bookmark_page", type
: "bool" },
27 { id
: "pref.browser.homepage.disable_button.restore_default", type
: "bool" },
28 { id
: "browser.newtabpage.enabled", type
: "bool" },
31 const HOMEPAGE_OVERRIDE_KEY
= "homepage_override";
32 const URL_OVERRIDES_TYPE
= "url_overrides";
33 const NEW_TAB_KEY
= "newTabURL";
35 const BLANK_HOMEPAGE_URL
= "chrome://browser/content/blanktab.html";
38 HOME_MODE_FIREFOX_HOME
: "0",
40 HOME_MODE_CUSTOM
: "2",
41 HOMEPAGE_PREF
: "browser.startup.homepage",
42 NEWTAB_ENABLED_PREF
: "browser.newtabpage.enabled",
43 ACTIVITY_STREAM_PREF_BRANCH
: "browser.newtabpage.activity-stream.",
46 return Preferences
.getAll().filter(pref
=>
47 pref
.id
.includes(this.ACTIVITY_STREAM_PREF_BRANCH
)
51 get isPocketNewtabEnabled() {
52 const value
= Services
.prefs
.getStringPref(
53 "browser.newtabpage.activity-stream.discoverystream.config",
58 return JSON
.parse(value
).enabled
;
60 console
.error("Failed to parse Discovery Stream pref.");
67 async
syncToNewTabPref() {
68 let menulist
= document
.getElementById("newTabMode");
70 if (["0", "1"].includes(menulist
.value
)) {
71 let newtabEnabledPref
= Services
.prefs
.getBoolPref(
72 this.NEWTAB_ENABLED_PREF
,
75 let newValue
= menulist
.value
!== this.HOME_MODE_BLANK
;
76 // Only set this if the pref has changed, otherwise the pref change will trigger other listeners to repeat.
77 if (newtabEnabledPref
!== newValue
) {
78 Services
.prefs
.setBoolPref(this.NEWTAB_ENABLED_PREF
, newValue
);
80 let selectedAddon
= ExtensionSettingsStore
.getSetting(
85 ExtensionSettingsStore
.select(null, URL_OVERRIDES_TYPE
, NEW_TAB_KEY
);
88 let addon
= await AddonManager
.getAddonByID(menulist
.value
);
89 if (addon
&& addon
.isActive
) {
90 ExtensionSettingsStore
.select(
99 async
syncFromNewTabPref() {
100 let menulist
= document
.getElementById("newTabMode");
102 // If the new tab url was changed to about:blank or about:newtab
104 AboutNewTab
.newTabURL
=== "about:newtab" ||
105 AboutNewTab
.newTabURL
=== "about:blank" ||
106 AboutNewTab
.newTabURL
=== BLANK_HOMEPAGE_URL
108 let newtabEnabledPref
= Services
.prefs
.getBoolPref(
109 this.NEWTAB_ENABLED_PREF
,
112 let newValue
= newtabEnabledPref
113 ? this.HOME_MODE_FIREFOX_HOME
114 : this.HOME_MODE_BLANK
;
115 if (newValue
!== menulist
.value
) {
116 menulist
.value
= newValue
;
118 menulist
.disabled
= Preferences
.get(this.NEWTAB_ENABLED_PREF
).locked
;
119 // If change was triggered by installing an addon we need to update
120 // the value of the menulist to be that addon.
122 let selectedAddon
= ExtensionSettingsStore
.getSetting(
126 if (selectedAddon
&& menulist
.value
!== selectedAddon
.id
) {
127 menulist
.value
= selectedAddon
.id
;
133 * _updateMenuInterface: adds items to or removes them from the menulists
134 * @param {string} selectId Optional Id of the menulist to add or remove items from.
135 * If not included this will update both home and newtab menus.
137 async
_updateMenuInterface(selectId
) {
140 selects
= [document
.getElementById(selectId
)];
142 let newTabSelect
= document
.getElementById("newTabMode");
143 let homeSelect
= document
.getElementById("homeMode");
144 selects
= [homeSelect
, newTabSelect
];
147 for (let select
of selects
) {
148 // Remove addons from the menu popup which are no longer installed, or disabled.
149 // let menuOptions = select.menupopup.childNodes;
150 let menuOptions
= Array
.from(select
.menupopup
.childNodes
);
152 for (let option
of menuOptions
) {
153 // If the value is not a number, assume it is an addon ID
154 if (!/^\d+$/.test(option
.value
)) {
155 let addon
= await AddonManager
.getAddonByID(option
.value
);
156 if (option
&& (!addon
|| !addon
.isActive
)) {
162 let extensionOptions
;
163 await ExtensionSettingsStore
.initialize();
164 if (select
.id
=== "homeMode") {
165 extensionOptions
= ExtensionSettingsStore
.getAllSettings(
167 HOMEPAGE_OVERRIDE_KEY
170 extensionOptions
= ExtensionSettingsStore
.getAllSettings(
175 let addons
= await AddonManager
.getAddonsByIDs(
176 extensionOptions
.map(a
=> a
.id
)
179 // Add addon options to the menu popups
180 let menupopup
= select
.querySelector("menupopup");
181 for (let addon
of addons
) {
182 if (!addon
|| !addon
.id
|| !addon
.isActive
) {
185 let currentOption
= select
.querySelector(
186 `[value="${CSS.escape(addon.id)}"]`
188 if (!currentOption
) {
189 let option
= document
.createXULElement("menuitem");
190 option
.classList
.add("addon-with-favicon");
191 option
.value
= addon
.id
;
192 option
.label
= addon
.name
;
193 menupopup
.append(option
);
194 option
.querySelector("image").src
= addon
.iconURL
;
196 let setting
= extensionOptions
.find(o
=> o
.id
== addon
.id
);
198 (select
.id
=== "homeMode" && setting
.value
== HomePage
.get()) ||
199 (select
.id
=== "newTabMode" && setting
.value
== AboutNewTab
.newTabURL
)
201 select
.value
= addon
.id
;
208 * watchNewTab: Listen for changes to the new tab url and enable/disable appropriate
212 let newTabObserver
= () => {
213 this.syncFromNewTabPref();
214 this._updateMenuInterface("newTabMode");
216 Services
.obs
.addObserver(newTabObserver
, "newtab-url-changed");
217 window
.addEventListener("unload", () => {
218 Services
.obs
.removeObserver(newTabObserver
, "newtab-url-changed");
223 * watchHomePrefChange: Listen for preferences changes on the Home Tab in order to
224 * show the appropriate home menu selection.
226 watchHomePrefChange() {
227 const homePrefObserver
= (subject
, topic
, data
) => {
228 // only update this UI if it is exactly the HOMEPAGE_PREF, not other prefs with the same root.
229 if (data
&& data
!= this.HOMEPAGE_PREF
) {
232 this._updateUseCurrentButton();
233 this._renderCustomSettings();
234 this._handleHomePageOverrides();
235 this._updateMenuInterface("homeMode");
238 Services
.prefs
.addObserver(this.HOMEPAGE_PREF
, homePrefObserver
);
239 window
.addEventListener("unload", () => {
240 Services
.prefs
.removeObserver(this.HOMEPAGE_PREF
, homePrefObserver
);
245 * Listen extension changes on the New Tab and Home Tab
246 * in order to update the UI and show or hide the Restore Defaults button.
248 watchExtensionPrefChange() {
249 const extensionSettingChanged
= (evt
, setting
) => {
250 if (setting
.key
== "homepage_override" && setting
.type
== "prefs") {
251 this._updateMenuInterface("homeMode");
253 setting
.key
== "newTabURL" &&
254 setting
.type
== "url_overrides"
256 this._updateMenuInterface("newTabMode");
260 Management
.on("extension-setting-changed", extensionSettingChanged
);
261 window
.addEventListener("unload", () => {
262 Management
.off("extension-setting-changed", extensionSettingChanged
);
267 * Listen for all preferences changes on the Home Tab in order to show or
268 * hide the Restore Defaults button.
270 watchHomeTabPrefChange() {
271 const observer
= () => this.toggleRestoreDefaultsBtn();
272 Services
.prefs
.addObserver(this.ACTIVITY_STREAM_PREF_BRANCH
, observer
);
273 Services
.prefs
.addObserver(this.HOMEPAGE_PREF
, observer
);
274 Services
.prefs
.addObserver(this.NEWTAB_ENABLED_PREF
, observer
);
276 window
.addEventListener("unload", () => {
277 Services
.prefs
.removeObserver(this.ACTIVITY_STREAM_PREF_BRANCH
, observer
);
278 Services
.prefs
.removeObserver(this.HOMEPAGE_PREF
, observer
);
279 Services
.prefs
.removeObserver(this.NEWTAB_ENABLED_PREF
, observer
);
284 * _renderCustomSettings: Hides or shows the UI for setting a custom
286 * @param {obj} options
287 * @param {bool} options.shouldShow Should the custom UI be shown?
288 * @param {bool} options.isControlled Is an extension controlling the home page?
290 _renderCustomSettings(options
= {}) {
291 let { shouldShow
, isControlled
} = options
;
292 const customSettingsContainerEl
= document
.getElementById("customSettings");
293 const customUrlEl
= document
.getElementById("homePageUrl");
294 const homePage
= HomePage
.get();
295 const isHomePageCustom
=
296 (!this._isHomePageDefaultValue() &&
297 !this.isHomePageBlank() &&
301 if (typeof shouldShow
=== "undefined") {
302 shouldShow
= isHomePageCustom
;
304 customSettingsContainerEl
.hidden
= !shouldShow
;
306 // We can't use isHomePageDefaultValue and isHomePageBlank here because we want to disregard the blank
307 // possibility triggered by the browser.startup.page being 0.
308 // We also skip when HomePage is locked because it might be locked to a default that isn't "about:home"
309 // (and it makes existing tests happy).
312 this._isBlankPage(homePage
) ||
313 (HomePage
.isDefault
&& !HomePage
.locked
)
319 if (customUrlEl
.value
!== newValue
) {
320 customUrlEl
.value
= newValue
;
325 * _isHomePageDefaultValue
326 * @returns {bool} Is the homepage set to the default pref value?
328 _isHomePageDefaultValue() {
329 const startupPref
= Preferences
.get("browser.startup.page");
331 startupPref
.value
!== gMainPane
.STARTUP_PREF_BLANK
&& HomePage
.isDefault
337 * @returns {bool} Is the homepage set to about:blank?
340 const startupPref
= Preferences
.get("browser.startup.page");
342 ["about:blank", BLANK_HOMEPAGE_URL
, ""].includes(HomePage
.get()) ||
343 startupPref
.value
=== gMainPane
.STARTUP_PREF_BLANK
348 * _isTabAboutPreferencesOrSettings: Is a given tab set to about:preferences or about:settings?
349 * @param {Element} aTab A tab element
350 * @returns {bool} Is the linkedBrowser of aElement set to about:preferences or about:settings?
352 _isTabAboutPreferencesOrSettings(aTab
) {
354 aTab
.linkedBrowser
.currentURI
.spec
.startsWith("about:preferences") ||
355 aTab
.linkedBrowser
.currentURI
.spec
.startsWith("about:settings")
360 * _getTabsForHomePage
361 * @returns {Array} An array of current tabs
363 _getTabsForHomePage() {
365 let win
= Services
.wm
.getMostRecentWindow("navigator:browser");
367 // We should only include visible & non-pinned tabs
370 win
.document
.documentElement
.getAttribute("windowtype") ===
373 tabs
= win
.gBrowser
.visibleTabs
.slice(win
.gBrowser
.pinnedTabCount
);
374 tabs
= tabs
.filter(tab
=> !this._isTabAboutPreferencesOrSettings(tab
));
375 // XXX: Bug 1441637 - Fix tabbrowser to report tab.closing before it blurs it
376 tabs
= tabs
.filter(tab
=> !tab
.closing
);
382 _renderHomepageMode(controllingExtension
) {
383 const isDefault
= this._isHomePageDefaultValue();
384 const isBlank
= this.isHomePageBlank();
385 const el
= document
.getElementById("homeMode");
388 if (controllingExtension
&& controllingExtension
.id
) {
389 newValue
= controllingExtension
.id
;
390 } else if (isDefault
) {
391 newValue
= this.HOME_MODE_FIREFOX_HOME
;
392 } else if (isBlank
) {
393 newValue
= this.HOME_MODE_BLANK
;
395 newValue
= this.HOME_MODE_CUSTOM
;
397 if (el
.value
!== newValue
) {
402 _setInputDisabledStates(isControlled
) {
403 let tabCount
= this._getTabsForHomePage().length
;
405 // Disable or enable the inputs based on if this is controlled by an extension.
407 .querySelectorAll(".check-home-page-controlled")
408 .forEach(element
=> {
411 element
.getAttribute("preference") ||
412 element
.getAttribute("data-preference-related");
415 `Element with id ${element.id} did not have preference or data-preference-related attribute defined.`
419 if (pref
=== this.HOMEPAGE_PREF
) {
420 isDisabled
= HomePage
.locked
;
422 isDisabled
= Preferences
.get(pref
).locked
|| isControlled
;
425 if (pref
=== "pref.browser.disable_button.current_page") {
426 // Special case for current_page to disable it if tabCount is 0
427 isDisabled
= isDisabled
|| tabCount
< 1;
430 element
.disabled
= isDisabled
;
434 async
_handleHomePageOverrides() {
435 let controllingExtension
;
436 if (HomePage
.locked
) {
437 // Disable inputs if they are locked.
438 this._renderCustomSettings();
439 this._setInputDisabledStates(false);
441 if (HomePage
.get().startsWith("moz-extension:")) {
442 controllingExtension
= await
getControllingExtension(
444 HOMEPAGE_OVERRIDE_KEY
447 this._setInputDisabledStates();
448 this._renderCustomSettings({
449 isControlled
: !!controllingExtension
,
452 this._renderHomepageMode(controllingExtension
);
455 onMenuChange(event
) {
456 const { value
} = event
.target
;
457 const startupPref
= Preferences
.get("browser.startup.page");
458 let selectedAddon
= ExtensionSettingsStore
.getSetting(
460 HOMEPAGE_OVERRIDE_KEY
464 case this.HOME_MODE_FIREFOX_HOME
:
465 if (startupPref
.value
=== gMainPane
.STARTUP_PREF_BLANK
) {
466 startupPref
.value
= gMainPane
.STARTUP_PREF_HOMEPAGE
;
468 if (!HomePage
.isDefault
) {
471 this._renderCustomSettings({ shouldShow
: false });
474 ExtensionSettingsStore
.select(
477 HOMEPAGE_OVERRIDE_KEY
481 case this.HOME_MODE_BLANK
:
482 if (!this._isBlankPage(HomePage
.get())) {
483 HomePage
.safeSet(BLANK_HOMEPAGE_URL
);
485 this._renderCustomSettings({ shouldShow
: false });
488 ExtensionSettingsStore
.select(
491 HOMEPAGE_OVERRIDE_KEY
495 case this.HOME_MODE_CUSTOM
:
496 if (startupPref
.value
=== gMainPane
.STARTUP_PREF_BLANK
) {
497 Services
.prefs
.clearUserPref(startupPref
.id
);
499 if (HomePage
.getDefault() != HomePage
.getOriginalDefault()) {
502 this._renderCustomSettings({ shouldShow
: true });
504 ExtensionSettingsStore
.select(
507 HOMEPAGE_OVERRIDE_KEY
511 // extensions will have a variety of values as their ID, so treat it as default
513 AddonManager
.getAddonByID(value
).then(addon
=> {
514 if (addon
&& addon
.isActive
) {
515 ExtensionPreferencesManager
.selectSetting(
517 HOMEPAGE_OVERRIDE_KEY
520 this._renderCustomSettings({ shouldShow
: false });
526 * Switches the "Use Current Page" button between its singular and plural
529 async
_updateUseCurrentButton() {
530 let useCurrent
= document
.getElementById("useCurrentBtn");
531 let tabs
= this._getTabsForHomePage();
532 const tabCount
= tabs
.length
;
533 document
.l10n
.setAttributes(useCurrent
, "use-current-pages", { tabCount
});
535 // If the homepage is controlled by an extension then you can't use this.
537 await
getControllingExtensionInfo(
539 HOMEPAGE_OVERRIDE_KEY
545 // In this case, the button's disabled state is set by preferences.xml.
546 let prefName
= "pref.browser.homepage.disable_button.current_page";
547 if (Preferences
.get(prefName
).locked
) {
551 useCurrent
.disabled
= tabCount
< 1;
555 * Sets the home page to the URL(s) of any currently opened tab(s),
556 * updating about:preferences#home UI to reflect this.
558 setHomePageToCurrent() {
559 let tabs
= this._getTabsForHomePage();
560 function getTabURI(t
) {
561 return t
.linkedBrowser
.currentURI
.spec
;
564 // FIXME Bug 244192: using dangerous "|" joiner!
566 HomePage
.set(tabs
.map(getTabURI
).join("|")).catch(console
.error
);
570 _setHomePageToBookmarkClosed(rv
, aEvent
) {
571 if (aEvent
.detail
.button
!= "accept") {
574 if (rv
.urls
&& rv
.names
) {
575 // XXX still using dangerous "|" joiner!
576 HomePage
.set(rv
.urls
.join("|")).catch(console
.error
);
581 * Displays a dialog in which the user can select a bookmark to use as home
582 * page. If the user selects a bookmark, that bookmark's name is displayed in
583 * UI and the bookmark's address is stored to the home page preference.
585 setHomePageToBookmark() {
586 const rv
= { urls
: null, names
: null };
588 "chrome://browser/content/preferences/dialogs/selectBookmark.xhtml",
590 features
: "resizable=yes, modal=yes",
591 closingCallback
: this._setHomePageToBookmarkClosed
.bind(this, rv
),
597 restoreDefaultHomePage() {
599 this._handleHomePageOverrides();
600 Services
.prefs
.clearUserPref(this.NEWTAB_ENABLED_PREF
);
601 AboutNewTab
.resetNewTabURL();
604 onCustomHomePageChange(event
) {
605 const value
= event
.target
.value
|| HomePage
.getDefault();
606 HomePage
.set(value
).catch(console
.error
);
610 * Check all Home Tab preferences for user set values.
612 _changedHomeTabDefaultPrefs() {
613 // If Discovery Stream is enabled Firefox Home Content preference options are hidden
614 const homeContentChanged
=
615 !this.isPocketNewtabEnabled
&&
616 this.homePanePrefs
.some(pref
=> pref
.hasUserValue
);
617 const newtabPref
= Preferences
.get(this.NEWTAB_ENABLED_PREF
);
618 const extensionControlled
= Preferences
.get(
619 "browser.startup.homepage_override.extensionControlled"
623 homeContentChanged
||
624 HomePage
.overridden
||
625 newtabPref
.hasUserValue
||
626 AboutNewTab
.newTabURLOverridden
||
632 return url
== "about:blank" || url
== BLANK_HOMEPAGE_URL
;
636 * Show the Restore Defaults button if any preference on the Home tab was
637 * changed, or hide it otherwise.
639 toggleRestoreDefaultsBtn() {
640 const btn
= document
.getElementById("restoreDefaultHomePageBtn");
641 const prefChanged
= this._changedHomeTabDefaultPrefs();
643 btn
.style
.removeProperty("visibility");
645 btn
.style
.visibility
= "hidden";
650 * Set all prefs on the Home tab back to their default values.
652 restoreDefaultPrefsForHome() {
653 this.restoreDefaultHomePage();
654 // If Discovery Stream is enabled Firefox Home Content preference options are hidden
655 if (!this.isPocketNewtabEnabled
) {
656 this.homePanePrefs
.forEach(pref
=> Services
.prefs
.clearUserPref(pref
.id
));
663 .getElementById("homePageUrl")
664 .addEventListener("change", this.onCustomHomePageChange
.bind(this));
666 .getElementById("useCurrentBtn")
667 .addEventListener("command", this.setHomePageToCurrent
.bind(this));
669 .getElementById("useBookmarkBtn")
670 .addEventListener("command", this.setHomePageToBookmark
.bind(this));
672 .getElementById("restoreDefaultHomePageBtn")
673 .addEventListener("command", this.restoreDefaultPrefsForHome
.bind(this));
675 // Setup the add-on options for the new tab section before registering the
677 this._updateMenuInterface();
679 .getElementById("newTabMode")
680 .addEventListener("command", this.syncToNewTabPref
.bind(this));
682 .getElementById("homeMode")
683 .addEventListener("command", this.onMenuChange
.bind(this));
685 this._updateUseCurrentButton();
686 this._handleHomePageOverrides();
687 this.syncFromNewTabPref();
688 window
.addEventListener("focus", this._updateUseCurrentButton
.bind(this));
690 // Extension/override-related events
692 this.watchHomePrefChange();
693 this.watchExtensionPrefChange();
694 this.watchHomeTabPrefChange();
695 // Notify observers that the UI is now ready
696 Services
.obs
.notifyObservers(window
, "home-pane-loaded");