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 };