main.js (233051B)
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 letterboxing.js */ 7 /* import-globals-from preferences.js */ 8 /* import-globals-from /toolkit/mozapps/preferences/fontbuilder.js */ 9 /* import-globals-from /browser/base/content/aboutDialog-appUpdater.js */ 10 /* global MozXULElement */ 11 12 ChromeUtils.defineESModuleGetters(this, { 13 BackgroundUpdate: "resource://gre/modules/BackgroundUpdate.sys.mjs", 14 UpdateListener: "resource://gre/modules/UpdateListener.sys.mjs", 15 // LinkPreview.sys.mjs is missing. tor-browser#44045. 16 MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs", 17 SelectableProfileService: 18 "resource:///modules/profiles/SelectableProfileService.sys.mjs", 19 TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs", 20 WindowsLaunchOnLogin: "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs", 21 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 22 FormAutofillPreferences: 23 "resource://autofill/FormAutofillPreferences.sys.mjs", 24 getMozRemoteImageURL: "moz-src:///browser/modules/FaviconUtils.sys.mjs", 25 }); 26 27 // Constants & Enumeration Values 28 const TYPE_PDF = "application/pdf"; 29 30 const PREF_PDFJS_DISABLED = "pdfjs.disabled"; 31 32 // Pref for when containers is being controlled 33 const PREF_CONTAINERS_EXTENSION = "privacy.userContext.extension"; 34 35 // Strings to identify ExtensionSettingsStore overrides 36 const CONTAINERS_KEY = "privacy.containers"; 37 38 const FORCED_COLORS_QUERY = matchMedia("(forced-colors)"); 39 40 const AUTO_UPDATE_CHANGED_TOPIC = 41 UpdateUtils.PER_INSTALLATION_PREFS["app.update.auto"].observerTopic; 42 const BACKGROUND_UPDATE_CHANGED_TOPIC = 43 UpdateUtils.PER_INSTALLATION_PREFS["app.update.background.enabled"] 44 .observerTopic; 45 46 const ICON_URL_APP = 47 AppConstants.platform == "linux" 48 ? "moz-icon://dummy.exe?size=16" 49 : "chrome://browser/skin/preferences/application.png"; 50 51 // For CSS. Can be one of "ask", "save" or "handleInternally". If absent, the icon URL 52 // was set by us to a custom handler icon and CSS should not try to override it. 53 const APP_ICON_ATTR_NAME = "appHandlerIcon"; 54 55 const OPEN_EXTERNAL_LINK_NEXT_TO_ACTIVE_TAB_VALUE = 56 Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT; 57 58 Preferences.addAll([ 59 // Startup 60 { id: "browser.startup.page", type: "int" }, 61 { id: "browser.startup.windowsLaunchOnLogin.enabled", type: "bool" }, 62 { id: "browser.privatebrowsing.autostart", type: "bool" }, 63 64 // Downloads 65 { id: "browser.download.useDownloadDir", type: "bool", inverted: true }, 66 { id: "browser.download.enableDeletePrivate", type: "bool" }, 67 { id: "browser.download.deletePrivate", type: "bool" }, 68 { id: "browser.download.always_ask_before_handling_new_types", type: "bool" }, 69 { id: "browser.download.folderList", type: "int" }, 70 { id: "browser.download.dir", type: "file" }, 71 72 /* Tab preferences 73 Preferences: 74 75 browser.link.open_newwindow 76 1 opens such links in the most recent window or tab, 77 2 opens such links in a new window, 78 3 opens such links in a new tab 79 browser.link.open_newwindow.override.external 80 - this setting overrides `browser.link.open_newwindow` for externally 81 opened links. 82 - see `nsIBrowserDOMWindow` constants for the meaning of each value. 83 browser.tabs.loadInBackground 84 - true if display should switch to a new tab which has been opened from a 85 link, false if display shouldn't switch 86 browser.tabs.warnOnClose 87 - true if when closing a window with multiple tabs the user is warned and 88 allowed to cancel the action, false to just close the window 89 browser.tabs.warnOnOpen 90 - true if the user should be warned if he attempts to open a lot of tabs at 91 once (e.g. a large folder of bookmarks), false otherwise 92 browser.warnOnQuitShortcut 93 - true if the user should be warned if they quit using the keyboard shortcut 94 browser.taskbar.previews.enable 95 - true if tabs are to be shown in the Windows 7 taskbar 96 */ 97 98 { id: "browser.link.open_newwindow", type: "int" }, 99 { id: "browser.link.open_newwindow.override.external", type: "int" }, 100 { id: "browser.tabs.loadInBackground", type: "bool", inverted: true }, 101 { id: "browser.tabs.warnOnClose", type: "bool" }, 102 { id: "browser.warnOnQuitShortcut", type: "bool" }, 103 { id: "browser.tabs.warnOnOpen", type: "bool" }, 104 { id: "browser.ctrlTab.sortByRecentlyUsed", type: "bool" }, 105 { id: "browser.tabs.hoverPreview.enabled", type: "bool" }, 106 { id: "browser.tabs.hoverPreview.showThumbnails", type: "bool" }, 107 { id: "browser.tabs.groups.smart.userEnabled", type: "bool" }, 108 { id: "browser.tabs.groups.smart.enabled", type: "bool" }, 109 { id: "privacy.userContext.ui.enabled", type: "bool" }, 110 111 { id: "sidebar.verticalTabs", type: "bool" }, 112 { id: "sidebar.revamp", type: "bool" }, 113 114 // CFR 115 { 116 id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", 117 type: "bool", 118 }, 119 { 120 id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", 121 type: "bool", 122 }, 123 124 // High Contrast 125 { id: "browser.display.document_color_use", type: "int" }, 126 127 // Fonts 128 { id: "font.language.group", type: "string" }, 129 130 // Languages 131 { id: "intl.regional_prefs.use_os_locales", type: "bool" }, 132 133 // General tab 134 135 /* Accessibility 136 * accessibility.browsewithcaret 137 - true enables keyboard navigation and selection within web pages using a 138 visible caret, false uses normal keyboard navigation with no caret 139 * accessibility.typeaheadfind 140 - when set to true, typing outside text areas and input boxes will 141 automatically start searching for what's typed within the current 142 document; when set to false, no search action happens */ 143 { id: "accessibility.browsewithcaret", type: "bool" }, 144 { id: "accessibility.typeaheadfind", type: "bool" }, 145 { id: "accessibility.blockautorefresh", type: "bool" }, 146 147 /* Zoom */ 148 { id: "browser.zoom.full", type: "bool" }, 149 150 /* Browsing 151 * general.autoScroll 152 - when set to true, clicking the scroll wheel on the mouse activates a 153 mouse mode where moving the mouse down scrolls the document downward with 154 speed correlated with the distance of the cursor from the original 155 position at which the click occurred (and likewise with movement upward); 156 if false, this behavior is disabled 157 * general.smoothScroll 158 - set to true to enable finer page scrolling than line-by-line on page-up, 159 page-down, and other such page movements */ 160 { id: "general.autoScroll", type: "bool" }, 161 { id: "general.smoothScroll", type: "bool" }, 162 { id: "widget.gtk.overlay-scrollbars.enabled", type: "bool", inverted: true }, 163 { id: "layout.css.always_underline_links", type: "bool" }, 164 { id: "layout.spellcheckDefault", type: "int" }, 165 { id: "accessibility.tabfocus", type: "int" }, 166 { id: "browser.ml.linkPreview.enabled", type: "bool" }, 167 { id: "browser.ml.linkPreview.optin", type: "bool" }, 168 { id: "browser.ml.linkPreview.longPress", type: "bool" }, 169 170 { 171 id: "browser.preferences.defaultPerformanceSettings.enabled", 172 type: "bool", 173 }, 174 { id: "dom.ipc.processCount", type: "int" }, 175 { id: "dom.ipc.processCount.web", type: "int" }, 176 { id: "layers.acceleration.disabled", type: "bool", inverted: true }, 177 178 // Files and Applications 179 { id: "pref.downloads.disable_button.edit_actions", type: "bool" }, 180 181 // DRM content 182 { id: "media.eme.enabled", type: "bool" }, 183 184 // Update 185 { id: "browser.preferences.advanced.selectedTabIndex", type: "int" }, 186 { id: "browser.search.update", type: "bool" }, 187 188 { id: "privacy.userContext.enabled", type: "bool" }, 189 { 190 id: "privacy.userContext.newTabContainerOnLeftClick.enabled", 191 type: "bool", 192 }, 193 194 // Picture-in-Picture 195 { 196 id: "media.videocontrols.picture-in-picture.video-toggle.enabled", 197 type: "bool", 198 }, 199 { 200 id: "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled", 201 type: "bool", 202 }, 203 204 // Media 205 { id: "media.hardwaremediakeys.enabled", type: "bool" }, 206 207 // Appearance 208 { id: "layout.css.prefers-color-scheme.content-override", type: "int" }, 209 210 // Translations 211 { id: "browser.translations.automaticallyPopup", type: "bool" }, 212 ]); 213 214 if (AppConstants.HAVE_SHELL_SERVICE) { 215 Preferences.addAll([ 216 { id: "browser.shell.checkDefaultBrowser", type: "bool" }, 217 { id: "pref.general.disable_button.default_browser", type: "bool" }, 218 ]); 219 } 220 221 if (AppConstants.platform === "win") { 222 Preferences.addAll([ 223 { id: "browser.taskbar.previews.enable", type: "bool" }, 224 { id: "ui.osk.enabled", type: "bool" }, 225 ]); 226 } 227 228 if (AppConstants.MOZ_UPDATER) { 229 Preferences.addAll([ 230 { id: "app.update.disable_button.showUpdateHistory", type: "bool" }, 231 ]); 232 233 if (AppConstants.NIGHTLY_BUILD) { 234 Preferences.addAll([{ id: "app.update.suppressPrompts", type: "bool" }]); 235 } 236 } 237 238 Preferences.addSetting({ 239 id: "privateBrowsingAutoStart", 240 pref: "browser.privatebrowsing.autostart", 241 }); 242 243 Preferences.addSetting( 244 /** @type {{ _getLaunchOnLoginApprovedCachedValue: boolean } & SettingConfig} */ ({ 245 id: "launchOnLoginApproved", 246 _getLaunchOnLoginApprovedCachedValue: true, 247 get() { 248 return this._getLaunchOnLoginApprovedCachedValue; 249 }, 250 // Check for a launch on login registry key 251 // This accounts for if a user manually changes it in the registry 252 // Disabling in Task Manager works outside of just deleting the registry key 253 // in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run 254 // but it is not possible to change it back to enabled as the disabled value is just a random 255 // hexadecimal number 256 setup() { 257 if (AppConstants.platform !== "win") { 258 /** 259 * WindowsLaunchOnLogin isnt available if not on windows 260 * but this setup function still fires, so must prevent 261 * WindowsLaunchOnLogin.getLaunchOnLoginApproved 262 * below from executing unnecessarily. 263 */ 264 return; 265 } 266 // @ts-ignore bug 1996860 267 WindowsLaunchOnLogin.getLaunchOnLoginApproved().then(val => { 268 this._getLaunchOnLoginApprovedCachedValue = val; 269 }); 270 }, 271 }) 272 ); 273 274 Preferences.addSetting({ 275 id: "windowsLaunchOnLoginEnabled", 276 pref: "browser.startup.windowsLaunchOnLogin.enabled", 277 }); 278 279 Preferences.addSetting( 280 /** @type {{_getLaunchOnLoginEnabledValue: boolean, startWithLastProfile: boolean} & SettingConfig} */ ({ 281 id: "windowsLaunchOnLogin", 282 deps: ["launchOnLoginApproved", "windowsLaunchOnLoginEnabled"], 283 _getLaunchOnLoginEnabledValue: false, 284 get startWithLastProfile() { 285 return Cc["@mozilla.org/toolkit/profile-service;1"].getService( 286 Ci.nsIToolkitProfileService 287 ).startWithLastProfile; 288 }, 289 get() { 290 return this._getLaunchOnLoginEnabledValue; 291 }, 292 setup(emitChange) { 293 if (AppConstants.platform !== "win") { 294 /** 295 * WindowsLaunchOnLogin isnt available if not on windows 296 * but this setup function still fires, so must prevent 297 * WindowsLaunchOnLogin.getLaunchOnLoginEnabled 298 * below from executing unnecessarily. 299 */ 300 return; 301 } 302 303 /** @type {boolean} */ 304 let getLaunchOnLoginEnabledValue; 305 let maybeEmitChange = () => { 306 if ( 307 getLaunchOnLoginEnabledValue !== this._getLaunchOnLoginEnabledValue 308 ) { 309 this._getLaunchOnLoginEnabledValue = getLaunchOnLoginEnabledValue; 310 emitChange(); 311 } 312 }; 313 if (!this.startWithLastProfile) { 314 getLaunchOnLoginEnabledValue = false; 315 maybeEmitChange(); 316 } else { 317 // @ts-ignore bug 1996860 318 WindowsLaunchOnLogin.getLaunchOnLoginEnabled().then(val => { 319 getLaunchOnLoginEnabledValue = val; 320 maybeEmitChange(); 321 }); 322 } 323 }, 324 visible: ({ windowsLaunchOnLoginEnabled }) => { 325 let isVisible = 326 AppConstants.platform === "win" && windowsLaunchOnLoginEnabled.value; 327 if (isVisible) { 328 // @ts-ignore bug 1996860 329 NimbusFeatures.windowsLaunchOnLogin.recordExposureEvent({ 330 once: true, 331 }); 332 } 333 return isVisible; 334 }, 335 disabled({ launchOnLoginApproved }) { 336 return !this.startWithLastProfile || !launchOnLoginApproved.value; 337 }, 338 onUserChange(checked) { 339 if (checked) { 340 // windowsLaunchOnLogin has been checked: create registry key or shortcut 341 // The shortcut is created with the same AUMID as Firefox itself. However, 342 // this is not set during browser tests and the fallback of checking the 343 // registry fails. As such we pass an arbitrary AUMID for the purpose 344 // of testing. 345 // @ts-ignore bug 1996860 346 WindowsLaunchOnLogin.createLaunchOnLogin(); 347 Services.prefs.setBoolPref( 348 "browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt", 349 true 350 ); 351 } else { 352 // windowsLaunchOnLogin has been unchecked: delete registry key and shortcut 353 // @ts-ignore bug 1996860 354 WindowsLaunchOnLogin.removeLaunchOnLogin(); 355 } 356 }, 357 }) 358 ); 359 360 Preferences.addSetting({ 361 id: "windowsLaunchOnLoginDisabledProfileBox", 362 deps: ["windowsLaunchOnLoginEnabled"], 363 visible: ({ windowsLaunchOnLoginEnabled }) => { 364 if (AppConstants.platform !== "win") { 365 return false; 366 } 367 let startWithLastProfile = Cc[ 368 "@mozilla.org/toolkit/profile-service;1" 369 ].getService(Ci.nsIToolkitProfileService).startWithLastProfile; 370 371 return !startWithLastProfile && windowsLaunchOnLoginEnabled.value; 372 }, 373 }); 374 375 Preferences.addSetting({ 376 id: "windowsLaunchOnLoginDisabledBox", 377 deps: ["launchOnLoginApproved", "windowsLaunchOnLoginEnabled"], 378 visible: ({ launchOnLoginApproved, windowsLaunchOnLoginEnabled }) => { 379 if (AppConstants.platform !== "win") { 380 return false; 381 } 382 let startWithLastProfile = Cc[ 383 "@mozilla.org/toolkit/profile-service;1" 384 ].getService(Ci.nsIToolkitProfileService).startWithLastProfile; 385 386 return ( 387 startWithLastProfile && 388 !launchOnLoginApproved.value && 389 windowsLaunchOnLoginEnabled.value 390 ); 391 }, 392 }); 393 394 Preferences.addSetting({ 395 /** 396 * The "Open previous windows and tabs" option on about:preferences page. 397 */ 398 id: "browserRestoreSession", 399 pref: "browser.startup.page", 400 deps: ["privateBrowsingAutoStart"], 401 get: 402 /** 403 * Returns the value of the "Open previous windows and tabs" option based 404 * on the value of the browser.privatebrowsing.autostart pref. 405 * 406 * @param {number | undefined} value 407 * @returns {boolean} 408 */ 409 value => { 410 const pbAutoStartPref = Preferences.get( 411 "browser.privatebrowsing.autostart" 412 ); 413 let newValue = pbAutoStartPref.value 414 ? false 415 : value === gMainPane.STARTUP_PREF_RESTORE_SESSION; 416 417 return newValue; 418 }, 419 set: checked => { 420 const startupPref = Preferences.get("browser.startup.page"); 421 let newValue; 422 423 if (checked) { 424 // We need to restore the blank homepage setting in our other pref 425 if (startupPref.value === gMainPane.STARTUP_PREF_BLANK) { 426 // @ts-ignore bug 1996860 427 HomePage.safeSet("about:blank"); 428 } 429 newValue = gMainPane.STARTUP_PREF_RESTORE_SESSION; 430 } else { 431 newValue = gMainPane.STARTUP_PREF_HOMEPAGE; 432 } 433 return newValue; 434 }, 435 disabled: deps => { 436 return deps.privateBrowsingAutoStart.value; 437 }, 438 }); 439 440 Preferences.addSetting({ 441 id: "useAutoScroll", 442 pref: "general.autoScroll", 443 }); 444 Preferences.addSetting({ 445 id: "useSmoothScrolling", 446 pref: "general.smoothScroll", 447 }); 448 449 Preferences.addSetting({ 450 id: "useOverlayScrollbars", 451 pref: "widget.gtk.overlay-scrollbars.enabled", 452 visible: () => AppConstants.MOZ_WIDGET_GTK, 453 }); 454 Preferences.addSetting({ 455 id: "useOnScreenKeyboard", 456 // Bug 1993053: Restore the pref to `ui.osk.enabled` after changing 457 // the PrefereceNotFoundError throwing behavior. 458 pref: AppConstants.platform == "win" ? "ui.osk.enabled" : undefined, 459 visible: () => AppConstants.platform == "win", 460 }); 461 Preferences.addSetting({ 462 id: "useCursorNavigation", 463 pref: "accessibility.browsewithcaret", 464 }); 465 Preferences.addSetting( 466 /** @type {{ _storedFullKeyboardNavigation: number } & SettingConfig} */ ({ 467 _storedFullKeyboardNavigation: -1, 468 id: "useFullKeyboardNavigation", 469 pref: "accessibility.tabfocus", 470 visible: () => AppConstants.platform == "macosx", 471 /** 472 * Returns true if any full keyboard nav is enabled and false otherwise, caching 473 * the current value to enable proper pref restoration if the checkbox is 474 * never changed. 475 * 476 * accessibility.tabfocus 477 * - an integer controlling the focusability of: 478 * 1 text controls 479 * 2 form elements 480 * 4 links 481 * 7 all of the above 482 */ 483 get(prefVal) { 484 this._storedFullKeyboardNavigation = prefVal; 485 return prefVal == 7; 486 }, 487 /** 488 * Returns the value of the full keyboard nav preference represented by UI, 489 * preserving the preference's "hidden" value if the preference is 490 * unchanged and represents a value not strictly allowed in UI. 491 */ 492 set(checked) { 493 if (checked) { 494 return 7; 495 } 496 if (this._storedFullKeyboardNavigation != 7) { 497 // 1/2/4 values set via about:config should persist 498 return this._storedFullKeyboardNavigation; 499 } 500 // When the checkbox is unchecked, default to just text controls. 501 return 1; 502 }, 503 }) 504 ); 505 Preferences.addSetting({ 506 id: "linkPreviewEnabled", 507 pref: "browser.ml.linkPreview.enabled", 508 // @ts-ignore bug 1996860 509 visible: () => false, // LinkPreview is missing. tor-browser#44045. 510 }); 511 Preferences.addSetting({ 512 id: "linkPreviewKeyPoints", 513 pref: "browser.ml.linkPreview.optin", 514 // @ts-ignore bug 1996860 515 visible: () => false, // LinkPreview is missing. tor-browser#44045. 516 }); 517 Preferences.addSetting({ 518 id: "linkPreviewLongPress", 519 pref: "browser.ml.linkPreview.longPress", 520 }); 521 Preferences.addSetting({ 522 id: "alwaysUnderlineLinks", 523 pref: "layout.css.always_underline_links", 524 }); 525 Preferences.addSetting({ 526 id: "searchStartTyping", 527 pref: "accessibility.typeaheadfind", 528 }); 529 Preferences.addSetting({ 530 id: "pictureInPictureToggleEnabled", 531 pref: "media.videocontrols.picture-in-picture.video-toggle.enabled", 532 visible: () => 533 Services.prefs.getBoolPref( 534 "media.videocontrols.picture-in-picture.enabled" 535 ), 536 onUserChange(checked) { 537 if (!checked) { 538 Glean.pictureinpictureSettings.disableSettings.record(); 539 } 540 }, 541 }); 542 Preferences.addSetting({ 543 id: "pictureInPictureEnableWhenSwitchingTabs", 544 pref: "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled", 545 deps: ["pictureInPictureToggleEnabled"], 546 onUserChange(checked) { 547 if (checked) { 548 Glean.pictureinpictureSettings.enableAutotriggerSettings.record(); 549 } 550 }, 551 }); 552 Preferences.addSetting({ 553 id: "mediaControlToggleEnabled", 554 pref: "media.hardwaremediakeys.enabled", 555 // For media control toggle button, we support it on Windows, macOS and 556 // gtk-based Linux. 557 visible: () => 558 AppConstants.platform == "win" || 559 AppConstants.platform == "macosx" || 560 AppConstants.MOZ_WIDGET_GTK, 561 }); 562 Preferences.addSetting({ 563 id: "playDRMContent", 564 pref: "media.eme.enabled", 565 visible: () => { 566 if (!Services.prefs.getBoolPref("browser.eme.ui.enabled", false)) { 567 return false; 568 } 569 if (AppConstants.platform == "win") { 570 try { 571 return parseFloat(Services.sysinfo.get("version")) >= 6; 572 } catch (ex) { 573 return false; 574 } 575 } 576 return true; 577 }, 578 }); 579 Preferences.addSetting({ 580 id: "cfrRecommendations", 581 pref: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", 582 }); 583 Preferences.addSetting({ 584 id: "cfrRecommendations-features", 585 pref: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", 586 }); 587 Preferences.addSetting({ 588 id: "web-appearance-override-warning", 589 setup: emitChange => { 590 FORCED_COLORS_QUERY.addEventListener("change", emitChange); 591 return () => FORCED_COLORS_QUERY.removeEventListener("change", emitChange); 592 }, 593 visible: () => { 594 return FORCED_COLORS_QUERY.matches; 595 }, 596 }); 597 598 Preferences.addSetting( 599 /** @type {{ themeNames: string[] } & SettingConfig}} */ ({ 600 id: "web-appearance-chooser", 601 themeNames: ["dark", "light", "auto"], 602 pref: "layout.css.prefers-color-scheme.content-override", 603 setup(emitChange) { 604 Services.obs.addObserver(emitChange, "look-and-feel-changed"); 605 return () => 606 Services.obs.removeObserver(emitChange, "look-and-feel-changed"); 607 }, 608 get(val, _, setting) { 609 return ( 610 this.themeNames[val] || 611 this.themeNames[/** @type {number} */ (setting.pref.defaultValue)] 612 ); 613 }, 614 /** @param {string} val */ 615 set(val) { 616 return this.themeNames.indexOf(val); 617 }, 618 getControlConfig(config) { 619 // Set the auto theme image to the light/dark that matches. 620 let systemThemeIndex = Services.appinfo 621 .contentThemeDerivedColorSchemeIsDark 622 ? 2 623 : 1; 624 config.options[0].controlAttrs = { 625 ...config.options[0].controlAttrs, 626 imagesrc: config.options[systemThemeIndex].controlAttrs.imagesrc, 627 }; 628 return config; 629 }, 630 }) 631 ); 632 633 Preferences.addSetting({ 634 id: "web-appearance-manage-themes-link", 635 onUserClick: e => { 636 e.preventDefault(); 637 // @ts-ignore topChromeWindow global 638 window.browsingContext.topChromeWindow.BrowserAddonUI.openAddonsMgr( 639 "addons://list/theme" 640 ); 641 }, 642 }); 643 644 Preferences.addSetting({ 645 id: "containersPane", 646 onUserClick(e) { 647 e.preventDefault(); 648 gotoPref("paneContainers2"); 649 }, 650 }); 651 Preferences.addSetting({ id: "containersPlaceholder" }); 652 653 Preferences.addSetting({ 654 id: "offerTranslations", 655 pref: "browser.translations.automaticallyPopup", 656 }); 657 658 function createNeverTranslateSitesDescription() { 659 const description = document.createElement("span"); 660 description.dataset.l10nId = 661 "settings-translations-subpage-never-translate-sites-description"; 662 663 for (const [name, src] of [ 664 ["translations-icon", "chrome://browser/skin/translations.svg"], 665 ["settings-icon", "chrome://global/skin/icons/settings.svg"], 666 ]) { 667 const icon = document.createElement("img"); 668 icon.src = src; 669 670 icon.dataset.l10nName = name; 671 icon.style.verticalAlign = "middle"; 672 673 icon.setAttribute("role", "presentation"); 674 icon.setAttribute("width", "16"); 675 icon.setAttribute("height", "16"); 676 677 description.appendChild(icon); 678 } 679 680 return description; 681 } 682 683 Preferences.addSetting({ 684 id: "translationsDownloadLanguagesGroup", 685 }); 686 687 Preferences.addSetting({ 688 id: "translationsDownloadLanguagesRow", 689 }); 690 691 Preferences.addSetting({ 692 id: "translationsDownloadLanguagesSelect", 693 }); 694 695 Preferences.addSetting({ 696 id: "translationsDownloadLanguagesButton", 697 }); 698 699 Preferences.addSetting({ 700 id: "translationsDownloadLanguagesNoneRow", 701 }); 702 703 Preferences.addSetting({ 704 id: "translationsAlwaysTranslateLanguagesGroup", 705 }); 706 707 Preferences.addSetting({ 708 id: "translationsAlwaysTranslateLanguagesRow", 709 }); 710 711 Preferences.addSetting({ 712 id: "translationsAlwaysTranslateLanguagesSelect", 713 }); 714 715 Preferences.addSetting({ 716 id: "translationsAlwaysTranslateLanguagesNoneRow", 717 }); 718 719 Preferences.addSetting({ 720 id: "translationsAlwaysTranslateLanguagesButton", 721 }); 722 723 Preferences.addSetting({ 724 id: "translationsNeverTranslateLanguagesNoneRow", 725 }); 726 727 Preferences.addSetting({ 728 id: "translationsNeverTranslateLanguagesButton", 729 }); 730 731 Preferences.addSetting({ 732 id: "translationsNeverTranslateLanguagesGroup", 733 }); 734 735 Preferences.addSetting({ 736 id: "translationsNeverTranslateLanguagesRow", 737 }); 738 739 Preferences.addSetting({ 740 id: "translationsNeverTranslateLanguagesSelect", 741 }); 742 743 Preferences.addSetting({ 744 id: "translationsNeverTranslateSitesGroup", 745 }); 746 747 Preferences.addSetting({ 748 id: "translationsNeverTranslateSitesRow", 749 }); 750 751 Preferences.addSetting({ 752 id: "translationsNeverTranslateSitesNoneRow", 753 }); 754 755 Preferences.addSetting({ 756 id: "translationsManageButton", 757 onUserClick(e) { 758 e.preventDefault(); 759 gotoPref("paneTranslations"); 760 }, 761 }); 762 763 Preferences.addSetting({ 764 id: "data-migration", 765 visible: () => 766 !Services.policies || Services.policies.isAllowed("profileImport"), 767 onUserClick() { 768 const browserWindow = window.browsingContext.topChromeWindow; 769 MigrationUtils.showMigrationWizard(browserWindow, { 770 entrypoint: MigrationUtils.MIGRATION_ENTRYPOINTS.PREFERENCES, 771 }); 772 }, 773 }); 774 775 Preferences.addSetting({ 776 id: "connectionSettings", 777 onUserClick: () => gMainPane.showConnections(), 778 }); 779 780 Preferences.addSetting({ 781 id: "profilesPane", 782 onUserClick(e) { 783 e.preventDefault(); 784 gotoPref("paneProfiles"); 785 }, 786 }); 787 Preferences.addSetting({ 788 id: "profilesSettings", 789 visible() { 790 return SelectableProfileService.isEnabled; 791 }, 792 onUserClick: e => { 793 e.preventDefault(); 794 gotoPref("profiles"); 795 }, 796 }); 797 Preferences.addSetting({ 798 id: "manageProfiles", 799 onUserClick: e => { 800 e.preventDefault(); 801 // Using the existing function for now, since privacy.js also calls it 802 gMainPane.manageProfiles(); 803 }, 804 }); 805 Preferences.addSetting({ 806 id: "copyProfile", 807 deps: ["copyProfileSelect"], 808 disabled: ({ copyProfileSelect }) => !copyProfileSelect.value, 809 onUserClick: (e, { copyProfileSelect }) => { 810 e.preventDefault(); 811 SelectableProfileService.getProfile(copyProfileSelect.value).then( 812 profile => { 813 profile?.copyProfile(); 814 copyProfileSelect.config.set(""); 815 } 816 ); 817 }, 818 }); 819 Preferences.addSetting({ 820 id: "copyProfileBox", 821 visible: () => SelectableProfileService.initialized, 822 }); 823 Preferences.addSetting({ 824 id: "copyProfileError", 825 _hasError: false, 826 setup(emitChange) { 827 this.emitChange = emitChange; 828 }, 829 visible() { 830 return this._hasError; 831 }, 832 setError(value) { 833 this._hasError = !!value; 834 this.emitChange(); 835 }, 836 }); 837 Preferences.addSetting( 838 class ProfileList extends Preferences.AsyncSetting { 839 static id = "profileList"; 840 static PROFILE_UPDATED_OBS = "sps-profiles-updated"; 841 setup() { 842 Services.obs.addObserver( 843 this.emitChange, 844 ProfileList.PROFILE_UPDATED_OBS 845 ); 846 return () => { 847 Services.obs.removeObserver( 848 this.emitChange, 849 ProfileList.PROFILE_UPDATED_OBS 850 ); 851 }; 852 } 853 854 async get() { 855 let profiles = await SelectableProfileService.getAllProfiles(); 856 return profiles; 857 } 858 } 859 ); 860 Preferences.addSetting({ 861 id: "copyProfileSelect", 862 deps: ["profileList"], 863 _selectedProfile: null, 864 setup(emitChange) { 865 this.emitChange = emitChange; 866 document.l10n 867 .formatValue("preferences-copy-profile-select") 868 .then(result => (this.placeholderString = result)); 869 }, 870 get() { 871 return this._selectedProfile; 872 }, 873 set(inputVal) { 874 this._selectedProfile = inputVal; 875 this.emitChange(); 876 }, 877 getControlConfig(config, { profileList }) { 878 config.options = profileList.value.map(profile => { 879 return { controlAttrs: { label: profile.name }, value: profile.id }; 880 }); 881 882 // Put the placeholder at the front of the list. 883 config.options.unshift({ 884 controlAttrs: { label: this.placeholderString }, 885 value: "", 886 }); 887 888 return config; 889 }, 890 }); 891 Preferences.addSetting({ 892 id: "copyProfileHeader", 893 visible: () => SelectableProfileService.initialized, 894 }); 895 896 // Downloads 897 /* 898 * Preferences: 899 * 900 * browser.download.useDownloadDir - bool 901 * True - Save files directly to the folder configured via the 902 * browser.download.folderList preference. 903 * False - Always ask the user where to save a file and default to 904 * browser.download.lastDir when displaying a folder picker dialog. 905 * browser.download.deletePrivate - bool 906 * True - Delete files that were downloaded in a private browsing session 907 * on close of the session 908 * False - Keep files that were downloaded in a private browsing 909 * session 910 * browser.download.always_ask_before_handling_new_types - bool 911 * Defines the default behavior for new file handlers. 912 * True - When downloading a file that doesn't match any existing 913 * handlers, ask the user whether to save or open the file. 914 * False - Save the file. The user can change the default action in 915 * the Applications section in the preferences UI. 916 * browser.download.dir - local file handle 917 * A local folder the user may have selected for downloaded files to be 918 * saved. Migration of other browser settings may also set this path. 919 * This folder is enabled when folderList equals 2. 920 * browser.download.lastDir - local file handle 921 * May contain the last folder path accessed when the user browsed 922 * via the file save-as dialog. (see contentAreaUtils.js) 923 * browser.download.folderList - int 924 * Indicates the location users wish to save downloaded files too. 925 * It is also used to display special file labels when the default 926 * download location is either the Desktop or the Downloads folder. 927 * Values: 928 * 0 - The desktop is the default download location. 929 * 1 - The system's downloads folder is the default download location. 930 * 2 - The default download location is elsewhere as specified in 931 * browser.download.dir. 932 * browser.download.downloadDir 933 * deprecated. 934 * browser.download.defaultFolder 935 * deprecated. 936 */ 937 938 /** 939 * Helper object for managing the various downloads related settings. 940 */ 941 const DownloadsHelpers = new (class DownloadsHelpers { 942 folder; 943 folderPath; 944 folderHostPath; 945 displayName; 946 downloadsDir; 947 desktopDir; 948 downloadsFolderLocalizedName; 949 desktopFolderLocalizedName; 950 951 setupDownloadsHelpersFields = async () => { 952 this.downloadsDir = await this._getDownloadsFolder("Downloads"); 953 this.desktopDir = await this._getDownloadsFolder("Desktop"); 954 [this.downloadsFolderLocalizedName, this.desktopFolderLocalizedName] = 955 await document.l10n.formatValues([ 956 { id: "downloads-folder-name" }, 957 { id: "desktop-folder-name" }, 958 ]); 959 }; 960 961 /** 962 * Returns the Downloads folder. If aFolder is "Desktop", then the Downloads 963 * folder returned is the desktop folder; otherwise, it is a folder whose name 964 * indicates that it is a download folder and whose path is as determined by 965 * the XPCOM directory service via the download manager's attribute 966 * defaultDownloadsDirectory. 967 * 968 * @throws if aFolder is not "Desktop" or "Downloads" 969 */ 970 async _getDownloadsFolder(aFolder) { 971 switch (aFolder) { 972 case "Desktop": 973 return Services.dirsvc.get("Desk", Ci.nsIFile); 974 case "Downloads": { 975 let downloadsDir = await Downloads.getSystemDownloadsDirectory(); 976 return new FileUtils.File(downloadsDir); 977 } 978 } 979 throw new Error( 980 "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'" 981 ); 982 } 983 984 _getSystemDownloadFolderDetails(folderIndex) { 985 let currentDirPref = Preferences.get("browser.download.dir"); 986 987 let file; 988 let firefoxLocalizedName; 989 if (folderIndex == 2 && currentDirPref.value) { 990 file = currentDirPref.value; 991 if (file.equals(this.downloadsDir)) { 992 folderIndex = 1; 993 } else if (file.equals(this.desktopDir)) { 994 folderIndex = 0; 995 } 996 } 997 switch (folderIndex) { 998 case 2: // custom path, handled above. 999 break; 1000 1001 case 1: { 1002 // downloads 1003 file = this.downloadsDir; 1004 firefoxLocalizedName = this.downloadsFolderLocalizedName; 1005 break; 1006 } 1007 1008 case 0: 1009 // fall through 1010 default: { 1011 file = this.desktopDir; 1012 firefoxLocalizedName = this.desktopFolderLocalizedName; 1013 } 1014 } 1015 1016 if (file) { 1017 let displayName = file.path; 1018 1019 // Attempt to translate path to the path as exists on the host 1020 // in case the provided path comes from the document portal 1021 if (AppConstants.platform == "linux") { 1022 if (this.folderHostPath && displayName == this.folderPath) { 1023 displayName = this.folderHostPath; 1024 if (displayName == this.downloadsDir.path) { 1025 firefoxLocalizedName = this.downloadsFolderLocalizedName; 1026 } else if (displayName == this.desktopDir.path) { 1027 firefoxLocalizedName = this.desktopFolderLocalizedName; 1028 } 1029 } else if (displayName != this.folderPath) { 1030 this.folderHostPath = null; 1031 try { 1032 file.hostPath().then(folderHostPath => { 1033 this.folderHostPath = folderHostPath; 1034 Preferences.getSetting("downloadFolder")?.onChange(); 1035 }); 1036 } catch (error) { 1037 /* ignored */ 1038 } 1039 } 1040 } 1041 1042 if (firefoxLocalizedName) { 1043 let folderDisplayName, leafName; 1044 // Either/both of these can throw, so check for failures in both cases 1045 // so we don't just break display of the download pref: 1046 try { 1047 folderDisplayName = file.displayName; 1048 } catch (ex) { 1049 /* ignored */ 1050 } 1051 try { 1052 leafName = file.leafName; 1053 } catch (ex) { 1054 /* ignored */ 1055 } 1056 1057 // If we found a localized name that's different from the leaf name, 1058 // use that: 1059 if (folderDisplayName && folderDisplayName != leafName) { 1060 return { file, folderDisplayName }; 1061 } 1062 1063 // Otherwise, check if we've got a localized name ourselves. 1064 // You can't move the system download or desktop dir on macOS, 1065 // so if those are in use just display them. On other platforms 1066 // only do so if the folder matches the localized name. 1067 if ( 1068 AppConstants.platform == "macosx" || 1069 leafName == firefoxLocalizedName 1070 ) { 1071 return { file, folderDisplayName: firefoxLocalizedName }; 1072 } 1073 } 1074 1075 // If we get here, attempts to use a "pretty" name failed. Just display 1076 // the full path: 1077 // Force the left-to-right direction when displaying a custom path. 1078 return { file, folderDisplayName: `\u2066${displayName}\u2069` }; 1079 } 1080 1081 // Don't even have a file - fall back to desktop directory for the 1082 // use of the icon, and an empty label: 1083 file = this.desktopDir; 1084 return { file, folderDisplayName: "" }; 1085 } 1086 1087 /** 1088 * Determines the type of the given folder. 1089 * 1090 * @param aFolder 1091 * the folder whose type is to be determined 1092 * @returns integer 1093 * 0 if aFolder is the Desktop or is unspecified, 1094 * 1 if aFolder is the Downloads folder, 1095 * 2 otherwise 1096 */ 1097 _folderToIndex(aFolder) { 1098 if (!aFolder || aFolder.equals(this.desktopDir)) { 1099 return 0; 1100 } else if (aFolder.equals(this.downloadsDir)) { 1101 return 1; 1102 } 1103 return 2; 1104 } 1105 1106 getFolderDetails() { 1107 let folderIndex = Preferences.get("browser.download.folderList").value; 1108 let { folderDisplayName, file } = 1109 this._getSystemDownloadFolderDetails(folderIndex); 1110 1111 this.folderPath = file?.path ?? ""; 1112 this.displayName = folderDisplayName; 1113 } 1114 1115 setFolder(folder) { 1116 this.folder = folder; 1117 1118 let folderListPref = Preferences.get("browser.download.folderList"); 1119 folderListPref.value = this._folderToIndex(this.folder); 1120 } 1121 })(); 1122 1123 Preferences.addSetting({ 1124 id: "browserDownloadFolderList", 1125 pref: "browser.download.folderList", 1126 }); 1127 Preferences.addSetting({ 1128 id: "downloadFolder", 1129 pref: "browser.download.dir", 1130 deps: ["browserDownloadFolderList"], 1131 get() { 1132 DownloadsHelpers.getFolderDetails(); 1133 return DownloadsHelpers.folderPath; 1134 }, 1135 set(folder) { 1136 DownloadsHelpers.setFolder(folder); 1137 return DownloadsHelpers.folder; 1138 }, 1139 getControlConfig(config) { 1140 if (DownloadsHelpers.displayName) { 1141 return { 1142 ...config, 1143 controlAttrs: { 1144 ...config.controlAttrs, 1145 ".displayValue": DownloadsHelpers.displayName, 1146 }, 1147 }; 1148 } 1149 return { 1150 ...config, 1151 }; 1152 }, 1153 setup(emitChange) { 1154 DownloadsHelpers.setupDownloadsHelpersFields().then(emitChange); 1155 }, 1156 disabled: ({ browserDownloadFolderList }) => { 1157 return browserDownloadFolderList.locked; 1158 }, 1159 }); 1160 Preferences.addSetting({ 1161 id: "alwaysAsk", 1162 pref: "browser.download.useDownloadDir", 1163 }); 1164 Preferences.addSetting({ 1165 id: "enableDeletePrivate", 1166 pref: "browser.download.enableDeletePrivate", 1167 }); 1168 Preferences.addSetting({ 1169 id: "deletePrivate", 1170 pref: "browser.download.deletePrivate", 1171 deps: ["enableDeletePrivate"], 1172 visible: ({ enableDeletePrivate }) => enableDeletePrivate.value, 1173 onUserChange() { 1174 Services.prefs.setBoolPref("browser.download.deletePrivate.chosen", true); 1175 }, 1176 }); 1177 /** 1178 * A helper object containing all logic related to 1179 * setting the browser as the user's default. 1180 */ 1181 const DefaultBrowserHelper = { 1182 /** 1183 * @type {number} 1184 */ 1185 _backoffIndex: 0, 1186 1187 /** 1188 * @type {number | undefined} 1189 */ 1190 _pollingTimer: undefined, 1191 1192 /** 1193 * Keeps track of the last known browser 1194 * default value set to compare while polling. 1195 * 1196 * @type {boolean | undefined} 1197 */ 1198 _lastPolledIsDefault: undefined, 1199 1200 /** 1201 * @type {typeof import('../shell/ShellService.sys.mjs').ShellService | undefined} 1202 */ 1203 get shellSvc() { 1204 return ( 1205 AppConstants.HAVE_SHELL_SERVICE && 1206 // @ts-ignore from utilityOverlay.js 1207 getShellService() 1208 ); 1209 }, 1210 1211 /** 1212 * Sets up polling of whether the browser is set to default, 1213 * and calls provided hasChanged function when the state changes. 1214 * 1215 * @param {Function} hasChanged 1216 */ 1217 pollForDefaultChanges(hasChanged) { 1218 if (this._pollingTimer) { 1219 return; 1220 } 1221 this._lastPolledIsDefault = this.isBrowserDefault; 1222 1223 // Exponential backoff mechanism will delay the polling times if user doesn't 1224 // trigger SetDefaultBrowser for a long time. 1225 const backoffTimes = [ 1226 1000, 1000, 1000, 1000, 2000, 2000, 2000, 5000, 5000, 10000, 1227 ]; 1228 1229 const pollForDefaultBrowser = () => { 1230 if ( 1231 (location.hash == "" || location.hash == "#general") && 1232 document.visibilityState == "visible" 1233 ) { 1234 const { isBrowserDefault } = this; 1235 if (isBrowserDefault !== this._lastPolledIsDefault) { 1236 this._lastPolledIsDefault = isBrowserDefault; 1237 hasChanged(); 1238 } 1239 } 1240 1241 if (!this._pollingTimer) { 1242 return; 1243 } 1244 1245 // approximately a "requestIdleInterval" 1246 this._pollingTimer = window.setTimeout( 1247 () => { 1248 window.requestIdleCallback(pollForDefaultBrowser); 1249 }, 1250 backoffTimes[ 1251 this._backoffIndex + 1 < backoffTimes.length 1252 ? this._backoffIndex++ 1253 : backoffTimes.length - 1 1254 ] 1255 ); 1256 }; 1257 1258 this._pollingTimer = window.setTimeout(() => { 1259 window.requestIdleCallback(pollForDefaultBrowser); 1260 }, backoffTimes[this._backoffIndex]); 1261 }, 1262 1263 /** 1264 * Stops timer for polling changes. 1265 */ 1266 clearPollingForDefaultChanges() { 1267 if (this._pollingTimer) { 1268 clearTimeout(this._pollingTimer); 1269 this._pollingTimer = undefined; 1270 } 1271 }, 1272 1273 /** 1274 * Checks if the browser is default through the shell service. 1275 */ 1276 get isBrowserDefault() { 1277 if (!this.canCheck) { 1278 return false; 1279 } 1280 return this.shellSvc?.isDefaultBrowser(false, true); 1281 }, 1282 1283 /** 1284 * Attempts to set the browser as the user's 1285 * default through the shell service. 1286 * 1287 * @returns {Promise<void>} 1288 */ 1289 async setDefaultBrowser() { 1290 // Reset exponential backoff delay time in order to do visual update in pollForDefaultBrowser. 1291 this._backoffIndex = 0; 1292 1293 try { 1294 await this.shellSvc?.setDefaultBrowser(false); 1295 } catch (e) { 1296 console.error(e); 1297 } 1298 }, 1299 1300 /** 1301 * Checks whether the browser is capable of being made default. 1302 * 1303 * @type {boolean} 1304 */ 1305 get canCheck() { 1306 if (AppConstants.BASE_BROWSER_VERSION) { 1307 // Disabled for Tor Browser. tor-browser#44343 and tor-browser#41822. 1308 return false; 1309 } 1310 return ( 1311 this.shellSvc && 1312 /** 1313 * Flatpak does not support setting nor detection of default browser 1314 */ 1315 !gGIOService?.isRunningUnderFlatpak 1316 ); 1317 }, 1318 }; 1319 1320 Preferences.addSetting({ 1321 id: "alwaysCheckDefault", 1322 pref: "browser.shell.checkDefaultBrowser", 1323 setup: emitChange => { 1324 if (!DefaultBrowserHelper.canCheck) { 1325 return; 1326 } 1327 DefaultBrowserHelper.pollForDefaultChanges(emitChange); 1328 // eslint-disable-next-line consistent-return 1329 return () => DefaultBrowserHelper.clearPollingForDefaultChanges(); 1330 }, 1331 /** 1332 * Show button for setting browser as default browser or information that 1333 * browser is already the default browser. 1334 */ 1335 visible: () => DefaultBrowserHelper.canCheck, 1336 disabled: (_, setting) => 1337 !DefaultBrowserHelper.canCheck || 1338 setting.locked || 1339 DefaultBrowserHelper.isBrowserDefault, 1340 }); 1341 1342 Preferences.addSetting({ 1343 id: "isDefaultPane", 1344 deps: ["alwaysCheckDefault"], 1345 visible: () => 1346 DefaultBrowserHelper.canCheck && DefaultBrowserHelper.isBrowserDefault, 1347 }); 1348 1349 Preferences.addSetting({ 1350 id: "isNotDefaultPane", 1351 deps: ["alwaysCheckDefault"], 1352 visible: () => 1353 DefaultBrowserHelper.canCheck && !DefaultBrowserHelper.isBrowserDefault, 1354 onUserClick: (e, { alwaysCheckDefault }) => { 1355 if (!DefaultBrowserHelper.canCheck) { 1356 return; 1357 } 1358 const setDefaultButton = /** @type {MozButton} */ (e.target); 1359 1360 if (!setDefaultButton) { 1361 return; 1362 } 1363 if (setDefaultButton.disabled) { 1364 return; 1365 } 1366 1367 /** 1368 * Disable the set default button, so that the user 1369 * doesn't try to hit it again while browser is being set to default. 1370 */ 1371 setDefaultButton.disabled = true; 1372 alwaysCheckDefault.value = true; 1373 DefaultBrowserHelper.setDefaultBrowser().finally(() => { 1374 setDefaultButton.disabled = false; 1375 }); 1376 }, 1377 }); 1378 1379 // Firefox support settings 1380 Preferences.addSetting({ 1381 id: "supportLinksGroup", 1382 }); 1383 Preferences.addSetting({ 1384 id: "supportGetHelp", 1385 }); 1386 Preferences.addSetting({ 1387 id: "supportShareIdeas", 1388 }); 1389 1390 // Performance settings 1391 Preferences.addSetting({ 1392 id: "contentProcessCount", 1393 pref: "dom.ipc.processCount", 1394 }); 1395 Preferences.addSetting({ 1396 id: "allowHWAccel", 1397 pref: "layers.acceleration.disabled", 1398 deps: ["useRecommendedPerformanceSettings"], 1399 visible({ useRecommendedPerformanceSettings }) { 1400 return !useRecommendedPerformanceSettings.value; 1401 }, 1402 }); 1403 Preferences.addSetting({ 1404 id: "useRecommendedPerformanceSettings", 1405 pref: "browser.preferences.defaultPerformanceSettings.enabled", 1406 deps: ["contentProcessCount", "allowHWAccel"], 1407 get(val, { allowHWAccel, contentProcessCount }) { 1408 if ( 1409 allowHWAccel.value != allowHWAccel.pref.defaultValue || 1410 contentProcessCount.value != contentProcessCount.pref.defaultValue 1411 ) { 1412 return false; 1413 } 1414 return val; 1415 }, 1416 set(val, { allowHWAccel, contentProcessCount }) { 1417 if (val) { 1418 contentProcessCount.value = contentProcessCount.pref.defaultValue; 1419 allowHWAccel.value = allowHWAccel.pref.defaultValue; 1420 } 1421 return val; 1422 }, 1423 }); 1424 1425 Preferences.addSetting({ 1426 id: "payment-item", 1427 async onUserClick(e) { 1428 const action = e.target.getAttribute("action"); 1429 const guid = e.target.getAttribute("guid"); 1430 if (action === "remove") { 1431 let [title, confirm, cancel] = await document.l10n.formatValues([ 1432 { id: "payments-delete-payment-prompt-title" }, 1433 { id: "payments-delete-payment-prompt-confirm-button" }, 1434 { id: "payments-delete-payment-prompt-cancel-button" }, 1435 ]); 1436 FormAutofillPreferences.prototype.openRemovePaymentDialog( 1437 guid, 1438 window.browsingContext.topChromeWindow.browsingContext, 1439 title, 1440 confirm, 1441 cancel 1442 ); 1443 } else if (action === "edit") { 1444 FormAutofillPreferences.prototype.openEditCreditCardDialog(guid, window); 1445 } 1446 }, 1447 }); 1448 1449 Preferences.addSetting({ 1450 id: "add-payment-button", 1451 deps: ["saveAndFillPayments"], 1452 setup: (emitChange, _, setting) => { 1453 function updateDepsAndChange() { 1454 setting._deps = null; 1455 emitChange(); 1456 } 1457 Services.obs.addObserver( 1458 updateDepsAndChange, 1459 "formautofill-preferences-initialized" 1460 ); 1461 return () => 1462 Services.obs.removeObserver( 1463 updateDepsAndChange, 1464 "formautofill-preferences-initialized" 1465 ); 1466 }, 1467 onUserClick: ({ target }) => { 1468 target.ownerGlobal.gSubDialog.open( 1469 "chrome://formautofill/content/editCreditCard.xhtml" 1470 ); 1471 }, 1472 disabled: ({ saveAndFillPayments }) => !saveAndFillPayments?.value, 1473 }); 1474 1475 Preferences.addSetting({ 1476 id: "payments-list-header", 1477 }); 1478 1479 Preferences.addSetting({ 1480 id: "no-payments-stored", 1481 }); 1482 1483 Preferences.addSetting( 1484 class extends Preferences.AsyncSetting { 1485 static id = "payments-list"; 1486 1487 /** @type {Promise<any[]>} */ 1488 paymentMethods; 1489 1490 beforeRefresh() { 1491 this.paymentMethods = this.getPaymentMethods(); 1492 } 1493 1494 async getPaymentMethods() { 1495 await FormAutofillPreferences.prototype.initializePaymentsStorage(); 1496 return FormAutofillPreferences.prototype.makePaymentsListItems(); 1497 } 1498 1499 async getControlConfig() { 1500 return { 1501 items: await this.paymentMethods, 1502 }; 1503 } 1504 1505 async visible() { 1506 return Boolean((await this.paymentMethods).length); 1507 } 1508 1509 setup() { 1510 Services.obs.addObserver(this.emitChange, "formautofill-storage-changed"); 1511 return () => 1512 Services.obs.removeObserver( 1513 this.emitChange, 1514 "formautofill-storage-changed" 1515 ); 1516 } 1517 } 1518 ); 1519 1520 // Tabs settings 1521 1522 // "Opening" tabs settings 1523 Preferences.addSetting({ 1524 id: "tabsOpening", 1525 }); 1526 /** 1527 * browser.link.open_newwindow - int 1528 * Determines where links targeting new windows should open. 1529 * Values: 1530 * 1 - Open in the current window or tab. 1531 * 2 - Open in a new window. 1532 * 3 - Open in a new tab in the most recent window. 1533 */ 1534 Preferences.addSetting({ 1535 id: "linkTargeting", 1536 pref: "browser.link.open_newwindow", 1537 /** 1538 * Determines where a link which opens a new window will open. 1539 * 1540 * @returns |true| if such links should be opened in new tabs 1541 */ 1542 get: prefVal => { 1543 return prefVal != 2; 1544 }, 1545 /** 1546 * Determines where a link which opens a new window will open. 1547 * 1548 * @returns 2 if such links should be opened in new windows, 1549 * 3 if such links should be opened in new tabs 1550 */ 1551 set: checked => { 1552 return checked ? 3 : 2; 1553 }, 1554 }); 1555 /** 1556 * browser.tabs.loadInBackground - bool 1557 * True - Whether browser should switch to a new tab opened from a link. 1558 */ 1559 Preferences.addSetting({ 1560 id: "switchToNewTabs", 1561 pref: "browser.tabs.loadInBackground", 1562 }); 1563 Preferences.addSetting({ 1564 id: "openAppLinksNextToActiveTab", 1565 pref: "browser.link.open_newwindow.override.external", 1566 /** 1567 * @returns {boolean} 1568 * Whether the "Open links in tabs instead of new windows" settings 1569 * checkbox should be checked. Should only be checked if the 1570 * `browser.link.open_newwindow.override.external` pref is set to the 1571 * value of 7 (nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT). 1572 */ 1573 get: prefVal => { 1574 return prefVal == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT; 1575 }, 1576 /** 1577 * This pref has at least 8 valid values but we are offering a checkbox 1578 * to set one specific value (`7`). 1579 * 1580 * @param {boolean} checked 1581 * @returns {number} 1582 * - `7` (`nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT`) if checked 1583 * - the default value of 1584 * `browser.link.open_newwindow.override.external` if not checked 1585 */ 1586 set: (checked, _, setting) => { 1587 return checked 1588 ? Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT 1589 : setting.pref.defaultValue; 1590 }, 1591 onUserChange: checked => { 1592 Glean.linkHandling.openNextToActiveTabSettingsEnabled.set(checked); 1593 Glean.linkHandling.openNextToActiveTabSettingsChange.record({ 1594 checked, 1595 }); 1596 }, 1597 }); 1598 /** 1599 * browser.tabs.warnOnOpen - bool 1600 * True - Whether the user should be warned when trying to open a lot of 1601 * tabs at once (e.g. a large folder of bookmarks), allowing to 1602 * cancel the action. 1603 */ 1604 Preferences.addSetting({ 1605 id: "warnOpenMany", 1606 pref: "browser.tabs.warnOnOpen", 1607 // The "opening multiple tabs might slow down Firefox" warning provides 1608 // an option for not showing this warning again. When the user disables it, 1609 // we provide checkboxes to re-enable the warning. 1610 visible: () => TransientPrefs.prefShouldBeVisible("browser.tabs.warnOnOpen"), 1611 }); 1612 1613 // "Interaction" tabs settings 1614 Preferences.addSetting({ 1615 id: "tabsInteraction", 1616 }); 1617 Preferences.addSetting({ 1618 id: "ctrlTabRecentlyUsedOrder", 1619 pref: "browser.ctrlTab.sortByRecentlyUsed", 1620 onUserClick: () => { 1621 Services.prefs.clearUserPref("browser.ctrlTab.migrated"); 1622 }, 1623 }); 1624 Preferences.addSetting({ 1625 id: "tabHoverPreview", 1626 pref: "browser.tabs.hoverPreview.enabled", 1627 }); 1628 Preferences.addSetting({ 1629 id: "tabPreviewShowThumbnails", 1630 pref: "browser.tabs.hoverPreview.showThumbnails", 1631 deps: ["tabHoverPreview"], 1632 visible: ({ tabHoverPreview }) => !!tabHoverPreview.value, 1633 }); 1634 Preferences.addSetting({ 1635 id: "smartTabGroups", 1636 pref: "browser.tabs.groups.smart.enabled", 1637 }); 1638 Preferences.addSetting({ 1639 id: "tabGroupSuggestions", 1640 pref: "browser.tabs.groups.smart.userEnabled", 1641 deps: ["smartTabGroups"], 1642 visible: ({ smartTabGroups }) => 1643 !!smartTabGroups.value && Services.locale.appLocaleAsBCP47.startsWith("en"), 1644 }); 1645 if (AppConstants.platform === "win") { 1646 /** 1647 * browser.taskbar.previews.enable - bool 1648 * True - Tabs are to be shown in Windows 7 taskbar. 1649 * False - Only the window is to be shown in Windows 7 taskbar. 1650 */ 1651 Preferences.addSetting({ 1652 id: "showTabsInTaskbar", 1653 pref: "browser.taskbar.previews.enable", 1654 // Functionality for "Show tabs in taskbar" on Windows 7 and up. 1655 visible: () => { 1656 if (AppConstants.platform !== "win") { 1657 return false; 1658 } 1659 1660 try { 1661 let ver = parseFloat(Services.sysinfo.getProperty("version")); 1662 return ver >= 6.1; 1663 } catch (ex) { 1664 return false; 1665 } 1666 }, 1667 }); 1668 } else { 1669 // Not supported unless we're on Windows 1670 Preferences.addSetting({ id: "showTabsInTaskbar", visible: () => false }); 1671 } 1672 1673 // "Containers" tabs settings 1674 Preferences.addSetting({ 1675 id: "privacyUserContextUI", 1676 pref: "privacy.userContext.ui.enabled", 1677 }); 1678 Preferences.addSetting({ 1679 id: "browserContainersbox", 1680 deps: ["privacyUserContextUI"], 1681 visible: ({ privacyUserContextUI }) => !!privacyUserContextUI.value, 1682 }); 1683 Preferences.addSetting({ 1684 id: "browserContainersCheckbox", 1685 pref: "privacy.userContext.enabled", 1686 controllingExtensionInfo: { 1687 storeId: "privacy.containers", 1688 l10nId: "extension-controlling-privacy-containers", 1689 }, 1690 async promptToCloseTabsAndDisable(count, setting) { 1691 let [title, message, okButton, cancelButton] = 1692 await document.l10n.formatValues([ 1693 { id: "containers-disable-alert-title" }, 1694 { id: "containers-disable-alert-desc", args: { tabCount: count } }, 1695 { id: "containers-disable-alert-ok-button", args: { tabCount: count } }, 1696 { id: "containers-disable-alert-cancel-button" }, 1697 ]); 1698 1699 let buttonFlags = 1700 Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0 + 1701 Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1; 1702 1703 let rv = Services.prompt.confirmEx( 1704 window, 1705 title, 1706 message, 1707 buttonFlags, 1708 okButton, 1709 cancelButton, 1710 null, 1711 null, 1712 {} 1713 ); 1714 1715 // User confirmed - disable containers and close container tabs. 1716 if (rv == 0) { 1717 await ContextualIdentityService.closeContainerTabs(); 1718 setting.pref.value = false; 1719 } 1720 1721 // Keep the checkbox checked when the user opts not to close tabs. 1722 return true; 1723 }, 1724 set(val, _, setting) { 1725 // When enabling container tabs, just set the pref value. 1726 if (val) { 1727 return val; 1728 } 1729 1730 // When disabling container tabs, check if there are container tabs currently 1731 // open. If there aren't, then proceed with disabling. 1732 let count = ContextualIdentityService.countContainerTabs(); 1733 if (count == 0) { 1734 return false; 1735 } 1736 1737 // When disabling container tabs with container tabs currently open show a 1738 // dialog to determine whether or not the tabs should be closed. 1739 return this.promptToCloseTabsAndDisable(count, setting); 1740 }, 1741 }); 1742 Preferences.addSetting({ 1743 id: "browserContainersSettings", 1744 deps: ["browserContainersCheckbox"], 1745 /** 1746 * Displays container panel for customising and adding containers. 1747 */ 1748 onUserClick: () => { 1749 gotoPref("containers"); 1750 }, 1751 getControlConfig: config => { 1752 let searchKeywords = [ 1753 "user-context-personal", 1754 "user-context-work", 1755 "user-context-banking", 1756 "user-context-shopping", 1757 ] 1758 .map(ContextualIdentityService.formatContextLabel) 1759 .join(" "); 1760 config.controlAttrs.searchkeywords = searchKeywords; 1761 return config; 1762 }, 1763 disabled: ({ browserContainersCheckbox }) => !browserContainersCheckbox.value, 1764 }); 1765 1766 // "Closing" tabs settings 1767 Preferences.addSetting({ 1768 id: "tabsClosing", 1769 }); 1770 /** 1771 * browser.tabs.warnOnClose - bool 1772 * True - If when closing a window with multiple tabs the user is warned and 1773 * allowed to cancel the action, false to just close the window. 1774 */ 1775 Preferences.addSetting({ 1776 id: "warnCloseMultiple", 1777 pref: "browser.tabs.warnOnClose", 1778 }); 1779 /** 1780 * browser.warnOnQuitShortcut - bool 1781 * True - If the keyboard shortcut (Ctrl/Cmd+Q) is pressed, the user should 1782 * be warned, false to just quit without prompting. 1783 */ 1784 Preferences.addSetting({ 1785 id: "warnOnQuitKey", 1786 pref: "browser.warnOnQuitShortcut", 1787 setup() { 1788 let quitKeyElement = 1789 window.browsingContext.topChromeWindow.document.getElementById( 1790 "key_quitApplication" 1791 ); 1792 if (quitKeyElement) { 1793 this.quitKey = ShortcutUtils.prettifyShortcut(quitKeyElement); 1794 } 1795 }, 1796 visible() { 1797 return AppConstants.platform !== "win" && this.quitKey; 1798 }, 1799 getControlConfig(config) { 1800 return { 1801 ...config, 1802 l10nArgs: { quitKey: this.quitKey }, 1803 }; 1804 }, 1805 }); 1806 1807 /** 1808 * Helper object for managing the various zoom related settings. 1809 */ 1810 const ZoomHelpers = { 1811 win: window.browsingContext.topChromeWindow, 1812 get FullZoom() { 1813 return this.win.FullZoom; 1814 }, 1815 get ZoomManager() { 1816 return this.win.ZoomManager; 1817 }, 1818 1819 /** 1820 * Set the global default zoom value. 1821 * 1822 * @param {number} newZoom - The new zoom 1823 * @returns {Promise<void>} 1824 */ 1825 async setDefaultZoom(newZoom) { 1826 let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService( 1827 Ci.nsIContentPrefService2 1828 ); 1829 let nonPrivateLoadContext = Cu.createLoadContext(); 1830 let resolvers = Promise.withResolvers(); 1831 /* Because our setGlobal function takes in a browsing context, and 1832 * because we want to keep this property consistent across both private 1833 * and non-private contexts, we create a non-private context and use that 1834 * to set the property, regardless of our actual context. 1835 */ 1836 cps2.setGlobal(this.FullZoom.name, newZoom, nonPrivateLoadContext, { 1837 handleCompletion: resolvers.resolve, 1838 handleError: resolvers.reject, 1839 }); 1840 return resolvers.promise; 1841 }, 1842 1843 async getDefaultZoom() { 1844 /** @import { ZoomUI as GlobalZoomUI } from "resource:///modules/ZoomUI.sys.mjs" */ 1845 /** @type {GlobalZoomUI} */ 1846 let ZoomUI = this.win.ZoomUI; 1847 return await ZoomUI.getGlobalValue(); 1848 }, 1849 1850 /** 1851 * The possible zoom values. 1852 * 1853 * @returns {number[]} 1854 */ 1855 get zoomValues() { 1856 return this.ZoomManager.zoomValues; 1857 }, 1858 1859 toggleFullZoom() { 1860 this.ZoomManager.toggleZoom(); 1861 }, 1862 }; 1863 Preferences.addSetting( 1864 class extends Preferences.AsyncSetting { 1865 static id = "defaultZoom"; 1866 /** @type {Record<"options", object[]>} */ 1867 optionsConfig; 1868 1869 /** 1870 * @param {string} val - zoom value as a string 1871 */ 1872 async set(val) { 1873 ZoomHelpers.setDefaultZoom( 1874 parseFloat((parseInt(val, 10) / 100).toFixed(2)) 1875 ); 1876 } 1877 async get() { 1878 return Math.round((await ZoomHelpers.getDefaultZoom()) * 100); 1879 } 1880 async getControlConfig() { 1881 if (!this.optionsConfig) { 1882 this.optionsConfig = { 1883 options: ZoomHelpers.zoomValues.map(a => { 1884 let value = Math.round(a * 100); 1885 return { 1886 value, 1887 l10nId: "preferences-default-zoom-value", 1888 l10nArgs: { percentage: value }, 1889 }; 1890 }), 1891 }; 1892 } 1893 return this.optionsConfig; 1894 } 1895 } 1896 ); 1897 Preferences.addSetting({ 1898 id: "zoomTextPref", 1899 pref: "browser.zoom.full", 1900 }); 1901 Preferences.addSetting({ 1902 id: "zoomText", 1903 deps: ["zoomTextPref"], 1904 // Use the Setting since the ZoomManager getter may not have updated yet. 1905 get: (_, { zoomTextPref }) => !zoomTextPref.value, 1906 set: () => ZoomHelpers.toggleFullZoom(), 1907 disabled: ({ zoomTextPref }) => zoomTextPref.locked, 1908 }); 1909 Preferences.addSetting({ 1910 id: "zoomWarning", 1911 deps: ["zoomText"], 1912 visible: ({ zoomText }) => Boolean(zoomText.value), 1913 }); 1914 Preferences.addSetting({ 1915 id: "contrastControlSettings", 1916 pref: "browser.display.document_color_use", 1917 }); 1918 Preferences.addSetting({ 1919 id: "colors", 1920 onUserClick() { 1921 gSubDialog.open( 1922 "chrome://browser/content/preferences/dialogs/colors.xhtml", 1923 { features: "resizable=no" } 1924 ); 1925 }, 1926 }); 1927 1928 Preferences.addSetting({ 1929 /** @type {{ _removeAddressDialogStrings: string[] } & SettingConfig} */ 1930 id: "address-item", 1931 _removeAddressDialogStrings: [], 1932 onUserClick(e) { 1933 const action = e.target.getAttribute("action"); 1934 const guid = e.target.getAttribute("guid"); 1935 if (action === "remove") { 1936 let [title, confirm, cancel] = this._removeAddressDialogStrings; 1937 FormAutofillPreferences.prototype.openRemoveAddressDialog( 1938 guid, 1939 window.browsingContext.topChromeWindow.browsingContext, 1940 title, 1941 confirm, 1942 cancel 1943 ); 1944 } else if (action === "edit") { 1945 FormAutofillPreferences.prototype.openEditAddressDialog(guid, window); 1946 } 1947 }, 1948 setup(emitChange) { 1949 document.l10n 1950 .formatValues([ 1951 { id: "addresses-delete-address-prompt-title" }, 1952 { id: "addresses-delete-address-prompt-confirm-button" }, 1953 { id: "addresses-delete-address-prompt-cancel-button" }, 1954 ]) 1955 .then(val => (this._removeAddressDialogStrings = val)) 1956 .then(emitChange); 1957 }, 1958 disabled() { 1959 return !!this._removeAddressDialogStrings.length; 1960 }, 1961 }); 1962 1963 Preferences.addSetting({ 1964 id: "add-address-button", 1965 deps: ["saveAndFillAddresses"], 1966 setup: (emitChange, _, setting) => { 1967 function updateDepsAndChange() { 1968 setting._deps = null; 1969 emitChange(); 1970 } 1971 Services.obs.addObserver( 1972 updateDepsAndChange, 1973 "formautofill-preferences-initialized" 1974 ); 1975 return () => 1976 Services.obs.removeObserver( 1977 updateDepsAndChange, 1978 "formautofill-preferences-initialized" 1979 ); 1980 }, 1981 onUserClick: () => { 1982 FormAutofillPreferences.prototype.openEditAddressDialog(undefined, window); 1983 }, 1984 disabled: ({ saveAndFillAddresses }) => !saveAndFillAddresses?.value, 1985 }); 1986 1987 Preferences.addSetting({ 1988 id: "addresses-list-header", 1989 }); 1990 1991 Preferences.addSetting({ 1992 id: "no-addresses-stored", 1993 }); 1994 1995 Preferences.addSetting( 1996 class extends Preferences.AsyncSetting { 1997 static id = "addresses-list"; 1998 1999 async getAddresses() { 2000 await FormAutofillPreferences.prototype.initializeAddressesStorage(); 2001 return FormAutofillPreferences.prototype.makeAddressesListItems(); 2002 } 2003 2004 async getControlConfig() { 2005 return { 2006 items: await this.getAddresses(), 2007 }; 2008 } 2009 2010 setup() { 2011 Services.obs.addObserver(this.emitChange, "formautofill-storage-changed"); 2012 return () => 2013 Services.obs.removeObserver( 2014 this.emitChange, 2015 "formautofill-storage-changed" 2016 ); 2017 } 2018 2019 async visible() { 2020 const items = await this.getAddresses(); 2021 return !!items.length; 2022 } 2023 } 2024 ); 2025 2026 SettingGroupManager.registerGroups({ 2027 containers: { 2028 // This section is marked as in progress for testing purposes 2029 inProgress: true, 2030 items: [ 2031 { 2032 id: "containersPlaceholder", 2033 control: "moz-message-bar", 2034 controlAttrs: { 2035 message: "Placeholder for updated containers", 2036 }, 2037 }, 2038 ], 2039 }, 2040 profilePane: { 2041 headingLevel: 2, 2042 id: "browserProfilesGroupPane", 2043 l10nId: "preferences-profiles-subpane-description", 2044 supportPage: "profile-management", 2045 items: [ 2046 { 2047 id: "manageProfiles", 2048 control: "moz-box-button", 2049 l10nId: "preferences-manage-profiles-button", 2050 }, 2051 { 2052 id: "copyProfileHeader", 2053 l10nId: "preferences-copy-profile-header", 2054 headingLevel: 2, 2055 supportPage: "profile-management", 2056 control: "moz-fieldset", 2057 items: [ 2058 { 2059 id: "copyProfileBox", 2060 l10nId: "preferences-profile-to-copy", 2061 control: "moz-box-item", 2062 items: [ 2063 { 2064 id: "copyProfileSelect", 2065 control: "moz-select", 2066 slot: "actions", 2067 }, 2068 { 2069 id: "copyProfile", 2070 l10nId: "preferences-copy-profile-button", 2071 control: "moz-button", 2072 slot: "actions", 2073 controlAttrs: { 2074 type: "primary", 2075 }, 2076 }, 2077 ], 2078 }, 2079 ], 2080 }, 2081 ], 2082 }, 2083 profiles: { 2084 id: "profilesGroup", 2085 l10nId: "preferences-profiles-section-header", 2086 headingLevel: 2, 2087 supportPage: "profile-management", 2088 items: [ 2089 { 2090 id: "profilesSettings", 2091 control: "moz-box-button", 2092 l10nId: "preferences-profiles-settings-button", 2093 }, 2094 ], 2095 }, 2096 startup: { 2097 items: [ 2098 { 2099 id: "browserRestoreSession", 2100 l10nId: "startup-restore-windows-and-tabs", 2101 }, 2102 { 2103 id: "windowsLaunchOnLogin", 2104 l10nId: "windows-launch-on-login", 2105 }, 2106 { 2107 id: "windowsLaunchOnLoginDisabledBox", 2108 control: "moz-message-bar", 2109 options: [ 2110 { 2111 control: "span", 2112 l10nId: "windows-launch-on-login-disabled", 2113 slot: "message", 2114 options: [ 2115 { 2116 control: "a", 2117 controlAttrs: { 2118 "data-l10n-name": "startup-link", 2119 href: "ms-settings:startupapps", 2120 target: "_self", 2121 }, 2122 }, 2123 ], 2124 }, 2125 ], 2126 }, 2127 { 2128 id: "windowsLaunchOnLoginDisabledProfileBox", 2129 control: "moz-message-bar", 2130 l10nId: "startup-windows-launch-on-login-profile-disabled", 2131 }, 2132 { 2133 id: "alwaysCheckDefault", 2134 l10nId: "always-check-default", 2135 }, 2136 { 2137 id: "isDefaultPane", 2138 l10nId: "is-default-browser", 2139 control: "moz-promo", 2140 }, 2141 { 2142 id: "isNotDefaultPane", 2143 l10nId: "is-not-default-browser", 2144 control: "moz-promo", 2145 options: [ 2146 { 2147 control: "moz-button", 2148 l10nId: "set-as-my-default-browser", 2149 id: "setDefaultButton", 2150 slot: "actions", 2151 controlAttrs: { 2152 type: "primary", 2153 }, 2154 }, 2155 ], 2156 }, 2157 ], 2158 }, 2159 importBrowserData: { 2160 l10nId: "preferences-data-migration-group", 2161 headingLevel: 2, 2162 items: [ 2163 { 2164 id: "data-migration", 2165 l10nId: "preferences-data-migration-button", 2166 control: "moz-box-button", 2167 }, 2168 ], 2169 }, 2170 homepage: { 2171 inProgress: true, 2172 headingLevel: 2, 2173 l10nId: "home-homepage-title", 2174 items: [ 2175 { 2176 id: "homepageNewWindows", 2177 control: "moz-select", 2178 l10nId: "home-homepage-new-windows", 2179 options: [ 2180 { 2181 value: "home", 2182 l10nId: "home-mode-choice-default-fx", 2183 }, 2184 { value: "blank", l10nId: "home-mode-choice-blank" }, 2185 { value: "custom", l10nId: "home-mode-choice-custom" }, 2186 ], 2187 }, 2188 { 2189 id: "homepageGoToCustomHomepageUrlPanel", 2190 control: "moz-box-button", 2191 l10nId: "home-homepage-custom-homepage-button", 2192 }, 2193 { 2194 id: "homepageNewTabs", 2195 control: "moz-select", 2196 l10nId: "home-homepage-new-tabs", 2197 options: [ 2198 { 2199 value: "true", 2200 l10nId: "home-mode-choice-default-fx", 2201 }, 2202 { value: "false", l10nId: "home-mode-choice-blank" }, 2203 ], 2204 }, 2205 { 2206 id: "homepageRestoreDefaults", 2207 control: "moz-button", 2208 l10nId: "home-restore-defaults", 2209 controlAttrs: { id: "restoreDefaultHomePageBtn" }, 2210 }, 2211 ], 2212 }, 2213 home: { 2214 inProgress: true, 2215 headingLevel: 2, 2216 l10nId: "home-prefs-content-header", 2217 // Icons are not ready to be used yet. 2218 // iconSrc: "chrome://browser/skin/home.svg", 2219 items: [ 2220 { 2221 id: "webSearch", 2222 l10nId: "home-prefs-search-header2", 2223 control: "moz-toggle", 2224 }, 2225 { 2226 id: "weather", 2227 l10nId: "home-prefs-weather-header", 2228 control: "moz-toggle", 2229 }, 2230 { 2231 id: "widgets", 2232 l10nId: "home-prefs-widgets-header", 2233 control: "moz-toggle", 2234 items: [ 2235 { 2236 id: "lists", 2237 l10nId: "home-prefs-lists-header", 2238 }, 2239 { 2240 id: "timer", 2241 l10nId: "home-prefs-timer-header", 2242 }, 2243 ], 2244 }, 2245 { 2246 id: "shortcuts", 2247 l10nId: "home-prefs-shortcuts-header", 2248 control: "moz-toggle", 2249 items: [ 2250 { 2251 id: "shortcutsRows", 2252 control: "moz-select", 2253 options: [ 2254 { 2255 value: 1, 2256 l10nId: "home-prefs-sections-rows-option", 2257 l10nArgs: { num: 1 }, 2258 }, 2259 { 2260 value: 2, 2261 l10nId: "home-prefs-sections-rows-option", 2262 l10nArgs: { num: 2 }, 2263 }, 2264 { 2265 value: 3, 2266 l10nId: "home-prefs-sections-rows-option", 2267 l10nArgs: { num: 3 }, 2268 }, 2269 { 2270 value: 4, 2271 l10nId: "home-prefs-sections-rows-option", 2272 l10nArgs: { num: 4 }, 2273 }, 2274 ], 2275 }, 2276 ], 2277 }, 2278 { 2279 id: "stories", 2280 l10nId: "home-prefs-stories-header2", 2281 control: "moz-toggle", 2282 items: [ 2283 { 2284 id: "manageTopics", 2285 l10nId: "home-prefs-manage-topics-link2", 2286 control: "moz-box-link", 2287 controlAttrs: { 2288 href: "about:newtab#customize-topics", 2289 }, 2290 }, 2291 ], 2292 }, 2293 { 2294 id: "supportFirefox", 2295 l10nId: "home-prefs-support-firefox-header", 2296 control: "moz-toggle", 2297 items: [ 2298 { 2299 id: "sponsoredShortcuts", 2300 l10nId: "home-prefs-shortcuts-by-option-sponsored", 2301 }, 2302 { 2303 id: "sponsoredStories", 2304 l10nId: "home-prefs-recommended-by-option-sponsored-stories", 2305 }, 2306 { 2307 id: "supportFirefoxPromo", 2308 l10nId: "home-prefs-mission-message2", 2309 control: "moz-promo", 2310 options: [ 2311 { 2312 control: "a", 2313 l10nId: "home-prefs-mission-message-learn-more-link", 2314 slot: "support-link", 2315 controlAttrs: { 2316 is: "moz-support-link", 2317 "support-page": "sponsor-privacy", 2318 "utm-content": "inproduct", 2319 }, 2320 }, 2321 ], 2322 }, 2323 ], 2324 }, 2325 { 2326 id: "recentActivity", 2327 l10nId: "home-prefs-recent-activity-header", 2328 control: "moz-toggle", 2329 items: [ 2330 { 2331 id: "recentActivityRows", 2332 control: "moz-select", 2333 controlAttrs: { 2334 class: "newtab-rows-select", 2335 }, 2336 options: [ 2337 { 2338 value: 1, 2339 l10nId: "home-prefs-sections-rows-option", 2340 l10nArgs: { num: 1 }, 2341 }, 2342 { 2343 value: 2, 2344 l10nId: "home-prefs-sections-rows-option", 2345 l10nArgs: { num: 2 }, 2346 }, 2347 { 2348 value: 3, 2349 l10nId: "home-prefs-sections-rows-option", 2350 l10nArgs: { num: 3 }, 2351 }, 2352 { 2353 value: 4, 2354 l10nId: "home-prefs-sections-rows-option", 2355 l10nArgs: { num: 4 }, 2356 }, 2357 ], 2358 }, 2359 { 2360 id: "recentActivityVisited", 2361 l10nId: "home-prefs-highlights-option-visited-pages", 2362 }, 2363 { 2364 id: "recentActivityBookmarks", 2365 l10nId: "home-prefs-highlights-options-bookmarks", 2366 }, 2367 { 2368 id: "recentActivityDownloads", 2369 l10nId: "home-prefs-highlights-option-most-recent-download", 2370 }, 2371 ], 2372 }, 2373 { 2374 id: "chooseWallpaper", 2375 l10nId: "home-prefs-choose-wallpaper-link2", 2376 control: "moz-box-link", 2377 controlAttrs: { 2378 href: "about:newtab#customize", 2379 }, 2380 iconSrc: "chrome://browser/skin/customize.svg", 2381 }, 2382 ], 2383 }, 2384 zoom: { 2385 l10nId: "preferences-zoom-header2", 2386 headingLevel: 2, 2387 items: [ 2388 { 2389 id: "defaultZoom", 2390 l10nId: "preferences-default-zoom-label", 2391 control: "moz-select", 2392 }, 2393 { 2394 id: "zoomText", 2395 l10nId: "preferences-zoom-text-only", 2396 }, 2397 { 2398 id: "zoomWarning", 2399 l10nId: "preferences-text-zoom-override-warning", 2400 control: "moz-message-bar", 2401 controlAttrs: { 2402 type: "warning", 2403 }, 2404 }, 2405 ], 2406 }, 2407 translations: { 2408 inProgress: true, 2409 l10nId: "settings-translations-header", 2410 iconSrc: "chrome://browser/skin/translations.svg", 2411 supportPage: "website-translation", 2412 headingLevel: 2, 2413 items: [ 2414 { 2415 id: "offerTranslations", 2416 l10nId: "settings-translations-offer-to-translate-label", 2417 }, 2418 { 2419 id: "translationsManageButton", 2420 l10nId: "settings-translations-more-settings-button", 2421 control: "moz-box-button", 2422 }, 2423 ], 2424 }, 2425 appearance: { 2426 l10nId: "web-appearance-group", 2427 items: [ 2428 { 2429 id: "web-appearance-override-warning", 2430 l10nId: "preferences-web-appearance-override-warning3", 2431 control: "moz-message-bar", 2432 }, 2433 { 2434 id: "web-appearance-chooser", 2435 control: "moz-visual-picker", 2436 options: [ 2437 { 2438 value: "auto", 2439 l10nId: "preferences-web-appearance-choice-auto2", 2440 controlAttrs: { 2441 id: "preferences-web-appearance-choice-auto", 2442 class: "appearance-chooser-item", 2443 imagesrc: 2444 "chrome://browser/content/preferences/web-appearance-light.svg", 2445 }, 2446 }, 2447 { 2448 value: "light", 2449 l10nId: "preferences-web-appearance-choice-light2", 2450 controlAttrs: { 2451 id: "preferences-web-appearance-choice-light", 2452 class: "appearance-chooser-item", 2453 imagesrc: 2454 "chrome://browser/content/preferences/web-appearance-light.svg", 2455 }, 2456 }, 2457 { 2458 value: "dark", 2459 l10nId: "preferences-web-appearance-choice-dark2", 2460 controlAttrs: { 2461 id: "preferences-web-appearance-choice-dark", 2462 class: "appearance-chooser-item", 2463 imagesrc: 2464 "chrome://browser/content/preferences/web-appearance-dark.svg", 2465 }, 2466 }, 2467 ], 2468 }, 2469 { 2470 id: "web-appearance-manage-themes-link", 2471 l10nId: "preferences-web-appearance-link", 2472 control: "moz-box-link", 2473 controlAttrs: { 2474 href: "about:addons", 2475 }, 2476 }, 2477 ], 2478 }, 2479 downloads: { 2480 l10nId: "downloads-header-2", 2481 headingLevel: 2, 2482 items: [ 2483 { 2484 id: "downloadFolder", 2485 l10nId: "download-save-where-2", 2486 control: "moz-input-folder", 2487 controlAttrs: { 2488 id: "chooseFolder", 2489 }, 2490 }, 2491 { 2492 id: "alwaysAsk", 2493 l10nId: "download-always-ask-where", 2494 }, 2495 { 2496 id: "deletePrivate", 2497 l10nId: "download-private-browsing-delete", 2498 }, 2499 ], 2500 }, 2501 drm: { 2502 subcategory: "drm", 2503 items: [ 2504 { 2505 id: "playDRMContent", 2506 l10nId: "play-drm-content", 2507 supportPage: "drm-content", 2508 }, 2509 ], 2510 }, 2511 contrast: { 2512 l10nId: "preferences-contrast-control-group", 2513 headingLevel: 2, 2514 items: [ 2515 { 2516 id: "contrastControlSettings", 2517 control: "moz-radio-group", 2518 l10nId: "preferences-contrast-control-radio-group", 2519 options: [ 2520 { 2521 id: "contrastSettingsAuto", 2522 value: 0, 2523 l10nId: "preferences-contrast-control-use-platform-settings", 2524 }, 2525 { 2526 id: "contrastSettingsOff", 2527 value: 1, 2528 l10nId: "preferences-contrast-control-off", 2529 }, 2530 { 2531 id: "contrastSettingsOn", 2532 value: 2, 2533 l10nId: "preferences-contrast-control-custom", 2534 items: [ 2535 { 2536 id: "colors", 2537 l10nId: "preferences-colors-manage-button", 2538 control: "moz-box-button", 2539 controlAttrs: { 2540 "search-l10n-ids": 2541 "colors-text-and-background, colors-text.label, colors-text-background.label, colors-links-header, colors-links-unvisited.label, colors-links-visited.label", 2542 }, 2543 }, 2544 ], 2545 }, 2546 ], 2547 }, 2548 ], 2549 }, 2550 browsing: { 2551 l10nId: "browsing-group-label", 2552 items: [ 2553 { 2554 id: "useAutoScroll", 2555 l10nId: "browsing-use-autoscroll", 2556 }, 2557 // { 2558 // id: "useSmoothScrolling", 2559 // l10nId: "browsing-use-smooth-scrolling", 2560 // }, 2561 { 2562 id: "useOverlayScrollbars", 2563 l10nId: "browsing-gtk-use-non-overlay-scrollbars", 2564 }, 2565 { 2566 id: "useOnScreenKeyboard", 2567 l10nId: "browsing-use-onscreen-keyboard", 2568 }, 2569 { 2570 id: "useCursorNavigation", 2571 l10nId: "browsing-use-cursor-navigation", 2572 }, 2573 { 2574 id: "useFullKeyboardNavigation", 2575 l10nId: "browsing-use-full-keyboard-navigation", 2576 }, 2577 // { 2578 // id: "alwaysUnderlineLinks", 2579 // l10nId: "browsing-always-underline-links", 2580 // }, 2581 { 2582 id: "searchStartTyping", 2583 l10nId: "browsing-search-on-start-typing", 2584 }, 2585 { 2586 id: "pictureInPictureToggleEnabled", 2587 l10nId: "browsing-picture-in-picture-toggle-enabled", 2588 supportPage: "picture-in-picture", 2589 items: [ 2590 { 2591 id: "pictureInPictureEnableWhenSwitchingTabs", 2592 l10nId: "browsing-picture-in-picture-enable-when-switching-tabs", 2593 }, 2594 ], 2595 }, 2596 { 2597 id: "mediaControlToggleEnabled", 2598 l10nId: "browsing-media-control", 2599 supportPage: "media-keyboard-control", 2600 }, 2601 // { 2602 // id: "cfrRecommendations", 2603 // l10nId: "browsing-cfr-recommendations", 2604 // supportPage: "extensionrecommendations", 2605 // subcategory: "cfraddons", 2606 // }, 2607 // { 2608 // id: "cfrRecommendations-features", 2609 // l10nId: "browsing-cfr-features", 2610 // supportPage: "extensionrecommendations", 2611 // subcategory: "cfrfeatures", 2612 // }, 2613 // { 2614 // id: "linkPreviewEnabled", 2615 // l10nId: "link-preview-settings-enable", 2616 // subcategory: "link-preview", 2617 // items: [ 2618 // { 2619 // id: "linkPreviewKeyPoints", 2620 // l10nId: "link-preview-settings-key-points", 2621 // }, 2622 // { 2623 // id: "linkPreviewLongPress", 2624 // l10nId: "link-preview-settings-long-press", 2625 // }, 2626 // ], 2627 // }, 2628 ], 2629 }, 2630 httpsOnly: { 2631 items: [ 2632 { 2633 id: "httpsOnlyRadioGroup", 2634 control: "moz-radio-group", 2635 l10nId: "httpsonly-label", 2636 supportPage: "https-only-prefs", 2637 options: [ 2638 { 2639 id: "httpsOnlyRadioEnabled", 2640 value: "enabled", 2641 l10nId: "httpsonly-radio-enabled", 2642 }, 2643 { 2644 id: "httpsOnlyRadioEnabledPBM", 2645 value: "privateOnly", 2646 l10nId: "httpsonly-radio-enabled-pbm", 2647 }, 2648 { 2649 id: "httpsOnlyRadioDisabled", 2650 value: "disabled", 2651 l10nId: "httpsonly-radio-disabled3", 2652 supportPage: "connection-upgrades", 2653 }, 2654 ], 2655 }, 2656 { 2657 id: "httpsOnlyExceptionButton", 2658 l10nId: "sitedata-cookies-exceptions", 2659 control: "moz-box-button", 2660 controlAttrs: { 2661 "search-l10n-ids": 2662 "permissions-address,permissions-allow.label,permissions-remove.label,permissions-remove-all.label,permissions-exceptions-https-only-desc2", 2663 }, 2664 }, 2665 ], 2666 }, 2667 certificates: { 2668 l10nId: "certs-description2", 2669 supportPage: "secure-website-certificate", 2670 headingLevel: 2, 2671 items: [ 2672 { 2673 id: "certEnableThirdPartyToggle", 2674 l10nId: "certs-thirdparty-toggle", 2675 supportPage: "automatically-trust-third-party-certificates", 2676 }, 2677 { 2678 id: "certificateButtonGroup", 2679 control: "moz-box-group", 2680 items: [ 2681 { 2682 id: "viewCertificatesButton", 2683 l10nId: "certs-view", 2684 control: "moz-box-button", 2685 controlAttrs: { 2686 "search-l10n-ids": 2687 "certmgr-tab-mine.label,certmgr-tab-people.label,certmgr-tab-servers.label,certmgr-tab-ca.label,certmgr-mine,certmgr-people,certmgr-server,certmgr-ca,certmgr-cert-name.label,certmgr-token-name.label,certmgr-view.label,certmgr-export.label,certmgr-delete.label", 2688 }, 2689 }, 2690 { 2691 id: "viewSecurityDevicesButton", 2692 l10nId: "certs-devices", 2693 control: "moz-box-button", 2694 controlAttrs: { 2695 "search-l10n-ids": 2696 "devmgr-window.title,devmgr-devlist.label,devmgr-header-details.label,devmgr-header-value.label,devmgr-button-login.label,devmgr-button-logout.label,devmgr-button-changepw.label,devmgr-button-load.label,devmgr-button-unload.label,certs-devices-enable-fips", 2697 }, 2698 }, 2699 ], 2700 }, 2701 ], 2702 }, 2703 browsingProtection: { 2704 items: [ 2705 { 2706 id: "enableSafeBrowsing", 2707 l10nId: "security-enable-safe-browsing", 2708 supportPage: "phishing-malware", 2709 control: "moz-checkbox", 2710 items: [ 2711 { 2712 id: "blockDownloads", 2713 l10nId: "security-block-downloads", 2714 }, 2715 { 2716 id: "blockUncommonUnwanted", 2717 l10nId: "security-block-uncommon-software", 2718 }, 2719 ], 2720 }, 2721 ], 2722 }, 2723 nonTechnicalPrivacy: { 2724 l10nId: "non-technical-privacy-label", 2725 items: [ 2726 { 2727 id: "gpcEnabled", 2728 l10nId: "global-privacy-control-description", 2729 supportPage: "global-privacy-control", 2730 controlAttrs: { 2731 "search-l10n-ids": "global-privacy-control-search", 2732 }, 2733 }, 2734 { 2735 id: "dntRemoval", 2736 l10nId: "do-not-track-removal2", 2737 control: "moz-box-link", 2738 supportPage: "how-do-i-turn-do-not-track-feature", 2739 }, 2740 ], 2741 }, 2742 nonTechnicalPrivacy2: { 2743 inProgress: true, 2744 l10nId: "non-technical-privacy-heading", 2745 headingLevel: 2, 2746 items: [ 2747 { 2748 id: "gpcEnabled", 2749 l10nId: "global-privacy-control-description", 2750 supportPage: "global-privacy-control", 2751 controlAttrs: { 2752 "search-l10n-ids": "global-privacy-control-search", 2753 }, 2754 }, 2755 { 2756 id: "relayIntegration", 2757 l10nId: "preferences-privacy-relay-available", 2758 supportPage: "firefox-relay-integration", 2759 }, 2760 { 2761 id: "dntRemoval", 2762 l10nId: "do-not-track-removal3", 2763 control: "moz-message-bar", 2764 supportPage: "how-do-i-turn-do-not-track-feature", 2765 controlAttrs: { 2766 dismissable: true, 2767 }, 2768 }, 2769 ], 2770 }, 2771 securityPrivacyStatus: { 2772 inProgress: true, 2773 items: [ 2774 { 2775 id: "privacyCard", 2776 control: "security-privacy-card", 2777 }, 2778 ], 2779 }, 2780 securityPrivacyWarnings: { 2781 inProgress: true, 2782 items: [ 2783 { 2784 id: "warningCard", 2785 l10nId: "security-privacy-issue-card", 2786 control: "moz-card", 2787 controlAttrs: { 2788 type: "accordion", 2789 }, 2790 items: [ 2791 { 2792 id: "securityWarningsGroup", 2793 control: "moz-box-group", 2794 controlAttrs: { 2795 type: "list", 2796 }, 2797 }, 2798 ], 2799 }, 2800 ], 2801 }, 2802 support: { 2803 inProgress: true, 2804 l10nId: "support-application-heading", 2805 headingLevel: 2, 2806 items: [ 2807 { 2808 id: "supportLinksGroup", 2809 control: "moz-box-group", 2810 items: [ 2811 { 2812 id: "supportGetHelp", 2813 l10nId: "support-get-help", 2814 control: "moz-box-link", 2815 supportPage: "preferences", 2816 }, 2817 { 2818 id: "supportShareIdeas", 2819 l10nId: "support-share-ideas", 2820 control: "moz-box-link", 2821 controlAttrs: { 2822 href: "https://connect.mozilla.org/", 2823 }, 2824 }, 2825 ], 2826 }, 2827 ], 2828 }, 2829 performance: { 2830 items: [ 2831 { 2832 id: "useRecommendedPerformanceSettings", 2833 l10nId: "performance-use-recommended-settings-checkbox", 2834 supportPage: "performance", 2835 }, 2836 { 2837 id: "allowHWAccel", 2838 l10nId: "performance-allow-hw-accel", 2839 }, 2840 ], 2841 }, 2842 ipprotection: { 2843 l10nId: "ip-protection-description", 2844 headingLevel: 2, 2845 // TODO: Replace support url with finalized link (Bug 1993266) 2846 supportPage: "ip-protection", 2847 items: [ 2848 { 2849 id: "ipProtectionExceptions", 2850 l10nId: "ip-protection-site-exceptions", 2851 control: "moz-fieldset", 2852 controlAttrs: { 2853 ".headingLevel": 3, 2854 }, 2855 items: [ 2856 { 2857 id: "ipProtectionExceptionAllListButton", 2858 control: "moz-box-button", 2859 }, 2860 ], 2861 }, 2862 { 2863 id: "ipProtectionAutoStart", 2864 l10nId: "ip-protection-autostart", 2865 control: "moz-fieldset", 2866 items: [ 2867 { 2868 id: "ipProtectionAutoStartCheckbox", 2869 l10nId: "ip-protection-autostart-checkbox", 2870 control: "moz-checkbox", 2871 }, 2872 { 2873 id: "ipProtectionAutoStartPrivateCheckbox", 2874 l10nId: "ip-protection-autostart-private-checkbox", 2875 control: "moz-checkbox", 2876 }, 2877 ], 2878 }, 2879 { 2880 id: "ipProtectionAdditionalLinks", 2881 control: "moz-box-group", 2882 options: [ 2883 { 2884 id: "ipProtectionSupportLink", 2885 l10nId: "ip-protection-contact-support-link", 2886 control: "moz-box-link", 2887 controlAttrs: { 2888 href: "https://support.mozilla.org/questions/new/mozilla-vpn/form", 2889 }, 2890 }, 2891 { 2892 id: "ipProtectionUpgradeLink", 2893 l10nId: "ip-protection-upgrade-link", 2894 control: "moz-box-link", 2895 controlAttrs: { 2896 href: "https://www.mozilla.org/products/vpn/", 2897 }, 2898 }, 2899 ], 2900 }, 2901 ], 2902 }, 2903 cookiesAndSiteData: { 2904 l10nId: "sitedata-label", 2905 items: [ 2906 { 2907 id: "clearSiteDataButton", 2908 l10nId: "sitedata-clear2", 2909 control: "moz-box-button", 2910 iconSrc: "chrome://browser/skin/flame.svg", 2911 controlAttrs: { 2912 "search-l10n-ids": ` 2913 clear-site-data-cookies-empty.label, 2914 clear-site-data-cache-empty.label 2915 `, 2916 }, 2917 }, 2918 { 2919 id: "deleteOnCloseInfo", 2920 l10nId: "sitedata-delete-on-close-private-browsing3", 2921 control: "moz-message-bar", 2922 }, 2923 { 2924 id: "manageDataSettingsGroup", 2925 control: "moz-box-group", 2926 controlAttrs: { 2927 type: "default", 2928 }, 2929 items: [ 2930 { 2931 id: "siteDataSize", 2932 l10nId: "sitedata-total-size-calculating", 2933 control: "moz-box-item", 2934 supportPage: "sitedata-learn-more", 2935 }, 2936 { 2937 id: "siteDataSettings", 2938 l10nId: "sitedata-settings2", 2939 control: "moz-box-button", 2940 controlAttrs: { 2941 "search-l10n-ids": ` 2942 site-data-settings-window.title, 2943 site-data-column-host.label, 2944 site-data-column-cookies.label, 2945 site-data-column-storage.label, 2946 site-data-settings-description, 2947 site-data-remove-all.label, 2948 `, 2949 }, 2950 }, 2951 { 2952 id: "cookieExceptions", 2953 l10nId: "sitedata-cookies-exceptions2", 2954 control: "moz-box-button", 2955 controlAttrs: { 2956 "search-l10n-ids": ` 2957 permissions-address, 2958 permissions-block.label, 2959 permissions-allow.label, 2960 permissions-remove.label, 2961 permissions-remove-all.label, 2962 permissions-exceptions-cookie-desc 2963 `, 2964 }, 2965 }, 2966 ], 2967 }, 2968 { 2969 id: "deleteOnClose", 2970 l10nId: "sitedata-delete-on-close", 2971 }, 2972 ], 2973 }, 2974 cookiesAndSiteData2: { 2975 inProgress: true, 2976 l10nId: "sitedata-heading", 2977 headingLevel: 2, 2978 items: [ 2979 { 2980 id: "siteDataSize", 2981 l10nId: "sitedata-total-size-calculating", 2982 control: "moz-box-item", 2983 supportPage: "sitedata-learn-more", 2984 }, 2985 { 2986 id: "manageDataSettingsGroup", 2987 control: "moz-box-group", 2988 controlAttrs: { 2989 type: "default", 2990 }, 2991 items: [ 2992 { 2993 id: "clearSiteDataButton", 2994 l10nId: "sitedata-clear2", 2995 control: "moz-box-button", 2996 iconSrc: "chrome://browser/skin/flame.svg", 2997 controlAttrs: { 2998 "search-l10n-ids": ` 2999 clear-site-data-cookies-empty.label, 3000 clear-site-data-cache-empty.label 3001 `, 3002 }, 3003 }, 3004 { 3005 id: "siteDataSettings", 3006 l10nId: "sitedata-settings3", 3007 control: "moz-box-button", 3008 controlAttrs: { 3009 "search-l10n-ids": ` 3010 site-data-settings-window.title, 3011 site-data-column-host.label, 3012 site-data-column-cookies.label, 3013 site-data-column-storage.label, 3014 site-data-settings-description, 3015 site-data-remove-all.label, 3016 `, 3017 }, 3018 }, 3019 { 3020 id: "cookieExceptions", 3021 l10nId: "sitedata-cookies-exceptions3", 3022 control: "moz-box-button", 3023 controlAttrs: { 3024 "search-l10n-ids": ` 3025 permissions-address, 3026 permissions-block.label, 3027 permissions-allow.label, 3028 permissions-remove.label, 3029 permissions-remove-all.label, 3030 permissions-exceptions-cookie-desc 3031 `, 3032 }, 3033 }, 3034 ], 3035 }, 3036 { 3037 id: "deleteOnClose", 3038 l10nId: "sitedata-delete-on-close", 3039 }, 3040 ], 3041 }, 3042 networkProxy: { 3043 items: [ 3044 { 3045 id: "connectionSettings", 3046 l10nId: "network-proxy-connection-settings", 3047 control: "moz-box-button", 3048 controlAttrs: { 3049 "search-l10n-ids": 3050 "connection-window2.title,connection-proxy-option-no.label,connection-proxy-option-auto.label,connection-proxy-option-system.label,connection-proxy-option-wpad.label,connection-proxy-option-manual.label,connection-proxy-http,connection-proxy-https,connection-proxy-http-port,connection-proxy-socks,connection-proxy-socks4,connection-proxy-socks5,connection-proxy-noproxy,connection-proxy-noproxy-desc,connection-proxy-https-sharing.label,connection-proxy-autotype.label,connection-proxy-reload.label,connection-proxy-autologin-checkbox.label,connection-proxy-socks-remote-dns.label", 3051 }, 3052 // Bug 1990552: due to how this lays out in the legacy page, we do not include a 3053 // controllingExtensionInfo attribute here. We will want one in the redesigned page, 3054 // using storeId: "proxy.settings". 3055 controllingExtensionInfo: undefined, 3056 }, 3057 ], 3058 }, 3059 passwords: { 3060 inProgress: true, 3061 id: "passwordsGroup", 3062 l10nId: "forms-passwords-header", 3063 headingLevel: 2, 3064 items: [ 3065 { 3066 id: "savePasswords", 3067 l10nId: "forms-ask-to-save-passwords", 3068 items: [ 3069 { 3070 id: "managePasswordExceptions", 3071 l10nId: "forms-manage-password-exceptions", 3072 control: "moz-box-button", 3073 controlAttrs: { 3074 "search-l10n-ids": 3075 "permissions-address,permissions-exceptions-saved-passwords-window.title,permissions-exceptions-saved-passwords-desc,", 3076 }, 3077 }, 3078 { 3079 id: "fillUsernameAndPasswords", 3080 l10nId: "forms-fill-usernames-and-passwords-2", 3081 controlAttrs: { 3082 "search-l10n-ids": "forms-saved-passwords-searchkeywords", 3083 }, 3084 }, 3085 { 3086 id: "suggestStrongPasswords", 3087 l10nId: "forms-suggest-passwords", 3088 supportPage: "how-generate-secure-password-firefox", 3089 }, 3090 ], 3091 }, 3092 { 3093 id: "requireOSAuthForPasswords", 3094 l10nId: "forms-os-reauth-2", 3095 }, 3096 { 3097 id: "allowWindowSSO", 3098 l10nId: "forms-windows-sso", 3099 supportPage: "windows-sso", 3100 }, 3101 { 3102 id: "manageSavedPasswords", 3103 l10nId: "forms-saved-passwords-2", 3104 control: "moz-box-link", 3105 }, 3106 { 3107 id: "additionalProtectionsGroup", 3108 l10nId: "forms-additional-protections-header", 3109 control: "moz-fieldset", 3110 controlAttrs: { 3111 headingLevel: 2, 3112 }, 3113 items: [ 3114 { 3115 id: "primaryPasswordNotSet", 3116 control: "moz-box-group", 3117 items: [ 3118 { 3119 id: "usePrimaryPassword", 3120 l10nId: "forms-primary-pw-use-2", 3121 control: "moz-box-item", 3122 supportPage: "primary-password-stored-logins", 3123 }, 3124 { 3125 id: "addPrimaryPassword", 3126 l10nId: "forms-primary-pw-set", 3127 control: "moz-box-button", 3128 }, 3129 ], 3130 }, 3131 { 3132 id: "primaryPasswordSet", 3133 control: "moz-box-group", 3134 items: [ 3135 { 3136 id: "statusPrimaryPassword", 3137 l10nId: "forms-primary-pw-on", 3138 control: "moz-box-item", 3139 controlAttrs: { 3140 iconsrc: "chrome://global/skin/icons/check-filled.svg", 3141 }, 3142 options: [ 3143 { 3144 id: "turnOffPrimaryPassword", 3145 l10nId: "forms-primary-pw-turn-off", 3146 control: "moz-button", 3147 slot: "actions", 3148 }, 3149 ], 3150 }, 3151 { 3152 id: "changePrimaryPassword", 3153 l10nId: "forms-primary-pw-change-2", 3154 control: "moz-box-button", 3155 }, 3156 ], 3157 }, 3158 { 3159 id: "breachAlerts", 3160 l10nId: "forms-breach-alerts", 3161 supportPage: "lockwise-alerts", 3162 }, 3163 ], 3164 }, 3165 ], 3166 }, 3167 history: { 3168 items: [ 3169 { 3170 id: "historyMode", 3171 control: "moz-select", 3172 options: [ 3173 { 3174 value: "remember", 3175 l10nId: "history-remember-option-all", 3176 }, 3177 { value: "dontremember", l10nId: "history-remember-option-never" }, 3178 { value: "custom", l10nId: "history-remember-option-custom" }, 3179 ], 3180 controlAttrs: { 3181 "search-l10n-ids": ` 3182 history-remember-description3, 3183 history-dontremember-description3, 3184 history-private-browsing-permanent.label, 3185 history-remember-browser-option.label, 3186 history-remember-search-option.label, 3187 history-clear-on-close-option.label, 3188 history-clear-on-close-settings.label 3189 `, 3190 }, 3191 }, 3192 { 3193 id: "privateBrowsingAutoStart", 3194 l10nId: "history-private-browsing-permanent", 3195 }, 3196 { 3197 id: "rememberHistory", 3198 l10nId: "history-remember-browser-option", 3199 }, 3200 { 3201 id: "rememberForms", 3202 l10nId: "history-remember-search-option", 3203 }, 3204 { 3205 id: "alwaysClear", 3206 l10nId: "history-clear-on-close-option", 3207 }, 3208 { 3209 id: "clearDataSettings", 3210 l10nId: "history-clear-on-close-settings", 3211 control: "moz-box-button", 3212 controlAttrs: { 3213 "search-l10n-ids": ` 3214 clear-data-settings-label, 3215 history-section-label, 3216 item-history-and-downloads.label, 3217 item-cookies.label, 3218 item-active-logins.label, 3219 item-cache.label, 3220 item-form-search-history.label, 3221 data-section-label, 3222 item-site-settings.label, 3223 item-offline-apps.label 3224 `, 3225 }, 3226 }, 3227 { 3228 id: "clearHistoryButton", 3229 l10nId: "history-clear-button", 3230 control: "moz-box-button", 3231 }, 3232 ], 3233 }, 3234 history2: { 3235 inProgress: true, 3236 l10nId: "history-section-header", 3237 items: [ 3238 { 3239 id: "deleteOnCloseInfo", 3240 l10nId: "sitedata-delete-on-close-private-browsing3", 3241 control: "moz-message-bar", 3242 }, 3243 { 3244 id: "historyMode", 3245 control: "moz-radio-group", 3246 options: [ 3247 { 3248 value: "remember", 3249 l10nId: "history-remember-option-all", 3250 }, 3251 { value: "dontremember", l10nId: "history-remember-option-never" }, 3252 { 3253 value: "custom", 3254 l10nId: "history-remember-option-custom", 3255 items: [ 3256 { 3257 id: "customHistoryButton", 3258 control: "moz-box-button", 3259 l10nId: "history-custom-button", 3260 }, 3261 ], 3262 }, 3263 ], 3264 controlAttrs: { 3265 "search-l10n-ids": ` 3266 history-remember-description3, 3267 history-dontremember-description3, 3268 history-private-browsing-permanent.label, 3269 history-remember-browser-option.label, 3270 history-remember-search-option.label, 3271 history-clear-on-close-option.label, 3272 history-clear-on-close-settings.label 3273 `, 3274 }, 3275 }, 3276 ], 3277 }, 3278 historyAdvanced: { 3279 l10nId: "history-custom-section-header", 3280 headingLevel: 2, 3281 items: [ 3282 { 3283 id: "privateBrowsingAutoStart", 3284 l10nId: "history-private-browsing-permanent", 3285 }, 3286 { 3287 id: "rememberHistory", 3288 l10nId: "history-remember-browser-option", 3289 }, 3290 { 3291 id: "rememberForms", 3292 l10nId: "history-remember-search-option", 3293 }, 3294 { 3295 id: "alwaysClear", 3296 l10nId: "history-clear-on-close-option", 3297 items: [ 3298 { 3299 id: "clearDataSettings", 3300 l10nId: "history-clear-on-close-settings", 3301 control: "moz-box-button", 3302 controlAttrs: { 3303 "search-l10n-ids": ` 3304 clear-data-settings-label, 3305 history-section-label, 3306 item-history-and-downloads.label, 3307 item-cookies.label, 3308 item-active-logins.label, 3309 item-cache.label, 3310 item-form-search-history.label, 3311 data-section-label, 3312 item-site-settings.label, 3313 item-offline-apps.label 3314 `, 3315 }, 3316 }, 3317 ], 3318 }, 3319 ], 3320 }, 3321 permissions: { 3322 id: "permissions", 3323 l10nId: "permissions-header2", 3324 headingLevel: 2, 3325 items: [ 3326 { 3327 id: "permissionBox", 3328 control: "moz-box-group", 3329 controlAttrs: { 3330 type: "list", 3331 }, 3332 items: [ 3333 { 3334 id: "locationSettingsButton", 3335 control: "moz-box-button", 3336 l10nId: "permissions-location2", 3337 controlAttrs: { 3338 ".iconSrc": "chrome://browser/skin/notification-icons/geo.svg", 3339 "search-l10n-ids": 3340 "permissions-remove.label,permissions-remove-all.label,permissions-site-location-window2.title,permissions-site-location-desc,permissions-site-location-disable-label,permissions-site-location-disable-desc", 3341 }, 3342 }, 3343 { 3344 id: "cameraSettingsButton", 3345 control: "moz-box-button", 3346 l10nId: "permissions-camera2", 3347 controlAttrs: { 3348 ".iconSrc": "chrome://browser/skin/notification-icons/camera.svg", 3349 "search-l10n-ids": 3350 "permissions-remove.label,permissions-remove-all.label,permissions-site-camera-window2.title,permissions-site-camera-desc,permissions-site-camera-disable-label,permissions-site-camera-disable-desc,", 3351 }, 3352 }, 3353 { 3354 id: "localHostSettingsButton", 3355 control: "moz-box-button", 3356 l10nId: "permissions-localhost2", 3357 controlAttrs: { 3358 ".iconSrc": 3359 "chrome://browser/skin/notification-icons/local-host.svg", 3360 "search-l10n-ids": 3361 "permissions-remove.label,permissions-remove-all.label,permissions-site-localhost-window.title,permissions-site-localhost-desc,permissions-site-localhost-disable-label,permissions-site-localhost-disable-desc,", 3362 }, 3363 }, 3364 { 3365 id: "localNetworkSettingsButton", 3366 control: "moz-box-button", 3367 l10nId: "permissions-local-network2", 3368 controlAttrs: { 3369 ".iconSrc": 3370 "chrome://browser/skin/notification-icons/local-network.svg", 3371 "search-l10n-ids": 3372 "permissions-remove.label,permissions-remove-all.label,permissions-site-local-network-window.title,permissions-site-local-network-desc,permissions-site-local-network-disable-label,permissions-site-local-network-disable-desc,", 3373 }, 3374 }, 3375 { 3376 id: "microphoneSettingsButton", 3377 control: "moz-box-button", 3378 l10nId: "permissions-microphone2", 3379 controlAttrs: { 3380 ".iconSrc": 3381 "chrome://browser/skin/notification-icons/microphone.svg", 3382 "search-l10n-ids": 3383 "permissions-remove.label,permissions-remove-all.label,permissions-site-microphone-window2.title,permissions-site-microphone-desc,permissions-site-microphone-disable-label,permissions-site-microphone-disable-desc,", 3384 }, 3385 }, 3386 { 3387 id: "speakerSettingsButton", 3388 control: "moz-box-button", 3389 l10nId: "permissions-speaker2", 3390 controlAttrs: { 3391 ".iconSrc": 3392 "chrome://browser/skin/notification-icons/speaker.svg", 3393 "search-l10n-ids": 3394 "permissions-remove.label,permissions-remove-all.label,permissions-site-speaker-window.title,permissions-site-speaker-desc,", 3395 }, 3396 }, 3397 { 3398 id: "notificationSettingsButton", 3399 control: "moz-box-button", 3400 l10nId: "permissions-notification2", 3401 controlAttrs: { 3402 ".iconSrc": 3403 "chrome://browser/skin/notification-icons/desktop-notification.svg", 3404 "search-l10n-ids": 3405 "permissions-remove.label,permissions-remove-all.label,permissions-site-notification-window2.title,permissions-site-notification-desc,permissions-site-notification-disable-label,permissions-site-notification-disable-desc,", 3406 }, 3407 }, 3408 { 3409 id: "autoplaySettingsButton", 3410 control: "moz-box-button", 3411 l10nId: "permissions-autoplay2", 3412 controlAttrs: { 3413 ".iconSrc": 3414 "chrome://browser/skin/notification-icons/autoplay-media.svg", 3415 "search-l10n-ids": 3416 "permissions-remove.label,permissions-remove-all.label,permissions-site-autoplay-window2.title,permissions-site-autoplay-desc,", 3417 }, 3418 }, 3419 { 3420 id: "xrSettingsButton", 3421 control: "moz-box-button", 3422 l10nId: "permissions-xr2", 3423 controlAttrs: { 3424 ".iconSrc": "chrome://browser/skin/notification-icons/xr.svg", 3425 "search-l10n-ids": 3426 "permissions-remove.label,permissions-remove-all.label,permissions-site-xr-window2.title,permissions-site-xr-desc,permissions-site-xr-disable-label,permissions-site-xr-disable-desc,", 3427 }, 3428 }, 3429 ], 3430 }, 3431 { 3432 id: "popupPolicy", 3433 l10nId: "permissions-block-popups2", 3434 items: [ 3435 { 3436 id: "popupPolicyButton", 3437 l10nId: "permissions-block-popups-exceptions-button2", 3438 control: "moz-box-button", 3439 controlAttrs: { 3440 "search-l10n-ids": 3441 "permissions-address,permissions-exceptions-popup-window3.title,permissions-exceptions-popup-desc2", 3442 }, 3443 }, 3444 ], 3445 }, 3446 { 3447 id: "warnAddonInstall", 3448 l10nId: "permissions-addon-install-warning2", 3449 items: [ 3450 { 3451 id: "addonExceptions", 3452 l10nId: "permissions-addon-exceptions2", 3453 control: "moz-box-button", 3454 controlAttrs: { 3455 "search-l10n-ids": 3456 "permissions-address,permissions-allow.label,permissions-remove.label,permissions-remove-all.label,permissions-exceptions-addons-window2.title,permissions-exceptions-addons-desc", 3457 }, 3458 }, 3459 ], 3460 }, 3461 { 3462 id: "notificationsDoNotDisturb", 3463 l10nId: "permissions-notification-pause", 3464 }, 3465 ], 3466 }, 3467 dnsOverHttps: { 3468 inProgress: true, 3469 items: [ 3470 { 3471 id: "dohBox", 3472 control: "moz-box-group", 3473 items: [ 3474 { 3475 id: "dohModeBoxItem", 3476 control: "moz-box-item", 3477 }, 3478 { 3479 id: "dohAdvancedButton", 3480 l10nId: "preferences-doh-advanced-button", 3481 control: "moz-box-button", 3482 }, 3483 ], 3484 }, 3485 ], 3486 }, 3487 defaultEngine: { 3488 l10nId: "search-engine-group", 3489 headingLevel: 2, 3490 items: [ 3491 { 3492 id: "defaultEngineNormal", 3493 l10nId: "search-default-engine", 3494 control: "moz-select", 3495 }, 3496 { 3497 id: "searchShowSearchTermCheckbox", 3498 l10nId: "search-show-search-term-option-2", 3499 }, 3500 { 3501 id: "browserSeparateDefaultEngine", 3502 l10nId: "search-separate-default-engine-2", 3503 items: [ 3504 { 3505 id: "defaultPrivateEngine", 3506 l10nId: "search-separate-default-engine-dropdown", 3507 control: "moz-select", 3508 }, 3509 ], 3510 }, 3511 ], 3512 }, 3513 searchSuggestions: { 3514 l10nId: "search-suggestions-header-2", 3515 headingLevel: 2, 3516 items: [ 3517 { 3518 id: "suggestionsInSearchFieldsCheckbox", 3519 l10nId: "search-show-suggestions-option", 3520 items: [ 3521 { 3522 id: "urlBarSuggestionCheckbox", 3523 l10nId: "search-show-suggestions-url-bar-option", 3524 }, 3525 { 3526 id: "showSearchSuggestionsFirstCheckbox", 3527 l10nId: "search-show-suggestions-above-history-option-2", 3528 }, 3529 { 3530 id: "showSearchSuggestionsPrivateWindowsCheckbox", 3531 l10nId: "search-show-suggestions-private-windows-2", 3532 }, 3533 { 3534 id: "showTrendingSuggestionsCheckbox", 3535 l10nId: "addressbar-locbar-showtrendingsuggestions-option-2", 3536 supportPage: "use-google-trending-search-firefox-address-bar", 3537 }, 3538 { 3539 id: "urlBarSuggestionPermanentPBMessage", 3540 l10nId: "search-suggestions-cant-show-2", 3541 control: "moz-message-bar", 3542 }, 3543 ], 3544 }, 3545 ], 3546 }, 3547 dnsOverHttpsAdvanced: { 3548 inProgress: true, 3549 l10nId: "preferences-doh-advanced-section", 3550 supportPage: "dns-over-https", 3551 headingLevel: 2, 3552 items: [ 3553 { 3554 id: "dohStatusBox", 3555 control: "moz-message-bar", 3556 }, 3557 { 3558 id: "dohRadioGroup", 3559 control: "moz-radio-group", 3560 options: [ 3561 { 3562 id: "dohRadioDefault", 3563 value: "default", 3564 l10nId: "preferences-doh-radio-default", 3565 }, 3566 { 3567 id: "dohRadioCustom", 3568 value: "custom", 3569 l10nId: "preferences-doh-radio-custom", 3570 items: [ 3571 { 3572 id: "dohFallbackIfCustom", 3573 l10nId: "preferences-doh-fallback-label", 3574 }, 3575 { 3576 id: "dohProviderSelect", 3577 l10nId: "preferences-doh-select-resolver-label", 3578 control: "moz-select", 3579 }, 3580 { 3581 id: "dohCustomProvider", 3582 control: "moz-input-text", 3583 l10nId: "preferences-doh-custom-provider-label", 3584 }, 3585 ], 3586 }, 3587 { 3588 id: "dohRadioOff", 3589 value: "off", 3590 l10nId: "preferences-doh-radio-off", 3591 }, 3592 ], 3593 }, 3594 { 3595 id: "dohExceptionsButton", 3596 l10nId: "preferences-doh-manage-exceptions2", 3597 control: "moz-box-button", 3598 controlAttrs: { 3599 "search-l10n-ids": 3600 "permissions-doh-entry-field,permissions-doh-add-exception.label,permissions-doh-remove.label,permissions-doh-remove-all.label,permissions-exceptions-doh-window.title,permissions-exceptions-manage-doh-desc,", 3601 }, 3602 }, 3603 ], 3604 }, 3605 managePayments: { 3606 items: [ 3607 { 3608 id: "add-payment-button", 3609 control: "moz-button", 3610 l10nId: "autofill-payment-methods-add-button", 3611 }, 3612 { 3613 id: "payments-list", 3614 control: "moz-box-group", 3615 controlAttrs: { 3616 type: "list", 3617 }, 3618 }, 3619 ], 3620 }, 3621 tabs: { 3622 l10nId: "tabs-group-header2", 3623 headingLevel: 2, 3624 items: [ 3625 { 3626 id: "tabsOpening", 3627 control: "moz-fieldset", 3628 l10nId: "tabs-opening-heading", 3629 headingLevel: 3, 3630 items: [ 3631 { 3632 id: "linkTargeting", 3633 l10nId: "open-new-link-as-tabs", 3634 }, 3635 { 3636 id: "switchToNewTabs", 3637 l10nId: "switch-to-new-tabs", 3638 }, 3639 { 3640 id: "openAppLinksNextToActiveTab", 3641 l10nId: "open-external-link-next-to-active-tab", 3642 }, 3643 { 3644 id: "warnOpenMany", 3645 l10nId: "warn-on-open-many-tabs", 3646 }, 3647 ], 3648 }, 3649 { 3650 id: "tabsInteraction", 3651 control: "moz-fieldset", 3652 l10nId: "tabs-interaction-heading", 3653 headingLevel: 3, 3654 items: [ 3655 { 3656 id: "ctrlTabRecentlyUsedOrder", 3657 l10nId: "ctrl-tab-recently-used-order", 3658 }, 3659 { 3660 id: "tabPreviewShowThumbnails", 3661 l10nId: "settings-tabs-show-image-in-preview", 3662 }, 3663 { 3664 id: "tabGroupSuggestions", 3665 l10nId: "settings-tabs-show-group-and-tab-suggestions", 3666 }, 3667 { 3668 id: "showTabsInTaskbar", 3669 l10nId: "show-tabs-in-taskbar", 3670 }, 3671 ], 3672 }, 3673 { 3674 id: "browserContainersbox", 3675 control: "moz-fieldset", 3676 l10nId: "tabs-containers-heading", 3677 headingLevel: 3, 3678 items: [ 3679 { 3680 id: "browserContainersCheckbox", 3681 l10nId: "browser-containers-enabled", 3682 supportPage: "containers", 3683 }, 3684 { 3685 id: "browserContainersSettings", 3686 l10nId: "browser-containers-settings", 3687 control: "moz-box-button", 3688 controlAttrs: { 3689 "search-l10n-ids": 3690 "containers-add-button.label, containers-settings-button.label, containers-remove-button.label, containers-new-tab-check.label", 3691 }, 3692 }, 3693 ], 3694 }, 3695 { 3696 id: "tabsClosing", 3697 control: "moz-fieldset", 3698 l10nId: "tabs-closing-heading", 3699 headingLevel: 3, 3700 items: [ 3701 { 3702 id: "warnCloseMultiple", 3703 l10nId: "ask-on-close-multiple-tabs", 3704 }, 3705 { 3706 id: "warnOnQuitKey", 3707 l10nId: "ask-on-quit-with-key", 3708 }, 3709 ], 3710 }, 3711 ], 3712 }, 3713 etpStatus: { 3714 inProgress: true, 3715 headingLevel: 2, 3716 l10nId: "preferences-etp-status-header", 3717 supportPage: "enhanced-tracking-protection", 3718 iconSrc: "chrome://browser/skin/controlcenter/tracking-protection.svg", 3719 items: [ 3720 { 3721 id: "etpStatusBoxGroup", 3722 control: "moz-box-group", 3723 items: [ 3724 { 3725 id: "etpStatusItem", 3726 l10nId: "preferences-etp-level-standard", 3727 control: "moz-box-item", 3728 }, 3729 { 3730 id: "etpStatusAdvancedButton", 3731 l10nId: "preferences-etp-status-advanced-button", 3732 control: "moz-box-button", 3733 }, 3734 ], 3735 }, 3736 { 3737 id: "protectionsDashboardLink", 3738 l10nId: "preferences-etp-status-protections-dashboard-link", 3739 control: "moz-box-link", 3740 controlAttrs: { 3741 href: "about:protections", 3742 }, 3743 }, 3744 ], 3745 }, 3746 etpBanner: { 3747 inProgress: true, 3748 items: [ 3749 { 3750 id: "etpBannerEl", 3751 control: "moz-card", 3752 }, 3753 ], 3754 }, 3755 etpAdvanced: { 3756 inProgress: true, 3757 headingLevel: 2, 3758 l10nId: "preferences-etp-advanced-settings-group", 3759 supportPage: "enhanced-tracking-protection", 3760 items: [ 3761 { 3762 id: "contentBlockingCategoryRadioGroup", 3763 control: "moz-radio-group", 3764 options: [ 3765 { 3766 id: "etpLevelStandard", 3767 value: "standard", 3768 l10nId: "preferences-etp-level-standard", 3769 }, 3770 { 3771 id: "etpLevelStrict", 3772 value: "strict", 3773 l10nId: "preferences-etp-level-strict", 3774 items: [ 3775 { 3776 id: "etpAllowListBaselineEnabled", 3777 l10nId: "content-blocking-baseline-exceptions-3", 3778 supportPage: "manage-enhanced-tracking-protection-exceptions", 3779 control: "moz-checkbox", 3780 items: [ 3781 { 3782 id: "etpAllowListConvenienceEnabled", 3783 l10nId: "content-blocking-convenience-exceptions-3", 3784 control: "moz-checkbox", 3785 }, 3786 ], 3787 }, 3788 ], 3789 }, 3790 { 3791 id: "etpLevelCustom", 3792 value: "custom", 3793 l10nId: "preferences-etp-level-custom", 3794 items: [ 3795 { 3796 id: "etpCustomizeButton", 3797 l10nId: "preferences-etp-customize-button", 3798 control: "moz-box-button", 3799 }, 3800 ], 3801 }, 3802 ], 3803 }, 3804 { 3805 id: "reloadTabsHint", 3806 control: "moz-message-bar", 3807 l10nId: "preferences-etp-reload-tabs-hint", 3808 options: [ 3809 { 3810 control: "moz-button", 3811 l10nId: "preferences-etp-reload-tabs-hint-button", 3812 slot: "actions", 3813 }, 3814 ], 3815 }, 3816 { 3817 id: "rfpWarning", 3818 control: "moz-message-bar", 3819 l10nId: "preferences-etp-rfp-warning-message", 3820 supportPage: "resist-fingerprinting", 3821 }, 3822 { 3823 id: "etpLevelWarning", 3824 control: "moz-promo", 3825 l10nId: "preferences-etp-level-warning-message", 3826 controlAttrs: { 3827 ".imageAlignment": "end", 3828 ".imageSrc": 3829 "chrome://browser/content/preferences/etp-toggle-promo.svg", 3830 }, 3831 }, 3832 { 3833 id: "etpManageExceptionsButton", 3834 l10nId: "preferences-etp-manage-exceptions-button", 3835 control: "moz-box-button", 3836 }, 3837 ], 3838 }, 3839 etpReset: { 3840 inProgress: true, 3841 headingLevel: 2, 3842 l10nId: "preferences-etp-reset", 3843 items: [ 3844 { 3845 id: "etpResetButtonGroup", 3846 control: "div", 3847 items: [ 3848 { 3849 id: "etpResetStandardButton", 3850 control: "moz-button", 3851 l10nId: "preferences-etp-reset-standard-button", 3852 }, 3853 { 3854 id: "etpResetStrictButton", 3855 control: "moz-button", 3856 l10nId: "preferences-etp-reset-strict-button", 3857 }, 3858 ], 3859 }, 3860 ], 3861 }, 3862 etpCustomize: { 3863 inProgress: true, 3864 headingLevel: 2, 3865 l10nId: "preferences-etp-custom-control-group", 3866 items: [ 3867 { 3868 id: "etpAllowListBaselineEnabledCustom", 3869 l10nId: "content-blocking-baseline-exceptions-3", 3870 supportPage: "manage-enhanced-tracking-protection-exceptions", 3871 control: "moz-checkbox", 3872 items: [ 3873 { 3874 id: "etpAllowListConvenienceEnabledCustom", 3875 l10nId: "content-blocking-convenience-exceptions-3", 3876 control: "moz-checkbox", 3877 }, 3878 ], 3879 }, 3880 { 3881 id: "etpCustomCookiesEnabled", 3882 l10nId: "preferences-etp-custom-cookies-enabled", 3883 control: "moz-toggle", 3884 items: [ 3885 { 3886 id: "cookieBehavior", 3887 l10nId: "preferences-etp-custom-cookie-behavior", 3888 control: "moz-select", 3889 options: [ 3890 { 3891 value: Ci.nsICookieService.BEHAVIOR_ACCEPT.toString(), 3892 l10nId: "preferences-etpc-custom-cookie-behavior-accept-all", 3893 }, 3894 { 3895 value: Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER.toString(), 3896 l10nId: "sitedata-option-block-cross-site-trackers", 3897 }, 3898 { 3899 value: 3900 Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN.toString(), 3901 l10nId: "sitedata-option-block-cross-site-cookies2", 3902 }, 3903 { 3904 value: Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN.toString(), 3905 l10nId: "sitedata-option-block-unvisited", 3906 }, 3907 { 3908 value: Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN.toString(), 3909 l10nId: "sitedata-option-block-all-cross-site-cookies", 3910 }, 3911 { 3912 value: Ci.nsICookieService.BEHAVIOR_REJECT.toString(), 3913 l10nId: "sitedata-option-block-all", 3914 }, 3915 ], 3916 }, 3917 ], 3918 }, 3919 { 3920 id: "etpCustomTrackingProtectionEnabled", 3921 l10nId: "preferences-etp-custom-tracking-protection-enabled", 3922 control: "moz-toggle", 3923 items: [ 3924 { 3925 id: "etpCustomTrackingProtectionEnabledContext", 3926 l10nId: 3927 "preferences-etp-custom-tracking-protection-enabled-context", 3928 control: "moz-select", 3929 options: [ 3930 { 3931 value: "all", 3932 l10nId: 3933 "content-blocking-tracking-protection-option-all-windows", 3934 }, 3935 { 3936 value: "pbmOnly", 3937 l10nId: "content-blocking-option-private", 3938 }, 3939 ], 3940 }, 3941 ], 3942 }, 3943 { 3944 id: "etpCustomCryptominingProtectionEnabled", 3945 l10nId: "preferences-etp-custom-crypto-mining-protection-enabled", 3946 control: "moz-toggle", 3947 }, 3948 { 3949 id: "etpCustomKnownFingerprintingProtectionEnabled", 3950 l10nId: 3951 "preferences-etp-custom-known-fingerprinting-protection-enabled", 3952 control: "moz-toggle", 3953 }, 3954 { 3955 id: "etpCustomSuspectFingerprintingProtectionEnabled", 3956 l10nId: 3957 "preferences-etp-custom-suspect-fingerprinting-protection-enabled", 3958 control: "moz-toggle", 3959 items: [ 3960 { 3961 id: "etpCustomSuspectFingerprintingProtectionEnabledContext", 3962 l10nId: 3963 "preferences-etp-custom-suspect-fingerprinting-protection-enabled-context", 3964 control: "moz-select", 3965 options: [ 3966 { 3967 value: "all", 3968 l10nId: 3969 "content-blocking-tracking-protection-option-all-windows", 3970 }, 3971 { 3972 value: "pbmOnly", 3973 l10nId: "content-blocking-option-private", 3974 }, 3975 ], 3976 }, 3977 ], 3978 }, 3979 ], 3980 }, 3981 manageAddresses: { 3982 items: [ 3983 { 3984 id: "add-address-button", 3985 control: "moz-button", 3986 l10nId: "autofill-addresses-add-button", 3987 }, 3988 { 3989 id: "addresses-list", 3990 control: "moz-box-group", 3991 controlAttrs: { 3992 type: "list", 3993 }, 3994 }, 3995 ], 3996 }, 3997 sync: { 3998 inProgress: true, 3999 l10nId: "sync-group-label", 4000 headingLevel: 2, 4001 items: [ 4002 { 4003 id: "syncNoFxaSignIn", 4004 l10nId: "sync-signedout-account-signin-4", 4005 control: "moz-box-link", 4006 iconSrc: "chrome://global/skin/icons/warning.svg", 4007 controlAttrs: { 4008 id: "noFxaSignIn", 4009 }, 4010 }, 4011 { 4012 id: "syncConfigured", 4013 control: "moz-box-group", 4014 items: [ 4015 { 4016 id: "syncStatus", 4017 l10nId: "prefs-syncing-on-2", 4018 control: "moz-box-item", 4019 iconSrc: "chrome://global/skin/icons/check-filled.svg", 4020 items: [ 4021 { 4022 id: "syncNow", 4023 control: "moz-button", 4024 l10nId: "prefs-sync-now-button-2", 4025 slot: "actions", 4026 }, 4027 { 4028 id: "syncing", 4029 control: "moz-button", 4030 l10nId: "prefs-syncing-button-2", 4031 slot: "actions", 4032 }, 4033 ], 4034 }, 4035 { 4036 id: "syncEnginesList", 4037 control: "sync-engines-list", 4038 }, 4039 { 4040 id: "syncChangeOptions", 4041 control: "moz-box-button", 4042 l10nId: "sync-manage-options-2", 4043 }, 4044 ], 4045 }, 4046 { 4047 id: "syncNotConfigured", 4048 l10nId: "prefs-syncing-off-2", 4049 control: "moz-box-item", 4050 iconSrc: "chrome://global/skin/icons/warning.svg", 4051 items: [ 4052 { 4053 id: "syncSetup", 4054 control: "moz-button", 4055 l10nId: "prefs-sync-turn-on-syncing-2", 4056 slot: "actions", 4057 }, 4058 ], 4059 }, 4060 { 4061 id: "fxaDeviceNameSection", 4062 l10nId: "sync-device-name-header-2", 4063 control: "moz-fieldset", 4064 controlAttrs: { 4065 ".headingLevel": 3, 4066 }, 4067 items: [ 4068 { 4069 id: "fxaDeviceNameGroup", 4070 control: "moz-box-group", 4071 items: [ 4072 { 4073 id: "fxaDeviceName", 4074 control: "sync-device-name", 4075 }, 4076 { 4077 id: "fxaConnectAnotherDevice", 4078 l10nId: "sync-connect-another-device-2", 4079 control: "moz-box-link", 4080 iconSrc: "chrome://browser/skin/device-phone.svg", 4081 controlAttrs: { 4082 id: "connect-another-device", 4083 href: "https://accounts.firefox.com/pair", 4084 }, 4085 }, 4086 ], 4087 }, 4088 ], 4089 }, 4090 ], 4091 }, 4092 account: { 4093 inProgress: true, 4094 l10nId: "account-group-label", 4095 headingLevel: 2, 4096 items: [ 4097 { 4098 id: "noFxaAccountGroup", 4099 control: "moz-box-group", 4100 items: [ 4101 { 4102 id: "noFxaAccount", 4103 control: "placeholder-message", 4104 l10nId: "account-placeholder", 4105 controlAttrs: { 4106 imagesrc: "chrome://global/skin/illustrations/security-error.svg", 4107 }, 4108 }, 4109 { 4110 id: "noFxaSignIn", 4111 control: "moz-box-link", 4112 l10nId: "sync-signedout-account-short", 4113 }, 4114 ], 4115 }, 4116 { 4117 id: "fxaSignedInGroup", 4118 control: "moz-box-group", 4119 items: [ 4120 { 4121 id: "fxaLoginVerified", 4122 control: "moz-box-item", 4123 l10nId: "sync-account-signed-in", 4124 l10nArgs: { email: "" }, 4125 iconSrc: "chrome://browser/skin/fxa/avatar-color.svg", 4126 controlAttrs: { 4127 layout: "large-icon", 4128 }, 4129 }, 4130 { 4131 id: "verifiedManage", 4132 control: "moz-box-link", 4133 l10nId: "sync-manage-account2", 4134 controlAttrs: { 4135 href: "https://accounts.firefox.com/settings", 4136 }, 4137 }, 4138 { 4139 id: "fxaUnlinkButton", 4140 control: "moz-box-button", 4141 l10nId: "sync-sign-out2", 4142 }, 4143 ], 4144 }, 4145 { 4146 id: "fxaUnverifiedGroup", 4147 control: "moz-box-group", 4148 items: [ 4149 { 4150 id: "fxaLoginUnverified", 4151 control: "placeholder-message", 4152 l10nId: "sync-signedin-unverified2", 4153 l10nArgs: { email: "" }, 4154 controlAttrs: { 4155 imagesrc: "chrome://global/skin/illustrations/security-error.svg", 4156 }, 4157 }, 4158 { 4159 id: "verifyFxaAccount", 4160 control: "moz-box-link", 4161 l10nId: "sync-verify-account", 4162 }, 4163 { 4164 id: "unverifiedUnlinkFxaAccount", 4165 control: "moz-box-button", 4166 l10nId: "sync-remove-account", 4167 }, 4168 ], 4169 }, 4170 { 4171 id: "fxaLoginRejectedGroup", 4172 control: "moz-box-group", 4173 items: [ 4174 { 4175 id: "fxaLoginRejected", 4176 control: "placeholder-message", 4177 l10nId: "sync-signedin-login-failure2", 4178 l10nArgs: { email: "" }, 4179 controlAttrs: { 4180 imagesrc: "chrome://global/skin/illustrations/security-error.svg", 4181 }, 4182 }, 4183 { 4184 id: "rejectReSignIn", 4185 control: "moz-box-link", 4186 l10nId: "sync-sign-in", 4187 }, 4188 { 4189 id: "rejectUnlinkFxaAccount", 4190 control: "moz-box-button", 4191 l10nId: "sync-remove-account", 4192 }, 4193 ], 4194 }, 4195 ], 4196 }, 4197 translationsAutomaticTranslation: { 4198 inProgress: true, 4199 headingLevel: 2, 4200 l10nId: "settings-translations-subpage-automatic-translation-header", 4201 items: [ 4202 { 4203 id: "translationsAlwaysTranslateLanguagesGroup", 4204 control: "moz-box-group", 4205 controlAttrs: { 4206 type: "list", 4207 }, 4208 items: [ 4209 { 4210 id: "translationsAlwaysTranslateLanguagesRow", 4211 l10nId: "settings-translations-subpage-always-translate-header", 4212 control: "moz-box-item", 4213 slot: "header", 4214 controlAttrs: { 4215 class: "box-header-bold", 4216 }, 4217 items: [ 4218 { 4219 id: "translationsAlwaysTranslateLanguagesSelect", 4220 slot: "actions", 4221 control: "moz-select", 4222 options: [ 4223 { 4224 value: "", 4225 l10nId: 4226 "settings-translations-subpage-language-select-option", 4227 }, 4228 ], 4229 }, 4230 { 4231 id: "translationsAlwaysTranslateLanguagesButton", 4232 l10nId: "settings-translations-subpage-language-add-button", 4233 control: "moz-button", 4234 slot: "actions", 4235 controlAttrs: { 4236 type: "icon", 4237 iconsrc: "chrome://global/skin/icons/plus.svg", 4238 }, 4239 }, 4240 ], 4241 }, 4242 { 4243 id: "translationsAlwaysTranslateLanguagesNoneRow", 4244 l10nId: "settings-translations-subpage-no-languages-added", 4245 control: "moz-box-item", 4246 controlAttrs: { 4247 class: "description-deemphasized", 4248 }, 4249 }, 4250 ], 4251 }, 4252 { 4253 id: "translationsNeverTranslateLanguagesGroup", 4254 control: "moz-box-group", 4255 controlAttrs: { 4256 type: "list", 4257 }, 4258 items: [ 4259 { 4260 id: "translationsNeverTranslateLanguagesRow", 4261 l10nId: "settings-translations-subpage-never-translate-header", 4262 control: "moz-box-item", 4263 slot: "header", 4264 controlAttrs: { 4265 class: "box-header-bold", 4266 }, 4267 items: [ 4268 { 4269 id: "translationsNeverTranslateLanguagesSelect", 4270 slot: "actions", 4271 control: "moz-select", 4272 options: [ 4273 { 4274 value: "", 4275 l10nId: 4276 "settings-translations-subpage-language-select-option", 4277 }, 4278 ], 4279 }, 4280 { 4281 id: "translationsNeverTranslateLanguagesButton", 4282 l10nId: "settings-translations-subpage-language-add-button", 4283 control: "moz-button", 4284 slot: "actions", 4285 controlAttrs: { 4286 type: "icon", 4287 iconsrc: "chrome://global/skin/icons/plus.svg", 4288 }, 4289 }, 4290 ], 4291 }, 4292 { 4293 id: "translationsNeverTranslateLanguagesNoneRow", 4294 l10nId: "settings-translations-subpage-no-languages-added", 4295 control: "moz-box-item", 4296 controlAttrs: { 4297 class: "description-deemphasized", 4298 }, 4299 }, 4300 ], 4301 }, 4302 { 4303 id: "translationsNeverTranslateSitesGroup", 4304 control: "moz-box-group", 4305 controlAttrs: { 4306 type: "list", 4307 }, 4308 items: [ 4309 { 4310 id: "translationsNeverTranslateSitesRow", 4311 l10nId: 4312 "settings-translations-subpage-never-translate-sites-header", 4313 control: "moz-box-item", 4314 controlAttrs: { 4315 class: "box-header-bold", 4316 ".description": createNeverTranslateSitesDescription(), 4317 }, 4318 }, 4319 { 4320 id: "translationsNeverTranslateSitesNoneRow", 4321 l10nId: "settings-translations-subpage-no-sites-added", 4322 control: "moz-box-item", 4323 controlAttrs: { 4324 class: "description-deemphasized", 4325 }, 4326 }, 4327 ], 4328 }, 4329 ], 4330 }, 4331 translationsDownloadLanguages: { 4332 inProgress: true, 4333 headingLevel: 2, 4334 l10nId: "settings-translations-subpage-speed-up-translation-header", 4335 items: [ 4336 { 4337 id: "translationsDownloadLanguagesGroup", 4338 control: "moz-box-group", 4339 controlAttrs: { 4340 type: "list", 4341 }, 4342 items: [ 4343 { 4344 id: "translationsDownloadLanguagesRow", 4345 l10nId: "settings-translations-subpage-download-languages-header", 4346 control: "moz-box-item", 4347 slot: "header", 4348 controlAttrs: { 4349 class: "box-header-bold", 4350 }, 4351 items: [ 4352 { 4353 id: "translationsDownloadLanguagesSelect", 4354 slot: "actions", 4355 control: "moz-select", 4356 options: [ 4357 { 4358 value: "", 4359 l10nId: 4360 "settings-translations-subpage-download-languages-select-option", 4361 }, 4362 ], 4363 }, 4364 { 4365 id: "translationsDownloadLanguagesButton", 4366 l10nId: 4367 "settings-translations-subpage-download-languages-button", 4368 control: "moz-button", 4369 slot: "actions", 4370 controlAttrs: { 4371 type: "icon", 4372 iconsrc: "chrome://browser/skin/downloads/downloads.svg", 4373 }, 4374 }, 4375 ], 4376 }, 4377 { 4378 id: "translationsDownloadLanguagesNoneRow", 4379 l10nId: "settings-translations-subpage-no-languages-downloaded", 4380 control: "moz-box-item", 4381 controlAttrs: { 4382 class: "description-deemphasized", 4383 }, 4384 }, 4385 ], 4386 }, 4387 ], 4388 }, 4389 firefoxSuggest: { 4390 id: "locationBarGroup", 4391 items: [ 4392 { 4393 id: "locationBarGroupHeader", 4394 l10nId: "addressbar-header-1", 4395 supportPage: "firefox-suggest", 4396 control: "moz-fieldset", 4397 controlAttrs: { 4398 headinglevel: 2, 4399 }, 4400 items: [ 4401 { 4402 id: "historySuggestion", 4403 l10nId: "addressbar-locbar-history-option", 4404 }, 4405 { 4406 id: "bookmarkSuggestion", 4407 l10nId: "addressbar-locbar-bookmarks-option", 4408 }, 4409 { 4410 id: "clipboardSuggestion", 4411 l10nId: "addressbar-locbar-clipboard-option", 4412 }, 4413 { 4414 id: "openpageSuggestion", 4415 l10nId: "addressbar-locbar-openpage-option", 4416 }, 4417 { 4418 id: "topSitesSuggestion", 4419 l10nId: "addressbar-locbar-shortcuts-option", 4420 }, 4421 { 4422 id: "enableRecentSearches", 4423 l10nId: "addressbar-locbar-showrecentsearches-option-2", 4424 }, 4425 { 4426 id: "enginesSuggestion", 4427 l10nId: "addressbar-locbar-engines-option-1", 4428 }, 4429 { 4430 id: "enableQuickActions", 4431 l10nId: "addressbar-locbar-quickactions-option", 4432 supportPage: "quick-actions-firefox-search-bar", 4433 }, 4434 { 4435 id: "firefoxSuggestAll", 4436 l10nId: "addressbar-locbar-suggest-all-option-2", 4437 items: [ 4438 { 4439 id: "firefoxSuggestSponsored", 4440 l10nId: "addressbar-locbar-suggest-sponsored-option-2", 4441 }, 4442 { 4443 id: "firefoxSuggestOnlineEnabledToggle", 4444 l10nId: "addressbar-firefox-suggest-online", 4445 supportPage: "firefox-suggest", 4446 subcategory: "w_what-is-firefox-suggest", 4447 }, 4448 ], 4449 }, 4450 { 4451 id: "dismissedSuggestionsDescription", 4452 l10nId: "addressbar-dismissed-suggestions-label-2", 4453 control: "moz-fieldset", 4454 controlAttrs: { 4455 headinglevel: 3, 4456 }, 4457 items: [ 4458 { 4459 id: "restoreDismissedSuggestions", 4460 l10nId: "addressbar-restore-dismissed-suggestions-button-2", 4461 control: "moz-button", 4462 iconSrc: 4463 "chrome://global/skin/icons/arrow-counterclockwise-16.svg", 4464 }, 4465 ], 4466 }, 4467 ], 4468 }, 4469 ], 4470 }, 4471 }); 4472 4473 /** 4474 * @param {string} id - ID of {@link SettingGroup} custom element. 4475 */ 4476 function initSettingGroup(id) { 4477 /** @type {SettingGroup} */ 4478 let group = document.querySelector(`setting-group[groupid=${id}]`); 4479 const config = SettingGroupManager.get(id); 4480 if (group && config) { 4481 if (config.inProgress && !srdSectionEnabled(id)) { 4482 group.remove(); 4483 return; 4484 } 4485 4486 let legacySections = document.querySelectorAll(`[data-srd-groupid=${id}]`); 4487 for (let section of legacySections) { 4488 section.hidden = true; 4489 section.removeAttribute("data-category"); 4490 section.setAttribute("data-hidden-from-search", "true"); 4491 } 4492 group.config = config; 4493 group.getSetting = Preferences.getSetting.bind(Preferences); 4494 } 4495 } 4496 4497 ChromeUtils.defineLazyGetter(this, "gIsPackagedApp", () => { 4498 return Services.sysinfo.getProperty("isPackagedApp"); 4499 }); 4500 4501 // A promise that resolves when the list of application handlers is loaded. 4502 // We store this in a global so tests can await it. 4503 var promiseLoadHandlersList; 4504 4505 // Load the preferences string bundle for other locales with fallbacks. 4506 function getBundleForLocales(newLocales) { 4507 let locales = Array.from( 4508 new Set([ 4509 ...newLocales, 4510 ...Services.locale.requestedLocales, 4511 Services.locale.lastFallbackLocale, 4512 ]) 4513 ); 4514 return new Localization( 4515 ["browser/preferences/preferences.ftl", "branding/brand.ftl"], 4516 false, 4517 undefined, 4518 locales 4519 ); 4520 } 4521 4522 var gNodeToObjectMap = new WeakMap(); 4523 4524 var gMainPane = { 4525 // The set of types the app knows how to handle. A hash of HandlerInfoWrapper 4526 // objects, indexed by type. 4527 _handledTypes: {}, 4528 4529 // The list of types we can show, sorted by the sort column/direction. 4530 // An array of HandlerInfoWrapper objects. We build this list when we first 4531 // load the data and then rebuild it when users change a pref that affects 4532 // what types we can show or change the sort column/direction. 4533 // Note: this isn't necessarily the list of types we *will* show; if the user 4534 // provides a filter string, we'll only show the subset of types in this list 4535 // that match that string. 4536 _visibleTypes: [], 4537 4538 // browser.startup.page values 4539 STARTUP_PREF_BLANK: 0, 4540 STARTUP_PREF_HOMEPAGE: 1, 4541 STARTUP_PREF_RESTORE_SESSION: 3, 4542 4543 // Convenience & Performance Shortcuts 4544 4545 get _list() { 4546 delete this._list; 4547 return (this._list = document.getElementById("handlersView")); 4548 }, 4549 4550 get _filter() { 4551 delete this._filter; 4552 return (this._filter = document.getElementById("filter")); 4553 }, 4554 4555 /** 4556 * Initialization of gMainPane. 4557 */ 4558 init() { 4559 /** 4560 * @param {string} aId 4561 * @param {string} aEventType 4562 * @param {(ev: Event) => void} aCallback 4563 */ 4564 function setEventListener(aId, aEventType, aCallback) { 4565 document 4566 .getElementById(aId) 4567 .addEventListener(aEventType, aCallback.bind(gMainPane)); 4568 } 4569 4570 this.displayUseSystemLocale(); 4571 this.updateProxySettingsUI(); 4572 initializeProxyUI(gMainPane); 4573 4574 if (Services.prefs.getBoolPref("intl.multilingual.enabled")) { 4575 gMainPane.initPrimaryBrowserLanguageUI(); 4576 } 4577 4578 gMainPane.initTranslations(); 4579 4580 // Initialize settings groups from the config object. 4581 initSettingGroup("appearance"); 4582 initSettingGroup("downloads"); 4583 initSettingGroup("drm"); 4584 initSettingGroup("contrast"); 4585 initSettingGroup("browsing"); 4586 initSettingGroup("zoom"); 4587 initSettingGroup("support"); 4588 initSettingGroup("translations"); 4589 initSettingGroup("performance"); 4590 initSettingGroup("startup"); 4591 initSettingGroup("importBrowserData"); 4592 initSettingGroup("networkProxy"); 4593 initSettingGroup("tabs"); 4594 initSettingGroup("profiles"); 4595 initSettingGroup("profilePane"); 4596 4597 setEventListener("manageBrowserLanguagesButton", "command", function () { 4598 gMainPane.showBrowserLanguagesSubDialog({ search: false }); 4599 }); 4600 if (AppConstants.MOZ_UPDATER) { 4601 // These elements are only compiled in when the updater is enabled 4602 setEventListener("checkForUpdatesButton", "command", function () { 4603 gAppUpdater.checkForUpdates(); 4604 }); 4605 setEventListener("downloadAndInstallButton", "command", function () { 4606 gAppUpdater.startDownload(); 4607 }); 4608 setEventListener("updateButton", "command", function () { 4609 gAppUpdater.buttonRestartAfterDownload(); 4610 }); 4611 setEventListener("checkForUpdatesButton2", "command", function () { 4612 gAppUpdater.checkForUpdates(); 4613 }); 4614 setEventListener("checkForUpdatesButton3", "command", function () { 4615 gAppUpdater.checkForUpdates(); 4616 }); 4617 setEventListener("checkForUpdatesButton4", "command", function () { 4618 gAppUpdater.checkForUpdates(); 4619 }); 4620 } 4621 4622 // setEventListener("chooseLanguage", "command", gMainPane.showLanguages); 4623 { 4624 const spoofEnglish = document.getElementById("spoofEnglish"); 4625 const kPrefSpoofEnglish = "privacy.spoof_english"; 4626 const preference = Preferences.add({ 4627 id: kPrefSpoofEnglish, 4628 type: "int", 4629 }); 4630 const spoofEnglishChanged = () => { 4631 spoofEnglish.checked = preference.value == 2; 4632 }; 4633 spoofEnglishChanged(); 4634 preference.on("change", spoofEnglishChanged); 4635 setEventListener("spoofEnglish", "command", () => { 4636 preference.value = spoofEnglish.checked ? 2 : 1; 4637 }); 4638 } 4639 // TODO (Bug 1817084) Remove this code when we disable the extension 4640 setEventListener( 4641 "fxtranslateButton", 4642 "command", 4643 gMainPane.showTranslationExceptions 4644 ); 4645 Preferences.get("font.language.group").on( 4646 "change", 4647 gMainPane._rebuildFonts.bind(gMainPane) 4648 ); 4649 setEventListener("advancedFonts", "command", gMainPane.configureFonts); 4650 4651 document 4652 .getElementById("browserLayoutShowSidebar") 4653 .addEventListener( 4654 "command", 4655 gMainPane.onShowSidebarCommand.bind(gMainPane), 4656 { capture: true } 4657 ); 4658 4659 document 4660 .getElementById("migrationWizardDialog") 4661 .addEventListener("MigrationWizard:Close", function (e) { 4662 e.currentTarget.close(); 4663 }); 4664 4665 // Initializes the fonts dropdowns displayed in this pane. 4666 this._rebuildFonts(); 4667 4668 // Firefox Translations settings panel 4669 // TODO (Bug 1817084) Remove this code when we disable the extension 4670 const fxtranslationsDisabledPrefName = "extensions.translations.disabled"; 4671 if (!Services.prefs.getBoolPref(fxtranslationsDisabledPrefName, true)) { 4672 let fxtranslationRow = document.getElementById("fxtranslationsBox"); 4673 fxtranslationRow.hidden = false; 4674 } 4675 4676 // Initialize the Firefox Updates section. 4677 let version = AppConstants.BASE_BROWSER_VERSION; 4678 4679 // Base Browser and derivatives: do not include the build ID in our alphas, 4680 // since they are not actually related to the build date. 4681 4682 // Append "(32-bit)" or "(64-bit)" build architecture to the version number: 4683 let bundle = Services.strings.createBundle( 4684 "chrome://browser/locale/browser.properties" 4685 ); 4686 let archResource = Services.appinfo.is64Bit 4687 ? "aboutDialog.architecture.sixtyFourBit" 4688 : "aboutDialog.architecture.thirtyTwoBit"; 4689 let arch = bundle.GetStringFromName(archResource); 4690 version += ` (${arch})`; 4691 4692 document.l10n.setAttributes( 4693 document.getElementById("updateAppInfo"), 4694 "update-application-version", 4695 { version } 4696 ); 4697 4698 // Show a release notes link if we have a URL. 4699 let relNotesLink = document.getElementById("releasenotes"); 4700 let relNotesPrefType = Services.prefs.getPrefType("app.releaseNotesURL"); 4701 if (relNotesPrefType != Services.prefs.PREF_INVALID) { 4702 let relNotesURL = Services.urlFormatter.formatURLPref( 4703 "app.releaseNotesURL" 4704 ); 4705 if (relNotesURL != "about:blank") { 4706 relNotesLink.href = relNotesURL; 4707 relNotesLink.hidden = false; 4708 } 4709 } 4710 4711 let defaults = Services.prefs.getDefaultBranch(null); 4712 let distroId = defaults.getCharPref("distribution.id", ""); 4713 if (distroId) { 4714 let distroString = distroId; 4715 4716 let distroVersion = defaults.getCharPref("distribution.version", ""); 4717 if (distroVersion) { 4718 distroString += " - " + distroVersion; 4719 } 4720 4721 let distroIdField = document.getElementById("distributionId"); 4722 distroIdField.value = distroString; 4723 distroIdField.hidden = false; 4724 4725 let distroAbout = defaults.getStringPref("distribution.about", ""); 4726 if (distroAbout) { 4727 let distroField = document.getElementById("distribution"); 4728 distroField.value = distroAbout; 4729 distroField.hidden = false; 4730 } 4731 } 4732 4733 if (AppConstants.MOZ_UPDATER) { 4734 gAppUpdater = new appUpdater(); 4735 setEventListener("showUpdateHistory", "command", gMainPane.showUpdates); 4736 4737 let updateDisabled = 4738 Services.policies && !Services.policies.isAllowed("appUpdate"); 4739 4740 if (gIsPackagedApp) { 4741 // When we're running inside an app package, there's no point in 4742 // displaying any update content here, and it would get confusing if we 4743 // did, because our updater is not enabled. 4744 // We can't rely on the hidden attribute for the toplevel elements, 4745 // because of the pane hiding/showing code interfering. 4746 document 4747 .getElementById("updatesCategory") 4748 .setAttribute("style", "display: none !important"); 4749 document 4750 .getElementById("updateApp") 4751 .setAttribute("style", "display: none !important"); 4752 } else if ( 4753 updateDisabled || 4754 UpdateUtils.appUpdateAutoSettingIsLocked() || 4755 gApplicationUpdateService.manualUpdateOnly 4756 ) { 4757 document.getElementById("updateAllowDescription").hidden = true; 4758 document.getElementById("updateSettingsContainer").hidden = true; 4759 } else { 4760 // Start with no option selected since we are still reading the value 4761 document.getElementById("autoDesktop").removeAttribute("selected"); 4762 document.getElementById("manualDesktop").removeAttribute("selected"); 4763 // Start reading the correct value from the disk 4764 this.readUpdateAutoPref(); 4765 setEventListener("updateRadioGroup", "command", event => { 4766 if (event.target.id == "backgroundUpdate") { 4767 this.writeBackgroundUpdatePref(); 4768 } else { 4769 this.writeUpdateAutoPref(); 4770 } 4771 }); 4772 if (this.isBackgroundUpdateUIAvailable()) { 4773 document.getElementById("backgroundUpdate").hidden = false; 4774 // Start reading the background update pref's value from the disk. 4775 this.readBackgroundUpdatePref(); 4776 } 4777 } 4778 4779 if (AppConstants.platform == "win") { 4780 // On Windows, the Application Update setting is an installation- 4781 // specific preference, not a profile-specific one. Show a warning to 4782 // inform users of this. 4783 let updateContainer = document.getElementById( 4784 "updateSettingsContainer" 4785 ); 4786 updateContainer.classList.add("updateSettingCrossUserWarningContainer"); 4787 document.getElementById("updateSettingCrossUserWarningDesc").hidden = 4788 false; 4789 } 4790 } 4791 4792 // Initilize Application section. 4793 4794 // Observe preferences that influence what we display so we can rebuild 4795 // the view when they change. 4796 Services.obs.addObserver(this, AUTO_UPDATE_CHANGED_TOPIC); 4797 Services.obs.addObserver(this, BACKGROUND_UPDATE_CHANGED_TOPIC); 4798 4799 setEventListener("filter", "MozInputSearch:search", gMainPane.filter); 4800 setEventListener("typeColumn", "click", gMainPane.sort); 4801 setEventListener("actionColumn", "click", gMainPane.sort); 4802 4803 // Listen for window unload so we can remove our preference observers. 4804 window.addEventListener("unload", this); 4805 4806 // Figure out how we should be sorting the list. We persist sort settings 4807 // across sessions, so we can't assume the default sort column/direction. 4808 // XXX should we be using the XUL sort service instead? 4809 if (document.getElementById("actionColumn").hasAttribute("sortDirection")) { 4810 this._sortColumn = document.getElementById("actionColumn"); 4811 // The typeColumn element always has a sortDirection attribute, 4812 // either because it was persisted or because the default value 4813 // from the xul file was used. If we are sorting on the other 4814 // column, we should remove it. 4815 document.getElementById("typeColumn").removeAttribute("sortDirection"); 4816 } else { 4817 this._sortColumn = document.getElementById("typeColumn"); 4818 } 4819 4820 gLetterboxingPrefs.init(); 4821 4822 // Notify observers that the UI is now ready 4823 Services.obs.notifyObservers(window, "main-pane-loaded"); 4824 4825 Preferences.addSyncFromPrefListener( 4826 document.getElementById("defaultFont"), 4827 element => FontBuilder.readFontSelection(element) 4828 ); 4829 Preferences.addSyncFromPrefListener( 4830 document.getElementById("checkSpelling"), 4831 () => this.readCheckSpelling() 4832 ); 4833 Preferences.addSyncToPrefListener( 4834 document.getElementById("checkSpelling"), 4835 () => this.writeCheckSpelling() 4836 ); 4837 this.setInitialized(); 4838 }, 4839 4840 preInit() { 4841 promiseLoadHandlersList = new Promise((resolve, reject) => { 4842 // Load the data and build the list of handlers for applications pane. 4843 // By doing this after pageshow, we ensure it doesn't delay painting 4844 // of the preferences page. 4845 window.addEventListener( 4846 "pageshow", 4847 async () => { 4848 await this.initialized; 4849 try { 4850 this._initListEventHandlers(); 4851 this._loadData(); 4852 await this._rebuildVisibleTypes(); 4853 await this._rebuildView(); 4854 await this._sortListView(); 4855 resolve(); 4856 } catch (ex) { 4857 reject(ex); 4858 } 4859 }, 4860 { once: true } 4861 ); 4862 }); 4863 }, 4864 4865 handleSubcategory(subcategory) { 4866 if (Services.policies && !Services.policies.isAllowed("profileImport")) { 4867 return false; 4868 } 4869 if (subcategory == "migrate") { 4870 this.showMigrationWizardDialog(); 4871 return true; 4872 } 4873 4874 if (subcategory == "migrate-autoclose") { 4875 this.showMigrationWizardDialog({ closeTabWhenDone: true }); 4876 } 4877 4878 return false; 4879 }, 4880 4881 /** 4882 * Handle toggling the "Show sidebar" checkbox to allow SidebarController to know the 4883 * origin of this change. 4884 */ 4885 onShowSidebarCommand(event) { 4886 // Note: We useCapture so while the checkbox' checked property is already updated, 4887 // the pref value has not yet been changed 4888 const willEnable = event.target.checked; 4889 if (willEnable) { 4890 window.browsingContext.topChromeWindow.SidebarController?.enabledViaSettings( 4891 true 4892 ); 4893 } 4894 }, 4895 4896 // CONTAINERS 4897 4898 /* 4899 * preferences: 4900 * 4901 * privacy.userContext.enabled 4902 * - true if containers is enabled 4903 */ 4904 4905 async onGetStarted() { 4906 if (!AppConstants.MOZ_DEV_EDITION) { 4907 return; 4908 } 4909 const win = Services.wm.getMostRecentWindow("navigator:browser"); 4910 if (!win) { 4911 return; 4912 } 4913 const user = await fxAccounts.getSignedInUser(); 4914 if (user) { 4915 // We have a user, open Sync preferences in the same tab 4916 win.openTrustedLinkIn("about:preferences#sync", "current"); 4917 return; 4918 } 4919 if (!(await FxAccounts.canConnectAccount())) { 4920 return; 4921 } 4922 let url = 4923 await FxAccounts.config.promiseConnectAccountURI("dev-edition-setup"); 4924 let accountsTab = win.gBrowser.addWebTab(url); 4925 win.gBrowser.selectedTab = accountsTab; 4926 }, 4927 4928 // HOME PAGE 4929 /* 4930 * Preferences: 4931 * 4932 * browser.startup.page 4933 * - what page(s) to show when the user starts the application, as an integer: 4934 * 4935 * 0: a blank page (DEPRECATED - this can be set via browser.startup.homepage) 4936 * 1: the home page (as set by the browser.startup.homepage pref) 4937 * 2: the last page the user visited (DEPRECATED) 4938 * 3: windows and tabs from the last session (a.k.a. session restore) 4939 * 4940 * The deprecated option is not exposed in UI; however, if the user has it 4941 * selected and doesn't change the UI for this preference, the deprecated 4942 * option is preserved. 4943 */ 4944 4945 /** 4946 * Utility function to enable/disable the button specified by aButtonID based 4947 * on the value of the Boolean preference specified by aPreferenceID. 4948 */ 4949 updateButtons(aButtonID, aPreferenceID) { 4950 var button = document.getElementById(aButtonID); 4951 var preference = Preferences.get(aPreferenceID); 4952 button.disabled = !preference.value; 4953 return undefined; 4954 }, 4955 4956 /** 4957 * Initialize the translations view. 4958 */ 4959 async initTranslations() { 4960 if (!Services.prefs.getBoolPref("browser.translations.enable")) { 4961 return; 4962 } 4963 4964 /** 4965 * Which phase a language download is in. 4966 * 4967 * @typedef {"downloaded" | "loading" | "uninstalled"} DownloadPhase 4968 */ 4969 4970 // Immediately show the group so that the async load of the component does 4971 // not cause the layout to jump. The group will be empty initially. 4972 document.getElementById("translationsGroup").hidden = false; 4973 4974 class TranslationsState { 4975 /** 4976 * The fully initialized state. 4977 * 4978 * @param {object} supportedLanguages 4979 * @param {Array<{ langTag: string, displayName: string}>} languageList 4980 * @param {Map<string, DownloadPhase>} downloadPhases 4981 */ 4982 constructor(supportedLanguages, languageList, downloadPhases) { 4983 this.supportedLanguages = supportedLanguages; 4984 this.languageList = languageList; 4985 this.downloadPhases = downloadPhases; 4986 } 4987 4988 /** 4989 * Handles all of the async initialization logic. 4990 */ 4991 static async create() { 4992 const supportedLanguages = 4993 await TranslationsParent.getSupportedLanguages(); 4994 const languageList = 4995 TranslationsParent.getLanguageList(supportedLanguages); 4996 const downloadPhases = 4997 await TranslationsState.createDownloadPhases(languageList); 4998 4999 if (supportedLanguages.languagePairs.length === 0) { 5000 throw new Error( 5001 "The supported languages list was empty. RemoteSettings may not be available at the moment." 5002 ); 5003 } 5004 5005 return new TranslationsState( 5006 supportedLanguages, 5007 languageList, 5008 downloadPhases 5009 ); 5010 } 5011 5012 /** 5013 * Determine the download phase of each language file. 5014 * 5015 * @param {Array<{ langTag: string, displayName: string}>} languageList 5016 * @returns {Promise<Map<string, DownloadPhase>>} Map the language tag to whether it is downloaded. 5017 */ 5018 static async createDownloadPhases(languageList) { 5019 const downloadPhases = new Map(); 5020 for (const { langTag } of languageList) { 5021 downloadPhases.set( 5022 langTag, 5023 (await TranslationsParent.hasAllFilesForLanguage(langTag)) 5024 ? "downloaded" 5025 : "uninstalled" 5026 ); 5027 } 5028 return downloadPhases; 5029 } 5030 } 5031 5032 class TranslationsView { 5033 /** @type {Map<string, XULButton>} */ 5034 deleteButtons = new Map(); 5035 /** @type {Map<string, XULButton>} */ 5036 downloadButtons = new Map(); 5037 5038 /** 5039 * @param {TranslationsState} state 5040 */ 5041 constructor(state) { 5042 this.state = state; 5043 this.elements = { 5044 settingsButton: document.getElementById( 5045 "translations-manage-settings-button" 5046 ), 5047 installList: document.getElementById( 5048 "translations-manage-install-list" 5049 ), 5050 installAll: document.getElementById( 5051 "translations-manage-install-all" 5052 ), 5053 deleteAll: document.getElementById("translations-manage-delete-all"), 5054 error: document.getElementById("translations-manage-error"), 5055 }; 5056 this.setup(); 5057 } 5058 5059 setup() { 5060 this.buildLanguageList(); 5061 5062 this.elements.settingsButton.addEventListener( 5063 "command", 5064 gMainPane.showTranslationsSettings 5065 ); 5066 this.elements.installAll.addEventListener( 5067 "command", 5068 this.handleInstallAll 5069 ); 5070 this.elements.deleteAll.addEventListener( 5071 "command", 5072 this.handleDeleteAll 5073 ); 5074 5075 Services.obs.addObserver(this, "intl:app-locales-changed"); 5076 } 5077 5078 destroy() { 5079 Services.obs.removeObserver(this, "intl:app-locales-changed"); 5080 } 5081 5082 handleInstallAll = async () => { 5083 this.hideError(); 5084 this.disableButtons(true); 5085 try { 5086 await TranslationsParent.downloadAllFiles(); 5087 this.markAllDownloadPhases("downloaded"); 5088 } catch (error) { 5089 TranslationsView.showError( 5090 "translations-manage-error-download", 5091 error 5092 ); 5093 await this.reloadDownloadPhases(); 5094 this.updateAllButtons(); 5095 } 5096 this.disableButtons(false); 5097 }; 5098 5099 handleDeleteAll = async () => { 5100 this.hideError(); 5101 this.disableButtons(true); 5102 try { 5103 await TranslationsParent.deleteAllLanguageFiles(); 5104 this.markAllDownloadPhases("uninstalled"); 5105 } catch (error) { 5106 TranslationsView.showError("translations-manage-error-remove", error); 5107 // The download phases are invalidated with the error and must be reloaded. 5108 await this.reloadDownloadPhases(); 5109 console.error(error); 5110 } 5111 this.disableButtons(false); 5112 }; 5113 5114 /** 5115 * @param {string} langTag 5116 * @returns {Function} 5117 */ 5118 getDownloadButtonHandler(langTag) { 5119 return async () => { 5120 this.hideError(); 5121 this.updateDownloadPhase(langTag, "loading"); 5122 try { 5123 await TranslationsParent.downloadLanguageFiles(langTag); 5124 this.updateDownloadPhase(langTag, "downloaded"); 5125 } catch (error) { 5126 TranslationsView.showError( 5127 "translations-manage-error-download", 5128 error 5129 ); 5130 this.updateDownloadPhase(langTag, "uninstalled"); 5131 } 5132 }; 5133 } 5134 5135 /** 5136 * @param {string} langTag 5137 * @returns {Function} 5138 */ 5139 getDeleteButtonHandler(langTag) { 5140 return async () => { 5141 this.hideError(); 5142 this.updateDownloadPhase(langTag, "loading"); 5143 try { 5144 await TranslationsParent.deleteLanguageFiles(langTag); 5145 this.updateDownloadPhase(langTag, "uninstalled"); 5146 } catch (error) { 5147 TranslationsView.showError( 5148 "translations-manage-error-remove", 5149 error 5150 ); 5151 // The download phases are invalidated with the error and must be reloaded. 5152 await this.reloadDownloadPhases(); 5153 } 5154 }; 5155 } 5156 5157 buildLanguageList() { 5158 const listFragment = document.createDocumentFragment(); 5159 5160 for (const { langTag, displayName } of this.state.languageList) { 5161 const hboxRow = document.createXULElement("hbox"); 5162 hboxRow.classList.add("translations-manage-language"); 5163 hboxRow.setAttribute("data-lang-tag", langTag); 5164 5165 const languageLabel = document.createXULElement("label"); 5166 languageLabel.textContent = displayName; // The display name is already localized. 5167 5168 const downloadButton = document.createXULElement("button"); 5169 const deleteButton = document.createXULElement("button"); 5170 5171 downloadButton.addEventListener( 5172 "command", 5173 this.getDownloadButtonHandler(langTag) 5174 ); 5175 deleteButton.addEventListener( 5176 "command", 5177 this.getDeleteButtonHandler(langTag) 5178 ); 5179 5180 document.l10n.setAttributes( 5181 downloadButton, 5182 "translations-manage-language-download-button" 5183 ); 5184 document.l10n.setAttributes( 5185 deleteButton, 5186 "translations-manage-language-remove-button" 5187 ); 5188 5189 downloadButton.hidden = true; 5190 deleteButton.hidden = true; 5191 5192 this.deleteButtons.set(langTag, deleteButton); 5193 this.downloadButtons.set(langTag, downloadButton); 5194 5195 hboxRow.appendChild(languageLabel); 5196 hboxRow.appendChild(downloadButton); 5197 hboxRow.appendChild(deleteButton); 5198 listFragment.appendChild(hboxRow); 5199 } 5200 this.updateAllButtons(); 5201 this.elements.installList.appendChild(listFragment); 5202 } 5203 5204 /** 5205 * Update the DownloadPhase for a single langTag. 5206 * 5207 * @param {string} langTag 5208 * @param {DownloadPhase} downloadPhase 5209 */ 5210 updateDownloadPhase(langTag, downloadPhase) { 5211 this.state.downloadPhases.set(langTag, downloadPhase); 5212 this.updateButton(langTag, downloadPhase); 5213 this.updateHeaderButtons(); 5214 } 5215 5216 /** 5217 * Recreates the download map when the state is invalidated. 5218 */ 5219 async reloadDownloadPhases() { 5220 this.state.downloadPhases = 5221 await TranslationsState.createDownloadPhases(this.state.languageList); 5222 this.updateAllButtons(); 5223 } 5224 5225 /** 5226 * Set all the downloads. 5227 * 5228 * @param {DownloadPhase} downloadPhase 5229 */ 5230 markAllDownloadPhases(downloadPhase) { 5231 const { downloadPhases } = this.state; 5232 for (const key of downloadPhases.keys()) { 5233 downloadPhases.set(key, downloadPhase); 5234 } 5235 this.updateAllButtons(); 5236 } 5237 5238 /** 5239 * If all languages are downloaded, or no languages are downloaded then 5240 * the visibility of the buttons need to change. 5241 */ 5242 updateHeaderButtons() { 5243 let allDownloaded = true; 5244 let allUninstalled = true; 5245 for (const downloadPhase of this.state.downloadPhases.values()) { 5246 if (downloadPhase === "loading") { 5247 // Don't count loading towards this calculation. 5248 continue; 5249 } 5250 allDownloaded &&= downloadPhase === "downloaded"; 5251 allUninstalled &&= downloadPhase === "uninstalled"; 5252 } 5253 5254 this.elements.installAll.hidden = allDownloaded; 5255 this.elements.deleteAll.hidden = allUninstalled; 5256 } 5257 5258 /** 5259 * Update the buttons according to their download state. 5260 */ 5261 updateAllButtons() { 5262 this.updateHeaderButtons(); 5263 for (const [langTag, downloadPhase] of this.state.downloadPhases) { 5264 this.updateButton(langTag, downloadPhase); 5265 } 5266 } 5267 5268 /** 5269 * @param {string} langTag 5270 * @param {DownloadPhase} downloadPhase 5271 */ 5272 updateButton(langTag, downloadPhase) { 5273 const downloadButton = this.downloadButtons.get(langTag); 5274 const deleteButton = this.deleteButtons.get(langTag); 5275 switch (downloadPhase) { 5276 case "downloaded": 5277 downloadButton.hidden = true; 5278 deleteButton.hidden = false; 5279 downloadButton.removeAttribute("disabled"); 5280 break; 5281 case "uninstalled": 5282 downloadButton.hidden = false; 5283 deleteButton.hidden = true; 5284 downloadButton.removeAttribute("disabled"); 5285 break; 5286 case "loading": 5287 downloadButton.hidden = false; 5288 deleteButton.hidden = true; 5289 downloadButton.setAttribute("disabled", "true"); 5290 break; 5291 } 5292 } 5293 5294 /** 5295 * @param {boolean} isDisabled 5296 */ 5297 disableButtons(isDisabled) { 5298 this.elements.installAll.disabled = isDisabled; 5299 this.elements.deleteAll.disabled = isDisabled; 5300 for (const button of this.downloadButtons.values()) { 5301 button.disabled = isDisabled; 5302 } 5303 for (const button of this.deleteButtons.values()) { 5304 button.disabled = isDisabled; 5305 } 5306 } 5307 5308 /** 5309 * This method is static in case an error happens during the creation of the 5310 * TranslationsState. 5311 * 5312 * @param {string} l10nId 5313 * @param {Error} error 5314 */ 5315 static showError(l10nId, error) { 5316 console.error(error); 5317 const errorMessage = document.getElementById( 5318 "translations-manage-error" 5319 ); 5320 errorMessage.hidden = false; 5321 document.l10n.setAttributes(errorMessage, l10nId); 5322 } 5323 5324 hideError() { 5325 this.elements.error.hidden = true; 5326 } 5327 5328 observe(_subject, topic, _data) { 5329 if (topic === "intl:app-locales-changed") { 5330 this.refreshLanguageListDisplay(); 5331 } 5332 } 5333 5334 refreshLanguageListDisplay() { 5335 try { 5336 const languageDisplayNames = 5337 TranslationsParent.createLanguageDisplayNames(); 5338 5339 for (const row of this.elements.installList.children) { 5340 const rowLangTag = row.getAttribute("data-lang-tag"); 5341 if (!rowLangTag) { 5342 continue; 5343 } 5344 5345 const label = row.querySelector("label"); 5346 if (label) { 5347 const newDisplayName = languageDisplayNames.of(rowLangTag); 5348 if (label.textContent !== newDisplayName) { 5349 label.textContent = newDisplayName; 5350 } 5351 } 5352 } 5353 } catch (error) { 5354 console.error(error); 5355 } 5356 } 5357 } 5358 5359 TranslationsState.create().then( 5360 state => { 5361 this._translationsView = new TranslationsView(state); 5362 }, 5363 error => { 5364 // This error can happen when a user is not connected to the internet, or 5365 // RemoteSettings is down for some reason. 5366 TranslationsView.showError("translations-manage-error-list", error); 5367 } 5368 ); 5369 }, 5370 5371 initPrimaryBrowserLanguageUI() { 5372 // This will register the "command" listener. 5373 let menulist = document.getElementById("primaryBrowserLocale"); 5374 new SelectionChangedMenulist(menulist, event => { 5375 gMainPane.onPrimaryBrowserLanguageMenuChange(event); 5376 }); 5377 5378 gMainPane.updatePrimaryBrowserLanguageUI(Services.locale.appLocaleAsBCP47); 5379 }, 5380 5381 /** 5382 * Update the available list of locales and select the locale that the user 5383 * is "selecting". This could be the currently requested locale or a locale 5384 * that the user would like to switch to after confirmation. 5385 * 5386 * @param {string} selected - The selected BCP 47 locale. 5387 */ 5388 async updatePrimaryBrowserLanguageUI(selected) { 5389 let available = await LangPackMatcher.getAvailableLocales(); 5390 let localeNames = Services.intl.getLocaleDisplayNames( 5391 undefined, 5392 available, 5393 { preferNative: true } 5394 ); 5395 let locales = available.map((code, i) => { 5396 let name = localeNames[i].replace(/\s*\(.+\)$/g, ""); 5397 if (code === "ja-JP-macos") { 5398 // Mozilla codebases handle Japanese in macOS in different ways, 5399 // sometimes they call it ja-JP-mac and sometimes they call it 5400 // ja-JP-macos. The former is translated to Japanese when specifying 5401 // preferNative to true, the latter is not. Since seeing ja-JP-macos 5402 // would be confusing anyway, we treat it as a special case. 5403 // See tor-browser#41372 and Bug 1726586. 5404 name = 5405 Services.intl.getLocaleDisplayNames(undefined, ["ja"], { 5406 preferNative: true, 5407 })[0] + " (ja)"; 5408 } else { 5409 name += ` (${code})`; 5410 } 5411 return { 5412 code, 5413 name, 5414 }; 5415 }); 5416 // tor-browser#42335: Sort language codes independently from the locale, 5417 // so do not use localeCompare. 5418 locales.sort((a, b) => a.code > b.code); 5419 5420 let fragment = document.createDocumentFragment(); 5421 for (let { code, name } of locales) { 5422 let menuitem = document.createXULElement("menuitem"); 5423 menuitem.setAttribute("value", code); 5424 menuitem.setAttribute("label", name); 5425 fragment.appendChild(menuitem); 5426 } 5427 5428 // Add an option to search for more languages if downloading is supported. 5429 if (Services.prefs.getBoolPref("intl.multilingual.downloadEnabled")) { 5430 let menuitem = document.createXULElement("menuitem"); 5431 menuitem.id = "primaryBrowserLocaleSearch"; 5432 menuitem.setAttribute( 5433 "label", 5434 await document.l10n.formatValue("browser-languages-search") 5435 ); 5436 menuitem.setAttribute("value", "search"); 5437 fragment.appendChild(menuitem); 5438 } 5439 5440 let menulist = document.getElementById("primaryBrowserLocale"); 5441 let menupopup = menulist.querySelector("menupopup"); 5442 menupopup.textContent = ""; 5443 menupopup.appendChild(fragment); 5444 menulist.value = selected; 5445 5446 document.getElementById("browserLanguagesBox").hidden = false; 5447 }, 5448 5449 /* Show the confirmation message bar to allow a restart into the new locales. */ 5450 async showConfirmLanguageChangeMessageBar(locales) { 5451 let messageBar = document.getElementById("confirmBrowserLanguage"); 5452 5453 // Get the bundle for the new locale. 5454 let newBundle = getBundleForLocales(locales); 5455 5456 // Find the messages and labels. 5457 let messages = await Promise.all( 5458 [newBundle, document.l10n].map(async bundle => 5459 bundle.formatValue("confirm-browser-language-change-description") 5460 ) 5461 ); 5462 let buttonLabels = await Promise.all( 5463 [newBundle, document.l10n].map(async bundle => 5464 bundle.formatValue("confirm-browser-language-change-button") 5465 ) 5466 ); 5467 5468 // If both the message and label are the same, just include one row. 5469 if (messages[0] == messages[1] && buttonLabels[0] == buttonLabels[1]) { 5470 messages.pop(); 5471 buttonLabels.pop(); 5472 } 5473 5474 let contentContainer = messageBar.querySelector( 5475 ".message-bar-content-container" 5476 ); 5477 contentContainer.textContent = ""; 5478 5479 for (let i = 0; i < messages.length; i++) { 5480 let messageContainer = document.createXULElement("hbox"); 5481 messageContainer.classList.add("message-bar-content"); 5482 messageContainer.style.flex = "1 50%"; 5483 messageContainer.setAttribute("align", "center"); 5484 5485 let description = document.createXULElement("description"); 5486 description.classList.add("message-bar-description"); 5487 5488 if (i == 0 && Services.intl.getScriptDirection(locales[0]) === "rtl") { 5489 description.classList.add("rtl-locale"); 5490 } 5491 description.setAttribute("flex", "1"); 5492 description.textContent = messages[i]; 5493 messageContainer.appendChild(description); 5494 5495 let button = document.createXULElement("button"); 5496 button.addEventListener( 5497 "command", 5498 gMainPane.confirmBrowserLanguageChange 5499 ); 5500 button.classList.add("message-bar-button"); 5501 button.setAttribute("locales", locales.join(",")); 5502 button.setAttribute("label", buttonLabels[i]); 5503 messageContainer.appendChild(button); 5504 5505 contentContainer.appendChild(messageContainer); 5506 } 5507 5508 messageBar.hidden = false; 5509 gMainPane.selectedLocalesForRestart = locales; 5510 }, 5511 5512 hideConfirmLanguageChangeMessageBar() { 5513 let messageBar = document.getElementById("confirmBrowserLanguage"); 5514 messageBar.hidden = true; 5515 let contentContainer = messageBar.querySelector( 5516 ".message-bar-content-container" 5517 ); 5518 contentContainer.textContent = ""; 5519 gMainPane.requestingLocales = null; 5520 }, 5521 5522 /* Confirm the locale change and restart the browser in the new locale. */ 5523 confirmBrowserLanguageChange(event) { 5524 let localesString = (event.target.getAttribute("locales") || "").trim(); 5525 if (!localesString || !localesString.length) { 5526 return; 5527 } 5528 let locales = localesString.split(","); 5529 Services.locale.requestedLocales = locales; 5530 5531 // Record the change in telemetry before we restart. 5532 gMainPane.recordBrowserLanguagesTelemetry("apply"); 5533 5534 // Restart with the new locale. 5535 let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( 5536 Ci.nsISupportsPRBool 5537 ); 5538 Services.obs.notifyObservers( 5539 cancelQuit, 5540 "quit-application-requested", 5541 "restart" 5542 ); 5543 if (!cancelQuit.data) { 5544 Services.startup.quit( 5545 Services.startup.eAttemptQuit | Services.startup.eRestart 5546 ); 5547 } 5548 }, 5549 5550 /* Show or hide the confirm change message bar based on the new locale. */ 5551 onPrimaryBrowserLanguageMenuChange(event) { 5552 let locale = event.target.value; 5553 5554 if (locale == "search") { 5555 gMainPane.showBrowserLanguagesSubDialog({ search: true }); 5556 return; 5557 } else if (locale == Services.locale.appLocaleAsBCP47) { 5558 this.hideConfirmLanguageChangeMessageBar(); 5559 return; 5560 } 5561 5562 let newLocales = Array.from( 5563 new Set([locale, ...Services.locale.requestedLocales]).values() 5564 ); 5565 5566 gMainPane.recordBrowserLanguagesTelemetry("reorder"); 5567 5568 switch (gMainPane.getLanguageSwitchTransitionType(newLocales)) { 5569 case "requires-restart": 5570 // Prepare to change the locales, as they were different. 5571 gMainPane.showConfirmLanguageChangeMessageBar(newLocales); 5572 gMainPane.updatePrimaryBrowserLanguageUI(newLocales[0]); 5573 break; 5574 case "live-reload": 5575 Services.locale.requestedLocales = newLocales; 5576 gMainPane.updatePrimaryBrowserLanguageUI( 5577 Services.locale.appLocaleAsBCP47 5578 ); 5579 gMainPane.hideConfirmLanguageChangeMessageBar(); 5580 break; 5581 case "locales-match": 5582 // They matched, so we can reset the UI. 5583 gMainPane.updatePrimaryBrowserLanguageUI( 5584 Services.locale.appLocaleAsBCP47 5585 ); 5586 gMainPane.hideConfirmLanguageChangeMessageBar(); 5587 break; 5588 default: 5589 throw new Error("Unhandled transition type."); 5590 } 5591 }, 5592 5593 /** 5594 * Shows a subdialog containing the profile selector page. 5595 */ 5596 manageProfiles() { 5597 SelectableProfileService.maybeSetupDataStore().then(() => { 5598 gSubDialog.open("about:profilemanager"); 5599 }); 5600 }, 5601 5602 /** 5603 * Shows a dialog in which the preferred language for web content may be set. 5604 */ 5605 showLanguages() { 5606 gSubDialog.open( 5607 "chrome://browser/content/preferences/dialogs/languages.xhtml" 5608 ); 5609 }, 5610 5611 recordBrowserLanguagesTelemetry(method, value = null) { 5612 Glean.intlUiBrowserLanguage[method + "Main"].record( 5613 value ? { value } : undefined 5614 ); 5615 }, 5616 5617 /** 5618 * Open the browser languages sub dialog in either the normal mode, or search mode. 5619 * The search mode is only available from the menu to change the primary browser 5620 * language. 5621 * 5622 * @param {{ search: boolean }} search 5623 */ 5624 showBrowserLanguagesSubDialog({ search }) { 5625 // Record the telemetry event with an id to associate related actions. 5626 let telemetryId = parseInt( 5627 Services.telemetry.msSinceProcessStart(), 5628 10 5629 ).toString(); 5630 let method = search ? "search" : "manage"; 5631 gMainPane.recordBrowserLanguagesTelemetry(method, telemetryId); 5632 5633 let opts = { 5634 selectedLocalesForRestart: gMainPane.selectedLocalesForRestart, 5635 search, 5636 telemetryId, 5637 }; 5638 gSubDialog.open( 5639 "chrome://browser/content/preferences/dialogs/browserLanguages.xhtml", 5640 { closingCallback: this.browserLanguagesClosed }, 5641 opts 5642 ); 5643 }, 5644 5645 /** 5646 * Determine the transition strategy for switching the locale based on prefs 5647 * and the switched locales. 5648 * 5649 * @param {Array<string>} newLocales - List of BCP 47 locale identifiers. 5650 * @returns {"locales-match" | "requires-restart" | "live-reload"} 5651 */ 5652 getLanguageSwitchTransitionType(newLocales) { 5653 const { appLocalesAsBCP47 } = Services.locale; 5654 if (appLocalesAsBCP47.join(",") === newLocales.join(",")) { 5655 // The selected locales match, the order matters. 5656 return "locales-match"; 5657 } 5658 5659 if (Services.prefs.getBoolPref("intl.multilingual.liveReload")) { 5660 if ( 5661 Services.intl.getScriptDirection(newLocales[0]) !== 5662 Services.intl.getScriptDirection(appLocalesAsBCP47[0]) && 5663 !Services.prefs.getBoolPref("intl.multilingual.liveReloadBidirectional") 5664 ) { 5665 // Bug 1750852: The directionality of the text changed, which requires a restart 5666 // until the quality of the switch can be improved. 5667 return "requires-restart"; 5668 } 5669 5670 return "live-reload"; 5671 } 5672 5673 return "requires-restart"; 5674 }, 5675 5676 /* Show or hide the confirm change message bar based on the updated ordering. */ 5677 browserLanguagesClosed() { 5678 // When the subdialog is closed, settings are stored on gBrowserLanguagesDialog. 5679 // The next time the dialog is opened, a new gBrowserLanguagesDialog is created. 5680 let { selected } = this.gBrowserLanguagesDialog; 5681 5682 this.gBrowserLanguagesDialog.recordTelemetry( 5683 selected ? "accept" : "cancel" 5684 ); 5685 5686 if (!selected) { 5687 // No locales were selected. Cancel the operation. 5688 return; 5689 } 5690 5691 // Track how often locale fallback order is changed. 5692 // Drop the first locale and filter to only include the overlapping set 5693 const prevLocales = Services.locale.requestedLocales.filter( 5694 lc => selected.indexOf(lc) > 0 5695 ); 5696 const newLocales = selected.filter( 5697 (lc, i) => i > 0 && prevLocales.includes(lc) 5698 ); 5699 if (prevLocales.some((lc, i) => newLocales[i] != lc)) { 5700 this.gBrowserLanguagesDialog.recordTelemetry("setFallback"); 5701 } 5702 5703 switch (gMainPane.getLanguageSwitchTransitionType(selected)) { 5704 case "requires-restart": 5705 gMainPane.showConfirmLanguageChangeMessageBar(selected); 5706 gMainPane.updatePrimaryBrowserLanguageUI(selected[0]); 5707 break; 5708 case "live-reload": 5709 Services.locale.requestedLocales = selected; 5710 5711 gMainPane.updatePrimaryBrowserLanguageUI( 5712 Services.locale.appLocaleAsBCP47 5713 ); 5714 gMainPane.hideConfirmLanguageChangeMessageBar(); 5715 break; 5716 case "locales-match": 5717 // They matched, so we can reset the UI. 5718 gMainPane.updatePrimaryBrowserLanguageUI( 5719 Services.locale.appLocaleAsBCP47 5720 ); 5721 gMainPane.hideConfirmLanguageChangeMessageBar(); 5722 break; 5723 default: 5724 throw new Error("Unhandled transition type."); 5725 } 5726 }, 5727 5728 displayUseSystemLocale() { 5729 let appLocale = Services.locale.appLocaleAsBCP47; 5730 let regionalPrefsLocales = Services.locale.regionalPrefsLocales; 5731 if (!regionalPrefsLocales.length) { 5732 return; 5733 } 5734 let systemLocale = regionalPrefsLocales[0]; 5735 let localeDisplayname = Services.intl.getLocaleDisplayNames( 5736 undefined, 5737 [systemLocale], 5738 { preferNative: true } 5739 ); 5740 if (!localeDisplayname.length) { 5741 return; 5742 } 5743 let localeName = localeDisplayname[0]; 5744 if (appLocale.split("-u-")[0] != systemLocale.split("-u-")[0]) { 5745 let checkbox = document.getElementById("useSystemLocale"); 5746 document.l10n.setAttributes(checkbox, "use-system-locale", { 5747 localeName, 5748 }); 5749 checkbox.hidden = false; 5750 } 5751 }, 5752 5753 /** 5754 * Displays the translation exceptions dialog where specific site and language 5755 * translation preferences can be set. 5756 */ 5757 // TODO (Bug 1817084) Remove this code when we disable the extension 5758 showTranslationExceptions() { 5759 gSubDialog.open( 5760 "chrome://browser/content/preferences/dialogs/translationExceptions.xhtml" 5761 ); 5762 }, 5763 5764 showTranslationsSettings() { 5765 gSubDialog.open( 5766 "chrome://browser/content/preferences/dialogs/translations.xhtml" 5767 ); 5768 }, 5769 5770 /** 5771 * Displays the fonts dialog, where web page font names and sizes can be 5772 * configured. 5773 */ 5774 configureFonts() { 5775 gSubDialog.open( 5776 "chrome://browser/content/preferences/dialogs/fonts.xhtml", 5777 { features: "resizable=no" } 5778 ); 5779 }, 5780 5781 // NETWORK 5782 /** 5783 * Displays a dialog in which proxy settings may be changed. 5784 */ 5785 showConnections() { 5786 gSubDialog.open( 5787 "chrome://browser/content/preferences/dialogs/connection.xhtml", 5788 { closingCallback: this.updateProxySettingsUI.bind(this) } 5789 ); 5790 }, 5791 5792 // Update the UI to show the proper description depending on whether an 5793 // extension is in control or not. 5794 async updateProxySettingsUI() { 5795 let controllingExtension = await getControllingExtension( 5796 PREF_SETTING_TYPE, 5797 PROXY_KEY 5798 ); 5799 let description = document.getElementById("connectionSettingsDescription"); 5800 5801 if (controllingExtension) { 5802 setControllingExtensionDescription( 5803 description, 5804 controllingExtension, 5805 "proxy.settings" 5806 ); 5807 } else { 5808 setControllingExtensionDescription( 5809 description, 5810 null, 5811 "network-proxy-connection-description" 5812 ); 5813 } 5814 }, 5815 5816 // FONTS 5817 5818 /** 5819 * Populates the default font list in UI. 5820 */ 5821 _rebuildFonts() { 5822 var langGroup = Services.locale.fontLanguageGroup; 5823 var isSerif = this._readDefaultFontTypeForLanguage(langGroup) == "serif"; 5824 this._selectDefaultLanguageGroup(langGroup, isSerif); 5825 }, 5826 5827 /** 5828 * Returns the type of the current default font for the language denoted by 5829 * aLanguageGroup. 5830 */ 5831 _readDefaultFontTypeForLanguage(aLanguageGroup) { 5832 const kDefaultFontType = "font.default.%LANG%"; 5833 var defaultFontTypePref = kDefaultFontType.replace( 5834 /%LANG%/, 5835 aLanguageGroup 5836 ); 5837 var preference = Preferences.get(defaultFontTypePref); 5838 if (!preference) { 5839 preference = Preferences.add({ id: defaultFontTypePref, type: "string" }); 5840 preference.on("change", gMainPane._rebuildFonts.bind(gMainPane)); 5841 } 5842 return preference.value; 5843 }, 5844 5845 _selectDefaultLanguageGroupPromise: Promise.resolve(), 5846 5847 _selectDefaultLanguageGroup(aLanguageGroup, aIsSerif) { 5848 this._selectDefaultLanguageGroupPromise = (async () => { 5849 // Avoid overlapping language group selections by awaiting the resolution 5850 // of the previous one. We do this because this function is re-entrant, 5851 // as inserting <preference> elements into the DOM sometimes triggers a call 5852 // back into this function. And since this function is also asynchronous, 5853 // that call can enter this function before the previous run has completed, 5854 // which would corrupt the font menulists. Awaiting the previous call's 5855 // resolution avoids that fate. 5856 await this._selectDefaultLanguageGroupPromise; 5857 5858 const kFontNameFmtSerif = "font.name.serif.%LANG%"; 5859 const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%"; 5860 const kFontNameListFmtSerif = "font.name-list.serif.%LANG%"; 5861 const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%"; 5862 const kFontSizeFmtVariable = "font.size.variable.%LANG%"; 5863 5864 var prefs = [ 5865 { 5866 format: aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif, 5867 type: "fontname", 5868 element: "defaultFont", 5869 fonttype: aIsSerif ? "serif" : "sans-serif", 5870 }, 5871 { 5872 format: aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif, 5873 type: "unichar", 5874 element: null, 5875 fonttype: aIsSerif ? "serif" : "sans-serif", 5876 }, 5877 { 5878 format: kFontSizeFmtVariable, 5879 type: "int", 5880 element: "defaultFontSize", 5881 fonttype: null, 5882 }, 5883 ]; 5884 for (var i = 0; i < prefs.length; ++i) { 5885 var preference = Preferences.get( 5886 prefs[i].format.replace(/%LANG%/, aLanguageGroup) 5887 ); 5888 if (!preference) { 5889 var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup); 5890 preference = Preferences.add({ id: name, type: prefs[i].type }); 5891 } 5892 5893 if (!prefs[i].element) { 5894 continue; 5895 } 5896 5897 var element = document.getElementById(prefs[i].element); 5898 if (element) { 5899 element.setAttribute("preference", preference.id); 5900 5901 if (prefs[i].fonttype) { 5902 await FontBuilder.buildFontList( 5903 aLanguageGroup, 5904 prefs[i].fonttype, 5905 element 5906 ); 5907 } 5908 5909 preference.setElementValue(element); 5910 } 5911 } 5912 })().catch(console.error); 5913 }, 5914 5915 /** 5916 * Displays the migration wizard dialog in an HTML dialog. 5917 */ 5918 async showMigrationWizardDialog({ closeTabWhenDone = false } = {}) { 5919 let migrationWizardDialog = document.getElementById( 5920 "migrationWizardDialog" 5921 ); 5922 5923 if (migrationWizardDialog.open) { 5924 return; 5925 } 5926 5927 await customElements.whenDefined("migration-wizard"); 5928 5929 // If we've been opened before, remove the old wizard and insert a 5930 // new one to put it back into its starting state. 5931 if (!migrationWizardDialog.firstElementChild) { 5932 let wizard = document.createElement("migration-wizard"); 5933 wizard.toggleAttribute("dialog-mode", true); 5934 migrationWizardDialog.appendChild(wizard); 5935 } 5936 migrationWizardDialog.firstElementChild.requestState(); 5937 5938 migrationWizardDialog.addEventListener( 5939 "close", 5940 () => { 5941 // Let others know that the wizard is closed -- potentially because of a 5942 // user action within the dialog that dispatches "MigrationWizard:Close" 5943 // but this also covers cases like hitting Escape. 5944 Services.obs.notifyObservers( 5945 migrationWizardDialog, 5946 "MigrationWizard:Closed" 5947 ); 5948 if (closeTabWhenDone) { 5949 window.close(); 5950 } 5951 }, 5952 { once: true } 5953 ); 5954 5955 migrationWizardDialog.showModal(); 5956 }, 5957 5958 /** 5959 * Stores the original value of the spellchecking preference to enable proper 5960 * restoration if unchanged (since we're mapping a tristate onto a checkbox). 5961 */ 5962 _storedSpellCheck: 0, 5963 5964 /** 5965 * Returns true if any spellchecking is enabled and false otherwise, caching 5966 * the current value to enable proper pref restoration if the checkbox is 5967 * never changed. 5968 * 5969 * layout.spellcheckDefault 5970 * - an integer: 5971 * 0 disables spellchecking 5972 * 1 enables spellchecking, but only for multiline text fields 5973 * 2 enables spellchecking for all text fields 5974 */ 5975 readCheckSpelling() { 5976 var pref = Preferences.get("layout.spellcheckDefault"); 5977 this._storedSpellCheck = pref.value; 5978 5979 return pref.value != 0; 5980 }, 5981 5982 /** 5983 * Returns the value of the spellchecking preference represented by UI, 5984 * preserving the preference's "hidden" value if the preference is 5985 * unchanged and represents a value not strictly allowed in UI. 5986 */ 5987 writeCheckSpelling() { 5988 var checkbox = document.getElementById("checkSpelling"); 5989 if (checkbox.checked) { 5990 if (this._storedSpellCheck == 2) { 5991 return 2; 5992 } 5993 return 1; 5994 } 5995 return 0; 5996 }, 5997 5998 _minUpdatePrefDisableTime: 1000, 5999 /** 6000 * Selects the correct item in the update radio group 6001 */ 6002 async readUpdateAutoPref() { 6003 if ( 6004 AppConstants.MOZ_UPDATER && 6005 (!Services.policies || Services.policies.isAllowed("appUpdate")) && 6006 !gIsPackagedApp 6007 ) { 6008 let radiogroup = document.getElementById("updateRadioGroup"); 6009 6010 radiogroup.disabled = true; 6011 let enabled = await UpdateUtils.getAppUpdateAutoEnabled(); 6012 radiogroup.value = enabled; 6013 radiogroup.disabled = false; 6014 6015 this.maybeDisableBackgroundUpdateControls(); 6016 } 6017 }, 6018 6019 /** 6020 * Writes the value of the automatic update radio group to the disk 6021 */ 6022 async writeUpdateAutoPref() { 6023 if ( 6024 AppConstants.MOZ_UPDATER && 6025 (!Services.policies || Services.policies.isAllowed("appUpdate")) && 6026 !gIsPackagedApp 6027 ) { 6028 let radiogroup = document.getElementById("updateRadioGroup"); 6029 let updateAutoValue = radiogroup.value == "true"; 6030 let _disableTimeOverPromise = new Promise(r => 6031 setTimeout(r, this._minUpdatePrefDisableTime) 6032 ); 6033 radiogroup.disabled = true; 6034 try { 6035 await UpdateUtils.setAppUpdateAutoEnabled(updateAutoValue); 6036 await _disableTimeOverPromise; 6037 radiogroup.disabled = false; 6038 } catch (error) { 6039 console.error(error); 6040 await Promise.all([ 6041 this.readUpdateAutoPref(), 6042 this.reportUpdatePrefWriteError(), 6043 ]); 6044 return; 6045 } 6046 6047 this.maybeDisableBackgroundUpdateControls(); 6048 6049 // If the value was changed to false the user should be given the option 6050 // to discard an update if there is one. 6051 if (!updateAutoValue) { 6052 await this.checkUpdateInProgress(); 6053 } 6054 // For tests: 6055 radiogroup.dispatchEvent(new CustomEvent("ProcessedUpdatePrefChange")); 6056 } 6057 }, 6058 6059 isBackgroundUpdateUIAvailable() { 6060 return ( 6061 AppConstants.MOZ_UPDATE_AGENT && 6062 // This UI controls a per-installation pref. It won't necessarily work 6063 // properly if per-installation prefs aren't supported. 6064 UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED && 6065 (!Services.policies || Services.policies.isAllowed("appUpdate")) && 6066 !gIsPackagedApp && 6067 !UpdateUtils.appUpdateSettingIsLocked("app.update.background.enabled") 6068 ); 6069 }, 6070 6071 maybeDisableBackgroundUpdateControls() { 6072 if (this.isBackgroundUpdateUIAvailable()) { 6073 let radiogroup = document.getElementById("updateRadioGroup"); 6074 let updateAutoEnabled = radiogroup.value == "true"; 6075 6076 // This control is only active if auto update is enabled. 6077 document.getElementById("backgroundUpdate").disabled = !updateAutoEnabled; 6078 } 6079 }, 6080 6081 async readBackgroundUpdatePref() { 6082 const prefName = "app.update.background.enabled"; 6083 if (this.isBackgroundUpdateUIAvailable()) { 6084 let backgroundCheckbox = document.getElementById("backgroundUpdate"); 6085 6086 // When the page first loads, the checkbox is unchecked until we finish 6087 // reading the config file from the disk. But, ideally, we don't want to 6088 // give the user the impression that this setting has somehow gotten 6089 // turned off and they need to turn it back on. We also don't want the 6090 // user interacting with the control, expecting a particular behavior, and 6091 // then have the read complete and change the control in an unexpected 6092 // way. So we disable the control while we are reading. 6093 // The only entry points for this function are page load and user 6094 // interaction with the control. By disabling the control to prevent 6095 // further user interaction, we prevent the possibility of entering this 6096 // function a second time while we are still reading. 6097 backgroundCheckbox.disabled = true; 6098 6099 // If we haven't already done this, it might result in the effective value 6100 // of the Background Update pref changing. Thus, we should do it before 6101 // we tell the user what value this pref has. 6102 await BackgroundUpdate.ensureExperimentToRolloutTransitionPerformed(); 6103 6104 let enabled = await UpdateUtils.readUpdateConfigSetting(prefName); 6105 backgroundCheckbox.checked = enabled; 6106 this.maybeDisableBackgroundUpdateControls(); 6107 } 6108 }, 6109 6110 async writeBackgroundUpdatePref() { 6111 const prefName = "app.update.background.enabled"; 6112 if (this.isBackgroundUpdateUIAvailable()) { 6113 let backgroundCheckbox = document.getElementById("backgroundUpdate"); 6114 backgroundCheckbox.disabled = true; 6115 let backgroundUpdateEnabled = backgroundCheckbox.checked; 6116 try { 6117 await UpdateUtils.writeUpdateConfigSetting( 6118 prefName, 6119 backgroundUpdateEnabled 6120 ); 6121 } catch (error) { 6122 console.error(error); 6123 await this.readBackgroundUpdatePref(); 6124 await this.reportUpdatePrefWriteError(); 6125 return; 6126 } 6127 6128 this.maybeDisableBackgroundUpdateControls(); 6129 } 6130 }, 6131 6132 async reportUpdatePrefWriteError() { 6133 let [title, message] = await document.l10n.formatValues([ 6134 { id: "update-setting-write-failure-title2" }, 6135 { 6136 id: "update-setting-write-failure-message2", 6137 args: { path: UpdateUtils.configFilePath }, 6138 }, 6139 ]); 6140 6141 // Set up the Ok Button 6142 let buttonFlags = 6143 Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_OK; 6144 Services.prompt.confirmEx( 6145 window, 6146 title, 6147 message, 6148 buttonFlags, 6149 null, 6150 null, 6151 null, 6152 null, 6153 {} 6154 ); 6155 }, 6156 6157 async checkUpdateInProgress() { 6158 const aus = Cc["@mozilla.org/updates/update-service;1"].getService( 6159 Ci.nsIApplicationUpdateService 6160 ); 6161 let um = Cc["@mozilla.org/updates/update-manager;1"].getService( 6162 Ci.nsIUpdateManager 6163 ); 6164 // We don't want to see an idle state just because the updater hasn't 6165 // initialized yet. 6166 await aus.init(); 6167 if (aus.currentState == Ci.nsIApplicationUpdateService.STATE_IDLE) { 6168 return; 6169 } 6170 6171 let [title, message, okButton, cancelButton] = 6172 await document.l10n.formatValues([ 6173 { id: "update-in-progress-title" }, 6174 { id: "update-in-progress-message" }, 6175 { id: "update-in-progress-ok-button" }, 6176 { id: "update-in-progress-cancel-button" }, 6177 ]); 6178 6179 // Continue is the cancel button which is BUTTON_POS_1 and is set as the 6180 // default so pressing escape or using a platform standard method of closing 6181 // the UI will not discard the update. 6182 let buttonFlags = 6183 Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0 + 6184 Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1 + 6185 Ci.nsIPrompt.BUTTON_POS_1_DEFAULT; 6186 6187 let rv = Services.prompt.confirmEx( 6188 window, 6189 title, 6190 message, 6191 buttonFlags, 6192 okButton, 6193 cancelButton, 6194 null, 6195 null, 6196 {} 6197 ); 6198 if (rv != 1) { 6199 await aus.stopDownload(); 6200 await um.cleanupActiveUpdates(); 6201 UpdateListener.clearPendingAndActiveNotifications(); 6202 } 6203 }, 6204 6205 /** 6206 * Displays the history of installed updates. 6207 */ 6208 showUpdates() { 6209 gSubDialog.open("chrome://mozapps/content/update/history.xhtml"); 6210 }, 6211 6212 destroy() { 6213 window.removeEventListener("unload", this); 6214 Services.obs.removeObserver(this, AUTO_UPDATE_CHANGED_TOPIC); 6215 Services.obs.removeObserver(this, BACKGROUND_UPDATE_CHANGED_TOPIC); 6216 6217 // Clean up the TranslationsView instance if it exists 6218 if (this._translationsView) { 6219 this._translationsView.destroy(); 6220 this._translationsView = null; 6221 } 6222 }, 6223 6224 // nsISupports 6225 6226 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), 6227 6228 // nsIObserver 6229 6230 async observe(aSubject, aTopic, aData) { 6231 if (aTopic == "nsPref:changed") { 6232 if (aData == PREF_CONTAINERS_EXTENSION) { 6233 return; 6234 } 6235 // Rebuild the list when there are changes to preferences that influence 6236 // whether or not to show certain entries in the list. 6237 if (!this._storingAction) { 6238 await this._rebuildView(); 6239 } 6240 } else if (aTopic == AUTO_UPDATE_CHANGED_TOPIC) { 6241 if (!AppConstants.MOZ_UPDATER) { 6242 return; 6243 } 6244 if (aData != "true" && aData != "false") { 6245 throw new Error("Invalid preference value for app.update.auto"); 6246 } 6247 document.getElementById("updateRadioGroup").value = aData; 6248 this.maybeDisableBackgroundUpdateControls(); 6249 } else if (aTopic == BACKGROUND_UPDATE_CHANGED_TOPIC) { 6250 if (!AppConstants.MOZ_UPDATE_AGENT) { 6251 return; 6252 } 6253 if (aData != "true" && aData != "false") { 6254 throw new Error( 6255 "Invalid preference value for app.update.background.enabled" 6256 ); 6257 } 6258 document.getElementById("backgroundUpdate").checked = aData == "true"; 6259 } 6260 }, 6261 6262 // EventListener 6263 6264 handleEvent(aEvent) { 6265 if (aEvent.type == "unload") { 6266 this.destroy(); 6267 if (AppConstants.MOZ_UPDATER) { 6268 onUnload(); 6269 } 6270 } 6271 }, 6272 6273 // Composed Model Construction 6274 6275 _loadData() { 6276 this._loadInternalHandlers(); 6277 this._loadApplicationHandlers(); 6278 }, 6279 6280 /** 6281 * Load higher level internal handlers so they can be turned on/off in the 6282 * applications menu. 6283 */ 6284 _loadInternalHandlers() { 6285 let internalHandlers = [new PDFHandlerInfoWrapper()]; 6286 6287 let enabledHandlers = Services.prefs 6288 .getCharPref("browser.download.viewableInternally.enabledTypes", "") 6289 .trim(); 6290 if (enabledHandlers) { 6291 for (let ext of enabledHandlers.split(",")) { 6292 internalHandlers.push( 6293 new ViewableInternallyHandlerInfoWrapper(null, ext.trim()) 6294 ); 6295 } 6296 } 6297 for (let internalHandler of internalHandlers) { 6298 if (internalHandler.enabled) { 6299 this._handledTypes[internalHandler.type] = internalHandler; 6300 } 6301 } 6302 }, 6303 6304 /** 6305 * Load the set of handlers defined by the application datastore. 6306 */ 6307 _loadApplicationHandlers() { 6308 for (let wrappedHandlerInfo of gHandlerService.enumerate()) { 6309 let type = wrappedHandlerInfo.type; 6310 6311 let handlerInfoWrapper; 6312 if (type in this._handledTypes) { 6313 handlerInfoWrapper = this._handledTypes[type]; 6314 } else { 6315 if (DownloadIntegration.shouldViewDownloadInternally(type)) { 6316 handlerInfoWrapper = new ViewableInternallyHandlerInfoWrapper(type); 6317 } else { 6318 handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo); 6319 } 6320 this._handledTypes[type] = handlerInfoWrapper; 6321 } 6322 } 6323 }, 6324 6325 // View Construction 6326 6327 selectedHandlerListItem: null, 6328 6329 _initListEventHandlers() { 6330 this._list.addEventListener("select", event => { 6331 if (event.target != this._list) { 6332 return; 6333 } 6334 6335 let handlerListItem = 6336 this._list.selectedItem && 6337 HandlerListItem.forNode(this._list.selectedItem); 6338 if (this.selectedHandlerListItem == handlerListItem) { 6339 return; 6340 } 6341 6342 if (this.selectedHandlerListItem) { 6343 this.selectedHandlerListItem.showActionsMenu = false; 6344 } 6345 this.selectedHandlerListItem = handlerListItem; 6346 if (handlerListItem) { 6347 this.rebuildActionsMenu(); 6348 handlerListItem.showActionsMenu = true; 6349 } 6350 }); 6351 }, 6352 6353 async _rebuildVisibleTypes() { 6354 this._visibleTypes = []; 6355 6356 // Map whose keys are string descriptions and values are references to the 6357 // first visible HandlerInfoWrapper that has this description. We use this 6358 // to determine whether or not to annotate descriptions with their types to 6359 // distinguish duplicate descriptions from each other. 6360 let visibleDescriptions = new Map(); 6361 for (let type in this._handledTypes) { 6362 // Yield before processing each handler info object to avoid monopolizing 6363 // the main thread, as the objects are retrieved lazily, and retrieval 6364 // can be expensive on Windows. 6365 await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); 6366 6367 let handlerInfo = this._handledTypes[type]; 6368 6369 // We couldn't find any reason to exclude the type, so include it. 6370 this._visibleTypes.push(handlerInfo); 6371 6372 let key = JSON.stringify(handlerInfo.description); 6373 let otherHandlerInfo = visibleDescriptions.get(key); 6374 if (!otherHandlerInfo) { 6375 // This is the first type with this description that we encountered 6376 // while rebuilding the _visibleTypes array this time. Make sure the 6377 // flag is reset so we won't add the type to the description. 6378 handlerInfo.disambiguateDescription = false; 6379 visibleDescriptions.set(key, handlerInfo); 6380 } else { 6381 // There is at least another type with this description. Make sure we 6382 // add the type to the description on both HandlerInfoWrapper objects. 6383 handlerInfo.disambiguateDescription = true; 6384 otherHandlerInfo.disambiguateDescription = true; 6385 } 6386 } 6387 }, 6388 6389 async _rebuildView() { 6390 let lastSelectedType = 6391 this.selectedHandlerListItem && 6392 this.selectedHandlerListItem.handlerInfoWrapper.type; 6393 this.selectedHandlerListItem = null; 6394 6395 // Clear the list of entries. 6396 this._list.textContent = ""; 6397 6398 var visibleTypes = this._visibleTypes; 6399 6400 let items = visibleTypes.map( 6401 visibleType => new HandlerListItem(visibleType) 6402 ); 6403 let itemsFragment = document.createDocumentFragment(); 6404 let lastSelectedItem; 6405 for (let item of items) { 6406 item.createNode(itemsFragment); 6407 if (item.handlerInfoWrapper.type == lastSelectedType) { 6408 lastSelectedItem = item; 6409 } 6410 } 6411 6412 for (let item of items) { 6413 item.setupNode(); 6414 this.rebuildActionsMenu(item.node, item.handlerInfoWrapper); 6415 item.refreshAction(); 6416 } 6417 6418 // If the user is filtering the list, then only show matching types. 6419 // If we filter, we need to first localize the fragment, to 6420 // be able to filter by localized values. 6421 if (this._filter.value) { 6422 await document.l10n.translateFragment(itemsFragment); 6423 6424 this._filterView(itemsFragment); 6425 6426 document.l10n.pauseObserving(); 6427 this._list.appendChild(itemsFragment); 6428 document.l10n.resumeObserving(); 6429 } else { 6430 // Otherwise we can just append the fragment and it'll 6431 // get localized via the Mutation Observer. 6432 this._list.appendChild(itemsFragment); 6433 } 6434 6435 if (lastSelectedItem) { 6436 this._list.selectedItem = lastSelectedItem.node; 6437 } 6438 }, 6439 6440 /** 6441 * Whether or not the given handler app is valid. 6442 * 6443 * @param aHandlerApp {nsIHandlerApp} the handler app in question 6444 * 6445 * @returns {boolean} whether or not it's valid 6446 */ 6447 isValidHandlerApp(aHandlerApp) { 6448 if (!aHandlerApp) { 6449 return false; 6450 } 6451 6452 if (aHandlerApp instanceof Ci.nsILocalHandlerApp) { 6453 return this._isValidHandlerExecutable(aHandlerApp.executable); 6454 } 6455 6456 if (aHandlerApp instanceof Ci.nsIWebHandlerApp) { 6457 return aHandlerApp.uriTemplate; 6458 } 6459 6460 if (aHandlerApp instanceof Ci.nsIGIOMimeApp) { 6461 return aHandlerApp.command; 6462 } 6463 if (aHandlerApp instanceof Ci.nsIGIOHandlerApp) { 6464 return aHandlerApp.id; 6465 } 6466 6467 return false; 6468 }, 6469 6470 _isValidHandlerExecutable(aExecutable) { 6471 let leafName; 6472 if (AppConstants.platform == "win") { 6473 leafName = `${AppConstants.MOZ_APP_NAME}.exe`; 6474 } else if (AppConstants.platform == "macosx") { 6475 leafName = AppConstants.MOZ_MACBUNDLE_NAME; 6476 } else { 6477 leafName = `${AppConstants.MOZ_APP_NAME}-bin`; 6478 } 6479 return ( 6480 aExecutable && 6481 aExecutable.exists() && 6482 aExecutable.isExecutable() && 6483 // XXXben - we need to compare this with the running instance executable 6484 // just don't know how to do that via script... 6485 // XXXmano TBD: can probably add this to nsIShellService 6486 aExecutable.leafName != leafName 6487 ); 6488 }, 6489 6490 /** 6491 * Rebuild the actions menu for the selected entry. Gets called by 6492 * the richlistitem constructor when an entry in the list gets selected. 6493 */ 6494 rebuildActionsMenu( 6495 typeItem = this._list.selectedItem, 6496 handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper 6497 ) { 6498 var menu = typeItem.querySelector(".actionsMenu"); 6499 var menuPopup = menu.menupopup; 6500 6501 // Clear out existing items. 6502 while (menuPopup.hasChildNodes()) { 6503 menuPopup.removeChild(menuPopup.lastChild); 6504 } 6505 6506 let internalMenuItem; 6507 // Add the "Open in Firefox" option for optional internal handlers. 6508 if ( 6509 handlerInfo instanceof InternalHandlerInfoWrapper && 6510 !handlerInfo.preventInternalViewing 6511 ) { 6512 internalMenuItem = document.createXULElement("menuitem"); 6513 internalMenuItem.setAttribute( 6514 "action", 6515 Ci.nsIHandlerInfo.handleInternally 6516 ); 6517 internalMenuItem.className = "menuitem-iconic"; 6518 document.l10n.setAttributes(internalMenuItem, "applications-open-inapp"); 6519 internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "handleInternally"); 6520 menuPopup.appendChild(internalMenuItem); 6521 } 6522 6523 var askMenuItem = document.createXULElement("menuitem"); 6524 askMenuItem.setAttribute("action", Ci.nsIHandlerInfo.alwaysAsk); 6525 askMenuItem.className = "menuitem-iconic"; 6526 document.l10n.setAttributes(askMenuItem, "applications-always-ask"); 6527 askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask"); 6528 menuPopup.appendChild(askMenuItem); 6529 6530 // Create a menu item for saving to disk. 6531 // Note: this option isn't available to protocol types, since we don't know 6532 // what it means to save a URL having a certain scheme to disk. 6533 if (handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) { 6534 var saveMenuItem = document.createXULElement("menuitem"); 6535 saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk); 6536 document.l10n.setAttributes(saveMenuItem, "applications-action-save"); 6537 saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save"); 6538 saveMenuItem.className = "menuitem-iconic"; 6539 menuPopup.appendChild(saveMenuItem); 6540 } 6541 6542 // Add a separator to distinguish these items from the helper app items 6543 // that follow them. 6544 let menuseparator = document.createXULElement("menuseparator"); 6545 menuPopup.appendChild(menuseparator); 6546 6547 // Create a menu item for the OS default application, if any. 6548 if (handlerInfo.hasDefaultHandler) { 6549 var defaultMenuItem = document.createXULElement("menuitem"); 6550 defaultMenuItem.setAttribute( 6551 "action", 6552 Ci.nsIHandlerInfo.useSystemDefault 6553 ); 6554 // If an internal option is available, don't show the application 6555 // name for the OS default to prevent two options from appearing 6556 // that may both say "Firefox". 6557 if (internalMenuItem) { 6558 document.l10n.setAttributes( 6559 defaultMenuItem, 6560 "applications-use-os-default" 6561 ); 6562 defaultMenuItem.setAttribute("image", ICON_URL_APP); 6563 } else { 6564 document.l10n.setAttributes( 6565 defaultMenuItem, 6566 "applications-use-app-default", 6567 { 6568 "app-name": handlerInfo.defaultDescription, 6569 } 6570 ); 6571 let image = handlerInfo.iconURLForSystemDefault; 6572 if (image) { 6573 defaultMenuItem.setAttribute("image", image); 6574 } 6575 } 6576 6577 menuPopup.appendChild(defaultMenuItem); 6578 } 6579 6580 // Create menu items for possible handlers. 6581 let preferredApp = handlerInfo.preferredApplicationHandler; 6582 var possibleAppMenuItems = []; 6583 for (let possibleApp of handlerInfo.possibleApplicationHandlers.enumerate()) { 6584 if (!this.isValidHandlerApp(possibleApp)) { 6585 continue; 6586 } 6587 6588 let menuItem = document.createXULElement("menuitem"); 6589 menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp); 6590 let label; 6591 if (possibleApp instanceof Ci.nsILocalHandlerApp) { 6592 label = getFileDisplayName(possibleApp.executable); 6593 } else { 6594 label = possibleApp.name; 6595 } 6596 document.l10n.setAttributes(menuItem, "applications-use-app", { 6597 "app-name": label, 6598 }); 6599 let image = this._getIconURLForHandlerApp(possibleApp); 6600 if (image) { 6601 menuItem.setAttribute("image", image); 6602 } 6603 6604 // Attach the handler app object to the menu item so we can use it 6605 // to make changes to the datastore when the user selects the item. 6606 menuItem.handlerApp = possibleApp; 6607 6608 menuPopup.appendChild(menuItem); 6609 possibleAppMenuItems.push(menuItem); 6610 } 6611 // Add gio handlers 6612 if (gGIOService) { 6613 var gioApps = gGIOService.getAppsForURIScheme(handlerInfo.type); 6614 let possibleHandlers = handlerInfo.possibleApplicationHandlers; 6615 for (let handler of gioApps.enumerate(Ci.nsIHandlerApp)) { 6616 // OS handler share the same name, it's most likely the same app, skipping... 6617 if (handler.name == handlerInfo.defaultDescription) { 6618 continue; 6619 } 6620 // Check if the handler is already in possibleHandlers 6621 let appAlreadyInHandlers = false; 6622 for (let i = possibleHandlers.length - 1; i >= 0; --i) { 6623 let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp); 6624 // nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp 6625 if (handler.equals(app)) { 6626 appAlreadyInHandlers = true; 6627 break; 6628 } 6629 } 6630 if (!appAlreadyInHandlers) { 6631 let menuItem = document.createXULElement("menuitem"); 6632 menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp); 6633 document.l10n.setAttributes(menuItem, "applications-use-app", { 6634 "app-name": handler.name, 6635 }); 6636 6637 let image = this._getIconURLForHandlerApp(handler); 6638 if (image) { 6639 menuItem.setAttribute("image", image); 6640 } 6641 6642 // Attach the handler app object to the menu item so we can use it 6643 // to make changes to the datastore when the user selects the item. 6644 menuItem.handlerApp = handler; 6645 6646 menuPopup.appendChild(menuItem); 6647 possibleAppMenuItems.push(menuItem); 6648 } 6649 } 6650 } 6651 6652 // Create a menu item for selecting a local application. 6653 let canOpenWithOtherApp = true; 6654 if (AppConstants.platform == "win") { 6655 // On Windows, selecting an application to open another application 6656 // would be meaningless so we special case executables. 6657 let executableType = Cc["@mozilla.org/mime;1"] 6658 .getService(Ci.nsIMIMEService) 6659 .getTypeFromExtension("exe"); 6660 canOpenWithOtherApp = handlerInfo.type != executableType; 6661 } 6662 if (canOpenWithOtherApp) { 6663 let menuItem = document.createXULElement("menuitem"); 6664 menuItem.className = "choose-app-item"; 6665 menuItem.addEventListener("command", function (e) { 6666 gMainPane.chooseApp(e); 6667 }); 6668 document.l10n.setAttributes(menuItem, "applications-use-other"); 6669 menuPopup.appendChild(menuItem); 6670 } 6671 6672 // Create a menu item for managing applications. 6673 if (possibleAppMenuItems.length) { 6674 let menuItem = document.createXULElement("menuseparator"); 6675 menuPopup.appendChild(menuItem); 6676 menuItem = document.createXULElement("menuitem"); 6677 menuItem.className = "manage-app-item"; 6678 menuItem.addEventListener("command", function (e) { 6679 gMainPane.manageApp(e); 6680 }); 6681 document.l10n.setAttributes(menuItem, "applications-manage-app"); 6682 menuPopup.appendChild(menuItem); 6683 } 6684 6685 // Select the item corresponding to the preferred action. If the always 6686 // ask flag is set, it overrides the preferred action. Otherwise we pick 6687 // the item identified by the preferred action (when the preferred action 6688 // is to use a helper app, we have to pick the specific helper app item). 6689 if (handlerInfo.alwaysAskBeforeHandling) { 6690 menu.selectedItem = askMenuItem; 6691 } else { 6692 // The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify 6693 // the actions the application can take with content of various types. 6694 // But since we've stopped support for plugins, there's no value 6695 // identifying the "use plugin" action, so we use this constant instead. 6696 const kActionUsePlugin = 5; 6697 6698 switch (handlerInfo.preferredAction) { 6699 case Ci.nsIHandlerInfo.handleInternally: 6700 if (internalMenuItem) { 6701 menu.selectedItem = internalMenuItem; 6702 } else { 6703 console.error("No menu item defined to set!"); 6704 } 6705 break; 6706 case Ci.nsIHandlerInfo.useSystemDefault: 6707 // We might not have a default item if we're not aware of an 6708 // OS-default handler for this type: 6709 menu.selectedItem = defaultMenuItem || askMenuItem; 6710 break; 6711 case Ci.nsIHandlerInfo.useHelperApp: 6712 if (preferredApp) { 6713 let preferredItem = possibleAppMenuItems.find(v => 6714 v.handlerApp.equals(preferredApp) 6715 ); 6716 if (preferredItem) { 6717 menu.selectedItem = preferredItem; 6718 } else { 6719 // This shouldn't happen, but let's make sure we end up with a 6720 // selected item: 6721 let possible = possibleAppMenuItems 6722 .map(v => v.handlerApp && v.handlerApp.name) 6723 .join(", "); 6724 console.error( 6725 new Error( 6726 `Preferred handler for ${handlerInfo.type} not in list of possible handlers!? (List: ${possible})` 6727 ) 6728 ); 6729 menu.selectedItem = askMenuItem; 6730 } 6731 } 6732 break; 6733 case kActionUsePlugin: 6734 // We no longer support plugins, select "ask" instead: 6735 menu.selectedItem = askMenuItem; 6736 break; 6737 case Ci.nsIHandlerInfo.saveToDisk: 6738 menu.selectedItem = saveMenuItem; 6739 break; 6740 } 6741 } 6742 }, 6743 6744 // Sorting & Filtering 6745 6746 _sortColumn: null, 6747 6748 /** 6749 * Sort the list when the user clicks on a column header. 6750 */ 6751 sort(event) { 6752 if (event.button != 0) { 6753 return; 6754 } 6755 var column = event.target; 6756 6757 // If the user clicked on a new sort column, remove the direction indicator 6758 // from the old column. 6759 if (this._sortColumn && this._sortColumn != column) { 6760 this._sortColumn.removeAttribute("sortDirection"); 6761 } 6762 6763 this._sortColumn = column; 6764 6765 // Set (or switch) the sort direction indicator. 6766 if (column.getAttribute("sortDirection") == "ascending") { 6767 column.setAttribute("sortDirection", "descending"); 6768 } else { 6769 column.setAttribute("sortDirection", "ascending"); 6770 } 6771 6772 this._sortListView(); 6773 }, 6774 6775 async _sortListView() { 6776 if (!this._sortColumn) { 6777 return; 6778 } 6779 let comp = new Services.intl.Collator(undefined, { 6780 usage: "sort", 6781 }); 6782 6783 await document.l10n.translateFragment(this._list); 6784 let items = Array.from(this._list.children); 6785 6786 let textForNode; 6787 if (this._sortColumn.getAttribute("value") === "type") { 6788 textForNode = n => n.querySelector(".typeDescription").textContent; 6789 } else { 6790 textForNode = n => n.querySelector(".actionsMenu").getAttribute("label"); 6791 } 6792 6793 let sortDir = this._sortColumn.getAttribute("sortDirection"); 6794 let multiplier = sortDir == "descending" ? -1 : 1; 6795 items.sort( 6796 (a, b) => multiplier * comp.compare(textForNode(a), textForNode(b)) 6797 ); 6798 6799 // Re-append items in the correct order: 6800 items.forEach(item => this._list.appendChild(item)); 6801 }, 6802 6803 _filterView(frag = this._list) { 6804 const filterValue = this._filter.value.toLowerCase(); 6805 for (let elem of frag.children) { 6806 const typeDescription = 6807 elem.querySelector(".typeDescription").textContent; 6808 const actionDescription = elem 6809 .querySelector(".actionDescription") 6810 .getAttribute("value"); 6811 elem.hidden = 6812 !typeDescription.toLowerCase().includes(filterValue) && 6813 !actionDescription.toLowerCase().includes(filterValue); 6814 } 6815 }, 6816 6817 /** 6818 * Filter the list when the user enters a filter term into the filter field. 6819 */ 6820 filter() { 6821 this._rebuildView(); // FIXME: Should this be await since bug 1508156? 6822 }, 6823 6824 focusFilterBox() { 6825 this._filter.focus(); 6826 this._filter.select(); 6827 }, 6828 6829 // Changes 6830 6831 // Whether or not we are currently storing the action selected by the user. 6832 // We use this to suppress notification-triggered updates to the list when 6833 // we make changes that may spawn such updates. 6834 // XXXgijs: this was definitely necessary when we changed feed preferences 6835 // from within _storeAction and its calltree. Now, it may still be 6836 // necessary, to avoid calling _rebuildView. bug 1499350 has more details. 6837 _storingAction: false, 6838 6839 onSelectAction(aActionItem) { 6840 this._storingAction = true; 6841 6842 try { 6843 this._storeAction(aActionItem); 6844 } finally { 6845 this._storingAction = false; 6846 } 6847 }, 6848 6849 _storeAction(aActionItem) { 6850 var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper; 6851 6852 let action = parseInt(aActionItem.getAttribute("action")); 6853 6854 // Set the preferred application handler. 6855 // We leave the existing preferred app in the list when we set 6856 // the preferred action to something other than useHelperApp so that 6857 // legacy datastores that don't have the preferred app in the list 6858 // of possible apps still include the preferred app in the list of apps 6859 // the user can choose to handle the type. 6860 if (action == Ci.nsIHandlerInfo.useHelperApp) { 6861 handlerInfo.preferredApplicationHandler = aActionItem.handlerApp; 6862 } 6863 6864 // Set the "always ask" flag. 6865 if (action == Ci.nsIHandlerInfo.alwaysAsk) { 6866 handlerInfo.alwaysAskBeforeHandling = true; 6867 } else { 6868 handlerInfo.alwaysAskBeforeHandling = false; 6869 } 6870 6871 // Set the preferred action. 6872 handlerInfo.preferredAction = action; 6873 6874 handlerInfo.store(); 6875 6876 // Update the action label and image to reflect the new preferred action. 6877 this.selectedHandlerListItem.refreshAction(); 6878 }, 6879 6880 manageApp(aEvent) { 6881 // Don't let the normal "on select action" handler get this event, 6882 // as we handle it specially ourselves. 6883 aEvent.stopPropagation(); 6884 6885 var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper; 6886 6887 let onComplete = () => { 6888 // Rebuild the actions menu so that we revert to the previous selection, 6889 // or "Always ask" if the previous default application has been removed 6890 this.rebuildActionsMenu(); 6891 6892 // update the richlistitem too. Will be visible when selecting another row 6893 this.selectedHandlerListItem.refreshAction(); 6894 }; 6895 6896 gSubDialog.open( 6897 "chrome://browser/content/preferences/dialogs/applicationManager.xhtml", 6898 { features: "resizable=no", closingCallback: onComplete }, 6899 handlerInfo 6900 ); 6901 }, 6902 6903 async chooseApp(aEvent) { 6904 // Don't let the normal "on select action" handler get this event, 6905 // as we handle it specially ourselves. 6906 aEvent.stopPropagation(); 6907 6908 var handlerApp; 6909 let chooseAppCallback = aHandlerApp => { 6910 // Rebuild the actions menu whether the user picked an app or canceled. 6911 // If they picked an app, we want to add the app to the menu and select it. 6912 // If they canceled, we want to go back to their previous selection. 6913 this.rebuildActionsMenu(); 6914 6915 // If the user picked a new app from the menu, select it. 6916 if (aHandlerApp) { 6917 let typeItem = this._list.selectedItem; 6918 let actionsMenu = typeItem.querySelector(".actionsMenu"); 6919 let menuItems = actionsMenu.menupopup.childNodes; 6920 for (let i = 0; i < menuItems.length; i++) { 6921 let menuItem = menuItems[i]; 6922 if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) { 6923 actionsMenu.selectedIndex = i; 6924 this.onSelectAction(menuItem); 6925 break; 6926 } 6927 } 6928 } 6929 }; 6930 6931 if (AppConstants.platform == "win") { 6932 var params = {}; 6933 var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper; 6934 6935 params.mimeInfo = handlerInfo.wrappedHandlerInfo; 6936 params.title = await document.l10n.formatValue( 6937 "applications-select-helper" 6938 ); 6939 if ("id" in handlerInfo.description) { 6940 params.description = await document.l10n.formatValue( 6941 handlerInfo.description.id, 6942 handlerInfo.description.args 6943 ); 6944 } else { 6945 params.description = handlerInfo.typeDescription.raw; 6946 } 6947 params.filename = null; 6948 params.handlerApp = null; 6949 6950 let onAppSelected = () => { 6951 if (this.isValidHandlerApp(params.handlerApp)) { 6952 handlerApp = params.handlerApp; 6953 6954 // Add the app to the type's list of possible handlers. 6955 handlerInfo.addPossibleApplicationHandler(handlerApp); 6956 } 6957 6958 chooseAppCallback(handlerApp); 6959 }; 6960 6961 gSubDialog.open( 6962 "chrome://global/content/appPicker.xhtml", 6963 { closingCallback: onAppSelected }, 6964 params 6965 ); 6966 } else { 6967 let winTitle = await document.l10n.formatValue( 6968 "applications-select-helper" 6969 ); 6970 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); 6971 let fpCallback = aResult => { 6972 if ( 6973 aResult == Ci.nsIFilePicker.returnOK && 6974 fp.file && 6975 this._isValidHandlerExecutable(fp.file) 6976 ) { 6977 handlerApp = Cc[ 6978 "@mozilla.org/uriloader/local-handler-app;1" 6979 ].createInstance(Ci.nsILocalHandlerApp); 6980 handlerApp.name = getFileDisplayName(fp.file); 6981 handlerApp.executable = fp.file; 6982 6983 // Add the app to the type's list of possible handlers. 6984 let handler = this.selectedHandlerListItem.handlerInfoWrapper; 6985 handler.addPossibleApplicationHandler(handlerApp); 6986 6987 chooseAppCallback(handlerApp); 6988 } 6989 }; 6990 6991 // Prompt the user to pick an app. If they pick one, and it's a valid 6992 // selection, then add it to the list of possible handlers. 6993 fp.init(window.browsingContext, winTitle, Ci.nsIFilePicker.modeOpen); 6994 fp.appendFilters(Ci.nsIFilePicker.filterApps); 6995 fp.open(fpCallback); 6996 } 6997 }, 6998 6999 _getIconURLForHandlerApp(aHandlerApp) { 7000 if (aHandlerApp instanceof Ci.nsILocalHandlerApp) { 7001 return this._getIconURLForFile(aHandlerApp.executable); 7002 } 7003 7004 if (aHandlerApp instanceof Ci.nsIWebHandlerApp) { 7005 return this._getIconURLForWebApp(aHandlerApp.uriTemplate); 7006 } 7007 7008 if (aHandlerApp instanceof Ci.nsIGIOHandlerApp) { 7009 return this._getIconURLForAppId(aHandlerApp.id); 7010 } 7011 7012 // We know nothing about other kinds of handler apps. 7013 return ""; 7014 }, 7015 7016 _getIconURLForAppId(aAppId) { 7017 return "moz-icon://" + aAppId + "?size=16"; 7018 }, 7019 7020 _getIconURLForFile(aFile) { 7021 var fph = Services.io 7022 .getProtocolHandler("file") 7023 .QueryInterface(Ci.nsIFileProtocolHandler); 7024 var urlSpec = fph.getURLSpecFromActualFile(aFile); 7025 7026 return "moz-icon://" + urlSpec + "?size=16"; 7027 }, 7028 7029 _getIconURLForWebApp(aWebAppURITemplate) { 7030 var uri = Services.io.newURI(aWebAppURITemplate); 7031 7032 // Unfortunately we can't use the favicon service to get the favicon, 7033 // because the service looks in the annotations table for a record with 7034 // the exact URL we give it, and users won't have such records for URLs 7035 // they don't visit, and users won't visit the web app's URL template, 7036 // they'll only visit URLs derived from that template (i.e. with %s 7037 // in the template replaced by the URL of the content being handled). 7038 7039 if ( 7040 /^https?$/.test(uri.scheme) && 7041 Services.prefs.getBoolPref("browser.chrome.site_icons") 7042 ) { 7043 // As the favicon originates from web content and is displayed in the parent process, 7044 // use the moz-remote-image: protocol to safely re-encode it. 7045 return getMozRemoteImageURL(uri.prePath + "/favicon.ico", 16); 7046 } 7047 7048 return ""; 7049 }, 7050 }; 7051 7052 gMainPane.initialized = new Promise(res => { 7053 gMainPane.setInitialized = res; 7054 }); 7055 7056 // Utilities 7057 7058 function getFileDisplayName(file) { 7059 if (AppConstants.platform == "win") { 7060 if (file instanceof Ci.nsILocalFileWin) { 7061 try { 7062 return file.getVersionInfoField("FileDescription"); 7063 } catch (e) {} 7064 } 7065 } 7066 if (AppConstants.platform == "macosx") { 7067 if (file instanceof Ci.nsILocalFileMac) { 7068 try { 7069 return file.bundleDisplayName; 7070 } catch (e) {} 7071 } 7072 } 7073 return file.leafName; 7074 } 7075 7076 function getLocalHandlerApp(aFile) { 7077 var localHandlerApp = Cc[ 7078 "@mozilla.org/uriloader/local-handler-app;1" 7079 ].createInstance(Ci.nsILocalHandlerApp); 7080 localHandlerApp.name = getFileDisplayName(aFile); 7081 localHandlerApp.executable = aFile; 7082 7083 return localHandlerApp; 7084 } 7085 7086 // eslint-disable-next-line no-undef 7087 let gHandlerListItemFragment = MozXULElement.parseXULToFragment(` 7088 <richlistitem> 7089 <hbox class="typeContainer" flex="1" align="center"> 7090 <html:img class="typeIcon" width="16" height="16" /> 7091 <label class="typeDescription" flex="1" crop="end"/> 7092 </hbox> 7093 <hbox class="actionContainer" flex="1" align="center"> 7094 <html:img class="actionIcon" width="16" height="16"/> 7095 <label class="actionDescription" flex="1" crop="end"/> 7096 </hbox> 7097 <hbox class="actionsMenuContainer" flex="1"> 7098 <menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1" aria-labelledby="actionColumn"> 7099 <menupopup/> 7100 </menulist> 7101 </hbox> 7102 </richlistitem> 7103 `); 7104 7105 /** 7106 * This is associated to <richlistitem> elements in the handlers view. 7107 */ 7108 class HandlerListItem { 7109 static forNode(node) { 7110 return gNodeToObjectMap.get(node); 7111 } 7112 7113 constructor(handlerInfoWrapper) { 7114 this.handlerInfoWrapper = handlerInfoWrapper; 7115 } 7116 7117 setOrRemoveAttributes(iterable) { 7118 for (let [selector, name, value] of iterable) { 7119 let node = selector ? this.node.querySelector(selector) : this.node; 7120 if (value) { 7121 node.setAttribute(name, value); 7122 } else { 7123 node.removeAttribute(name); 7124 } 7125 } 7126 } 7127 7128 createNode(list) { 7129 list.appendChild(document.importNode(gHandlerListItemFragment, true)); 7130 this.node = list.lastChild; 7131 gNodeToObjectMap.set(this.node, this); 7132 } 7133 7134 setupNode() { 7135 this.node 7136 .querySelector(".actionsMenu") 7137 .addEventListener("command", event => 7138 gMainPane.onSelectAction(event.originalTarget) 7139 ); 7140 7141 let typeDescription = this.handlerInfoWrapper.typeDescription; 7142 this.setOrRemoveAttributes([ 7143 [null, "type", this.handlerInfoWrapper.type], 7144 [".typeIcon", "srcset", this.handlerInfoWrapper.iconSrcSet], 7145 ]); 7146 localizeElement( 7147 this.node.querySelector(".typeDescription"), 7148 typeDescription 7149 ); 7150 this.showActionsMenu = false; 7151 } 7152 7153 refreshAction() { 7154 let { actionIconClass } = this.handlerInfoWrapper; 7155 this.setOrRemoveAttributes([ 7156 [null, APP_ICON_ATTR_NAME, actionIconClass], 7157 [ 7158 ".actionIcon", 7159 "srcset", 7160 actionIconClass ? null : this.handlerInfoWrapper.actionIconSrcset, 7161 ], 7162 ]); 7163 const selectedItem = this.node.querySelector("[selected=true]"); 7164 if (!selectedItem) { 7165 console.error("No selected item for " + this.handlerInfoWrapper.type); 7166 return; 7167 } 7168 const { id, args } = document.l10n.getAttributes(selectedItem); 7169 const messageIDs = { 7170 "applications-action-save": "applications-action-save-label", 7171 "applications-always-ask": "applications-always-ask-label", 7172 "applications-open-inapp": "applications-open-inapp-label", 7173 "applications-use-app-default": "applications-use-app-default-label", 7174 "applications-use-app": "applications-use-app-label", 7175 "applications-use-os-default": "applications-use-os-default-label", 7176 "applications-use-other": "applications-use-other-label", 7177 }; 7178 localizeElement(this.node.querySelector(".actionDescription"), { 7179 id: messageIDs[id], 7180 args, 7181 }); 7182 localizeElement(this.node.querySelector(".actionsMenu"), { id, args }); 7183 } 7184 7185 set showActionsMenu(value) { 7186 this.setOrRemoveAttributes([ 7187 [".actionContainer", "hidden", value], 7188 [".actionsMenuContainer", "hidden", !value], 7189 ]); 7190 } 7191 } 7192 7193 /** 7194 * This API facilitates dual-model of some localization APIs which 7195 * may operate on raw strings of l10n id/args pairs. 7196 * 7197 * The l10n can be: 7198 * 7199 * {raw: string} - raw strings to be used as text value of the element 7200 * {id: string} - l10n-id 7201 * {id: string, args: object} - l10n-id + l10n-args 7202 */ 7203 function localizeElement(node, l10n) { 7204 if (l10n.hasOwnProperty("raw")) { 7205 node.removeAttribute("data-l10n-id"); 7206 node.textContent = l10n.raw; 7207 } else { 7208 document.l10n.setAttributes(node, l10n.id, l10n.args); 7209 } 7210 } 7211 7212 /** 7213 * This object wraps nsIHandlerInfo with some additional functionality 7214 * the Applications prefpane needs to display and allow modification of 7215 * the list of handled types. 7216 * 7217 * We create an instance of this wrapper for each entry we might display 7218 * in the prefpane, and we compose the instances from various sources, 7219 * including the handler service. 7220 * 7221 * We don't implement all the original nsIHandlerInfo functionality, 7222 * just the stuff that the prefpane needs. 7223 */ 7224 class HandlerInfoWrapper { 7225 constructor(type, handlerInfo) { 7226 this.type = type; 7227 this.wrappedHandlerInfo = handlerInfo; 7228 this.disambiguateDescription = false; 7229 } 7230 7231 get description() { 7232 if (this.wrappedHandlerInfo.description) { 7233 return { raw: this.wrappedHandlerInfo.description }; 7234 } 7235 7236 if (this.primaryExtension) { 7237 var extension = this.primaryExtension.toUpperCase(); 7238 return { id: "applications-file-ending", args: { extension } }; 7239 } 7240 7241 return { raw: this.type }; 7242 } 7243 7244 /** 7245 * Describe, in a human-readable fashion, the type represented by the given 7246 * handler info object. Normally this is just the description, but if more 7247 * than one object presents the same description, "disambiguateDescription" 7248 * is set and we annotate the duplicate descriptions with the type itself 7249 * to help users distinguish between those types. 7250 */ 7251 get typeDescription() { 7252 if (this.disambiguateDescription) { 7253 const description = this.description; 7254 if (description.id) { 7255 // Pass through the arguments: 7256 let { args = {} } = description; 7257 args.type = this.type; 7258 return { 7259 id: description.id + "-with-type", 7260 args, 7261 }; 7262 } 7263 7264 return { 7265 id: "applications-type-description-with-type", 7266 args: { 7267 "type-description": description.raw, 7268 type: this.type, 7269 }, 7270 }; 7271 } 7272 7273 return this.description; 7274 } 7275 7276 get actionIconClass() { 7277 if (this.alwaysAskBeforeHandling) { 7278 return "ask"; 7279 } 7280 7281 switch (this.preferredAction) { 7282 case Ci.nsIHandlerInfo.saveToDisk: 7283 return "save"; 7284 7285 case Ci.nsIHandlerInfo.handleInternally: 7286 if (this instanceof InternalHandlerInfoWrapper) { 7287 return "handleInternally"; 7288 } 7289 break; 7290 } 7291 7292 return ""; 7293 } 7294 7295 get actionIconSrcset() { 7296 let icon = this.actionIcon; 7297 if (!icon || !icon.startsWith("moz-icon:")) { 7298 return icon; 7299 } 7300 // We rely on the icon already having the ?size= parameter. 7301 let srcset = []; 7302 for (let scale of [1, 2, 3]) { 7303 let scaledIcon = icon + "&scale=" + scale; 7304 srcset.push(`${scaledIcon} ${scale}x`); 7305 } 7306 return srcset.join(", "); 7307 } 7308 7309 get actionIcon() { 7310 switch (this.preferredAction) { 7311 case Ci.nsIHandlerInfo.useSystemDefault: 7312 return this.iconURLForSystemDefault; 7313 7314 case Ci.nsIHandlerInfo.useHelperApp: { 7315 let preferredApp = this.preferredApplicationHandler; 7316 if (gMainPane.isValidHandlerApp(preferredApp)) { 7317 return gMainPane._getIconURLForHandlerApp(preferredApp); 7318 } 7319 } 7320 // This should never happen, but if preferredAction is set to some weird 7321 // value, then fall back to the generic application icon. 7322 // Explicit fall-through 7323 default: 7324 return ICON_URL_APP; 7325 } 7326 } 7327 7328 get iconURLForSystemDefault() { 7329 // Handler info objects for MIME types on some OSes implement a property bag 7330 // interface from which we can get an icon for the default app, so if we're 7331 // dealing with a MIME type on one of those OSes, then try to get the icon. 7332 if ( 7333 this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo && 7334 this.wrappedHandlerInfo instanceof Ci.nsIPropertyBag 7335 ) { 7336 try { 7337 let url = this.wrappedHandlerInfo.getProperty( 7338 "defaultApplicationIconURL" 7339 ); 7340 if (url) { 7341 return url + "?size=16"; 7342 } 7343 } catch (ex) {} 7344 } 7345 7346 // If this isn't a MIME type object on an OS that supports retrieving 7347 // the icon, or if we couldn't retrieve the icon for some other reason, 7348 // then use a generic icon. 7349 return ICON_URL_APP; 7350 } 7351 7352 get preferredApplicationHandler() { 7353 return this.wrappedHandlerInfo.preferredApplicationHandler; 7354 } 7355 7356 set preferredApplicationHandler(aNewValue) { 7357 this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue; 7358 7359 // Make sure the preferred handler is in the set of possible handlers. 7360 if (aNewValue) { 7361 this.addPossibleApplicationHandler(aNewValue); 7362 } 7363 } 7364 7365 get possibleApplicationHandlers() { 7366 return this.wrappedHandlerInfo.possibleApplicationHandlers; 7367 } 7368 7369 addPossibleApplicationHandler(aNewHandler) { 7370 for (let app of this.possibleApplicationHandlers.enumerate()) { 7371 if (app.equals(aNewHandler)) { 7372 return; 7373 } 7374 } 7375 this.possibleApplicationHandlers.appendElement(aNewHandler); 7376 } 7377 7378 removePossibleApplicationHandler(aHandler) { 7379 var defaultApp = this.preferredApplicationHandler; 7380 if (defaultApp && aHandler.equals(defaultApp)) { 7381 // If the app we remove was the default app, we must make sure 7382 // it won't be used anymore 7383 this.alwaysAskBeforeHandling = true; 7384 this.preferredApplicationHandler = null; 7385 } 7386 7387 var handlers = this.possibleApplicationHandlers; 7388 for (var i = 0; i < handlers.length; ++i) { 7389 var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp); 7390 if (handler.equals(aHandler)) { 7391 handlers.removeElementAt(i); 7392 break; 7393 } 7394 } 7395 } 7396 7397 get hasDefaultHandler() { 7398 return this.wrappedHandlerInfo.hasDefaultHandler; 7399 } 7400 7401 get defaultDescription() { 7402 return this.wrappedHandlerInfo.defaultDescription; 7403 } 7404 7405 // What to do with content of this type. 7406 get preferredAction() { 7407 // If the action is to use a helper app, but we don't have a preferred 7408 // handler app, then switch to using the system default, if any; otherwise 7409 // fall back to saving to disk, which is the default action in nsMIMEInfo. 7410 // Note: "save to disk" is an invalid value for protocol info objects, 7411 // but the alwaysAskBeforeHandling getter will detect that situation 7412 // and always return true in that case to override this invalid value. 7413 if ( 7414 this.wrappedHandlerInfo.preferredAction == 7415 Ci.nsIHandlerInfo.useHelperApp && 7416 !gMainPane.isValidHandlerApp(this.preferredApplicationHandler) 7417 ) { 7418 if (this.wrappedHandlerInfo.hasDefaultHandler) { 7419 return Ci.nsIHandlerInfo.useSystemDefault; 7420 } 7421 return Ci.nsIHandlerInfo.saveToDisk; 7422 } 7423 7424 return this.wrappedHandlerInfo.preferredAction; 7425 } 7426 7427 set preferredAction(aNewValue) { 7428 this.wrappedHandlerInfo.preferredAction = aNewValue; 7429 } 7430 7431 get alwaysAskBeforeHandling() { 7432 // If this is a protocol type and the preferred action is "save to disk", 7433 // which is invalid for such types, then return true here to override that 7434 // action. This could happen when the preferred action is to use a helper 7435 // app, but the preferredApplicationHandler is invalid, and there isn't 7436 // a default handler, so the preferredAction getter returns save to disk 7437 // instead. 7438 if ( 7439 !(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) && 7440 this.preferredAction == Ci.nsIHandlerInfo.saveToDisk 7441 ) { 7442 return true; 7443 } 7444 7445 return this.wrappedHandlerInfo.alwaysAskBeforeHandling; 7446 } 7447 7448 set alwaysAskBeforeHandling(aNewValue) { 7449 this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue; 7450 } 7451 7452 // The primary file extension associated with this type, if any. 7453 get primaryExtension() { 7454 try { 7455 if ( 7456 this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo && 7457 this.wrappedHandlerInfo.primaryExtension 7458 ) { 7459 return this.wrappedHandlerInfo.primaryExtension; 7460 } 7461 } catch (ex) {} 7462 7463 return null; 7464 } 7465 7466 store() { 7467 gHandlerService.store(this.wrappedHandlerInfo); 7468 } 7469 7470 get iconSrcSet() { 7471 let srcset = []; 7472 for (let scale of [1, 2]) { 7473 let icon = this._getIcon(16, scale); 7474 if (!icon) { 7475 return null; 7476 } 7477 srcset.push(`${icon} ${scale}x`); 7478 } 7479 return srcset.join(", "); 7480 } 7481 7482 _getIcon(aSize, aScale = 1) { 7483 if (this.primaryExtension) { 7484 return `moz-icon://goat.${this.primaryExtension}?size=${aSize}&scale=${aScale}`; 7485 } 7486 7487 if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) { 7488 return `moz-icon://goat?size=${aSize}&scale=${aScale}&contentType=${this.type}`; 7489 } 7490 7491 // FIXME: consider returning some generic icon when we can't get a URL for 7492 // one (for example in the case of protocol schemes). Filed as bug 395141. 7493 return null; 7494 } 7495 } 7496 7497 /** 7498 * InternalHandlerInfoWrapper provides a basic mechanism to create an internal 7499 * mime type handler that can be enabled/disabled in the applications preference 7500 * menu. 7501 */ 7502 class InternalHandlerInfoWrapper extends HandlerInfoWrapper { 7503 constructor(mimeType, extension) { 7504 let type = gMIMEService.getFromTypeAndExtension(mimeType, extension); 7505 super(mimeType || type.type, type); 7506 } 7507 7508 // Override store so we so we can notify any code listening for registration 7509 // or unregistration of this handler. 7510 store() { 7511 super.store(); 7512 } 7513 7514 get preventInternalViewing() { 7515 return false; 7516 } 7517 7518 get enabled() { 7519 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 7520 } 7521 } 7522 7523 class PDFHandlerInfoWrapper extends InternalHandlerInfoWrapper { 7524 constructor() { 7525 super(TYPE_PDF, null); 7526 } 7527 7528 get preventInternalViewing() { 7529 return Services.prefs.getBoolPref(PREF_PDFJS_DISABLED); 7530 } 7531 7532 // PDF is always shown in the list, but the 'show internally' option is 7533 // hidden when the internal PDF viewer is disabled. 7534 get enabled() { 7535 return true; 7536 } 7537 } 7538 7539 class ViewableInternallyHandlerInfoWrapper extends InternalHandlerInfoWrapper { 7540 get enabled() { 7541 return DownloadIntegration.shouldViewDownloadInternally(this.type); 7542 } 7543 }