/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs"; import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs"; const { AppConstants } = ChromeUtils.importESModule( "resource://gre/modules/AppConstants.sys.mjs" ); /* Accessibility * accessibility.browsewithcaret - true enables keyboard navigation and selection within web pages using a visible caret, false uses normal keyboard navigation with no caret * accessibility.typeaheadfind - when set to true, typing outside text areas and input boxes will automatically start searching for what's typed within the current document; when set to false, no search action happens */ Preferences.addAll([ { id: "accessibility.browsewithcaret", type: "bool" }, { id: "accessibility.typeaheadfind", type: "bool" }, { id: "accessibility.blockautorefresh", type: "bool" }, { id: "accessibility.tabfocus", type: "int" }, { id: "browser.display.document_color_use", type: "int" }, { id: "font.language.group", type: "string" }, { id: "browser.zoom.full", type: "bool" }, { id: "general.autoScroll", type: "bool" }, { id: "general.smoothScroll", type: "bool" }, { id: "widget.gtk.overlay-scrollbars.enabled", type: "bool", inverted: true }, { id: "layout.css.always_underline_links", type: "bool" }, { id: "media.hardwaremediakeys.enabled", type: "bool" }, ]); if (AppConstants.platform === "win") { Preferences.addAll([{ id: "ui.osk.enabled", type: "bool" }]); } Preferences.addSetting({ id: "useOnScreenKeyboard", pref: AppConstants.platform == "win" ? "ui.osk.enabled" : undefined, visible: () => AppConstants.platform == "win", }); Preferences.addSetting({ id: "useCursorNavigation", pref: "accessibility.browsewithcaret", }); Preferences.addSetting( /** @type {{ _storedFullKeyboardNavigation: number } & SettingConfig} */ ({ _storedFullKeyboardNavigation: -1, id: "useFullKeyboardNavigation", pref: "accessibility.tabfocus", visible: () => AppConstants.platform == "macosx", /** * Returns true if any full keyboard nav is enabled and false otherwise, caching * the current value to enable proper pref restoration if the checkbox is * never changed. * * accessibility.tabfocus * - an integer controlling the focusability of: * 1 text controls * 2 form elements * 4 links * 7 all of the above */ get(prefVal) { this._storedFullKeyboardNavigation = prefVal; return prefVal == 7; }, /** * Returns the value of the full keyboard nav preference represented by UI, * preserving the preference's "hidden" value if the preference is * unchanged and represents a value not strictly allowed in UI. */ set(checked) { if (checked) { return 7; } if (this._storedFullKeyboardNavigation != 7) { return this._storedFullKeyboardNavigation; } return 1; }, }) ); Preferences.addSetting({ id: "alwaysUnderlineLinks", pref: "layout.css.always_underline_links", }); Preferences.addSetting({ id: "searchStartTyping", pref: "accessibility.typeaheadfind", }); Preferences.addSetting({ id: "mediaControlToggleEnabled", pref: "media.hardwaremediakeys.enabled", // For media control toggle button, we support it on Windows, macOS and // gtk-based Linux. visible: () => AppConstants.platform == "win" || AppConstants.platform == "macosx" || AppConstants.MOZ_WIDGET_GTK, }); Preferences.addSetting({ id: "useAutoScroll", pref: "general.autoScroll", }); Preferences.addSetting({ id: "useSmoothScrolling", pref: "general.smoothScroll", }); Preferences.addSetting({ id: "useOverlayScrollbars", pref: "widget.gtk.overlay-scrollbars.enabled", visible: () => AppConstants.MOZ_WIDGET_GTK, }); /** * Helper object for managing the various zoom related settings. */ const ZoomHelpers = { win: window.browsingContext.topChromeWindow, get FullZoom() { return this.win.FullZoom; }, get ZoomManager() { return this.win.ZoomManager; }, /** * Set the global default zoom value. * * @param {number} newZoom - The new zoom * @returns {Promise} */ async setDefaultZoom(newZoom) { let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService( Ci.nsIContentPrefService2 ); let nonPrivateLoadContext = Cu.createLoadContext(); let resolvers = Promise.withResolvers(); /* Because our setGlobal function takes in a browsing context, and * because we want to keep this property consistent across both private * and non-private contexts, we create a non-private context and use that * to set the property, regardless of our actual context. */ cps2.setGlobal(this.FullZoom.name, newZoom, nonPrivateLoadContext, { handleCompletion: resolvers.resolve, handleError: resolvers.reject, }); return resolvers.promise; }, async getDefaultZoom() { /** @import { ZoomUI as GlobalZoomUI } from "resource:///modules/ZoomUI.sys.mjs" */ /** @type {GlobalZoomUI} */ let ZoomUI = this.win.ZoomUI; return await ZoomUI.getGlobalValue(); }, /** * The possible zoom values. * * @returns {number[]} */ get zoomValues() { return this.ZoomManager.zoomValues; }, toggleFullZoom() { this.ZoomManager.toggleZoom(); }, }; Preferences.addSetting( class extends Preferences.AsyncSetting { static id = "defaultZoom"; /** @type {Record<"options", object[]>} */ optionsConfig; /** * @param {string} val - zoom value as a string */ async set(val) { await ZoomHelpers.setDefaultZoom( parseFloat((parseInt(val, 10) / 100).toFixed(2)) ); } async get() { return String(Math.round((await ZoomHelpers.getDefaultZoom()) * 100)); } async getControlConfig() { if (!this.optionsConfig) { this.optionsConfig = { options: ZoomHelpers.zoomValues.map(a => { let value = String(Math.round(a * 100)); return { value, controlAttrs: { label: `${value}%` }, l10nId: "preferences-default-zoom-value", l10nArgs: { percentage: value }, }; }), }; } return this.optionsConfig; } } ); Preferences.addSetting({ id: "zoomTextPref", pref: "browser.zoom.full", }); Preferences.addSetting({ id: "zoomText", deps: ["zoomTextPref"], // Use the Setting since the ZoomManager getter may not have updated yet. get: (_, { zoomTextPref }) => !zoomTextPref.value, set: () => ZoomHelpers.toggleFullZoom(), disabled: ({ zoomTextPref }) => zoomTextPref.locked, }); Preferences.addSetting({ id: "zoomWarning", deps: ["zoomText"], visible: ({ zoomText }) => Boolean(zoomText.value), }); /** * Helper object for managing font-related settings. */ const FontHelpers = { _enumerator: null, _allFonts: null, _localizedDefaultLabels: new Map(), async fetchLocalizedDefaultLabel(langGroup, fontType) { const cacheKey = `${langGroup}:${fontType}`; let defaultFont = this.enumerator.getDefaultFont(langGroup, fontType); if (!defaultFont) { defaultFont = this.enumerator.getDefaultFont(langGroup, ""); } const l10nId = defaultFont ? "fonts-label-default" : "fonts-label-default-unnamed"; const l10nArgs = defaultFont ? { name: defaultFont } : undefined; const [msg] = await document.l10n.formatMessages([ { id: l10nId, args: l10nArgs }, ]); const labelAttr = msg?.attributes?.find(a => a.name === "label"); if (labelAttr) { this._localizedDefaultLabels.set(cacheKey, labelAttr.value); } }, get enumerator() { if (!this._enumerator) { this._enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].createInstance( Ci.nsIFontEnumerator ); } return this._enumerator; }, ensurePref(prefId, type) { let pref = Preferences.get(prefId); if (!pref) { pref = Preferences.add({ id: prefId, type }); } return pref; }, get langGroup() { return Services.locale.fontLanguageGroup; }, getFontTypePrefId(langGroup) { return `font.default.${langGroup}`; }, getFontType(langGroup) { const prefId = this.getFontTypePrefId(langGroup); return Services.prefs.getCharPref(prefId, "serif"); }, getFontPrefId(langGroup) { const fontType = this.getFontType(langGroup); return `font.name.${fontType}.${langGroup}`; }, getSizePrefId(langGroup) { return `font.size.variable.${langGroup}`; }, buildFontOptions(langGroup, fontType) { let fonts = this.enumerator.EnumerateFonts(langGroup, fontType); let defaultFont = null; if (fonts.length) { defaultFont = this.enumerator.getDefaultFont(langGroup, fontType); } else { fonts = this.enumerator.EnumerateFonts(langGroup, ""); if (fonts.length) { defaultFont = this.enumerator.getDefaultFont(langGroup, ""); } } if (!this._allFonts) { this._allFonts = this.enumerator.EnumerateAllFonts(); } const options = []; if (fonts.length) { const cacheKey = `${langGroup}:${fontType}`; const localizedLabel = this._localizedDefaultLabels.get(cacheKey); if (defaultFont) { options.push({ value: "", controlAttrs: { label: localizedLabel || defaultFont }, l10nId: "fonts-label-default", l10nArgs: { name: defaultFont }, }); } else { options.push({ value: "", ...(localizedLabel ? { controlAttrs: { label: localizedLabel } } : {}), l10nId: "fonts-label-default-unnamed", }); } for (const font of fonts) { options.push({ value: font, controlAttrs: { label: font }, }); } } if (this._allFonts.length > fonts.length) { const fontSet = new Set(fonts); for (const font of this._allFonts) { if (!fontSet.has(font)) { options.push({ value: font, controlAttrs: { label: font }, }); } } } return options; }, fontSizeOptions: [ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72, ].map(size => ({ value: size, controlAttrs: { label: String(size) } })), }; Preferences.addSetting({ id: "fontLanguageGroup", pref: "font.language.group", }); Preferences.addSetting({ id: "fontType", deps: ["fontLanguageGroup"], setup(emitChange, deps, setting) { const handleChange = () => { setting.pref = FontHelpers.ensurePref( FontHelpers.getFontTypePrefId(FontHelpers.langGroup), "string" ); emitChange(); }; handleChange(); deps.fontLanguageGroup.on("change", handleChange); return () => deps.fontLanguageGroup.off("change", handleChange); }, }); Preferences.addSetting({ id: "defaultFont", deps: ["fontType"], optionsConfig: null, setup(emitChange, deps, setting) { const handleChange = () => { setting.pref = FontHelpers.ensurePref( FontHelpers.getFontPrefId(FontHelpers.langGroup), "fontname" ); this.optionsConfig = null; emitChange(); const langGroup = FontHelpers.langGroup; const fontType = FontHelpers.getFontType(langGroup); FontHelpers.fetchLocalizedDefaultLabel(langGroup, fontType) .then(() => { this.optionsConfig = null; emitChange(); }) .catch(console.error); }; handleChange(); deps.fontType.on("change", handleChange); return () => deps.fontType.off("change", handleChange); }, getControlConfig(config) { if (!this.optionsConfig) { this.optionsConfig = { ...config, options: FontHelpers.buildFontOptions( FontHelpers.langGroup, FontHelpers.getFontType(FontHelpers.langGroup) ), }; } return this.optionsConfig; }, }); Preferences.addSetting({ id: "defaultFontSize", deps: ["fontLanguageGroup"], setup(emitChange, deps, setting) { const handleLangChange = () => { setting.pref = FontHelpers.ensurePref( FontHelpers.getSizePrefId(FontHelpers.langGroup), "int" ); emitChange(); }; handleLangChange(); deps.fontLanguageGroup.on("change", handleLangChange); return () => deps.fontLanguageGroup.off("change", handleLangChange); }, getControlConfig(config) { return { ...config, options: FontHelpers.fontSizeOptions }; }, }); Preferences.addSetting({ id: "advancedFonts", onUserClick: () => window.gSubDialog.open( "chrome://browser/content/preferences/dialogs/fonts.xhtml", { features: "resizable=no" } ), }); Preferences.addSetting({ id: "contrastControlSettings", pref: "browser.display.document_color_use", }); Preferences.addSetting({ id: "colors", onUserClick() { window.gSubDialog.open( "chrome://browser/content/preferences/dialogs/colors.xhtml", { features: "resizable=no" } ); }, }); SettingGroupManager.registerGroups({ zoom: { l10nId: "preferences-default-zoom-label", iconSrc: "chrome://browser/skin/preferences/category-search.svg", headingLevel: 2, items: [ { id: "defaultZoom", l10nId: "preferences-default-zoom-select", control: "moz-select", }, { id: "zoomText", l10nId: "preferences-zoom-text-only", }, { id: "zoomWarning", l10nId: "preferences-text-zoom-override-warning", control: "moz-message-bar", controlAttrs: { type: "warning", }, }, ], }, fonts: { l10nId: "preferences-fonts-header2", headingLevel: 2, items: [ { id: "defaultFont", l10nId: "preferences-fonts-family", control: "moz-select", }, { id: "defaultFontSize", l10nId: "preferences-fonts-size", control: "moz-select", }, { id: "advancedFonts", l10nId: "preferences-fonts-advanced-settings", control: "moz-box-button", controlAttrs: { "search-l10n-ids": "fonts-window.title,fonts-langgroup-header,fonts-proportional-size,fonts-proportional-header,fonts-serif,fonts-sans-serif,fonts-monospace,fonts-langgroup-arabic.label,fonts-langgroup-armenian.label,fonts-langgroup-bengali.label,fonts-langgroup-simpl-chinese.label,fonts-langgroup-trad-chinese-hk.label,fonts-langgroup-trad-chinese.label,fonts-langgroup-cyrillic.label,fonts-langgroup-devanagari.label,fonts-langgroup-ethiopic.label,fonts-langgroup-georgian.label,fonts-langgroup-el.label,fonts-langgroup-gujarati.label,fonts-langgroup-gurmukhi.label,fonts-langgroup-japanese.label,fonts-langgroup-hebrew.label,fonts-langgroup-kannada.label,fonts-langgroup-khmer.label,fonts-langgroup-korean.label,fonts-langgroup-latin.label,fonts-langgroup-malayalam.label,fonts-langgroup-math.label,fonts-langgroup-odia.label,fonts-langgroup-sinhala.label,fonts-langgroup-tamil.label,fonts-langgroup-telugu.label,fonts-langgroup-thai.label,fonts-langgroup-tibetan.label,fonts-langgroup-canadian.label,fonts-langgroup-other.label,fonts-minsize,fonts-minsize-none.label,fonts-default-serif.label,fonts-default-sans-serif.label,fonts-allow-own.label", }, }, ], }, contrast: { l10nId: "preferences-contrast-control-group", iconSrc: "chrome://browser/skin/contrast.svg", headingLevel: 2, items: [ { id: "contrastControlSettings", control: "moz-radio-group", l10nId: "preferences-contrast-control-radio-group", options: [ { id: "contrastSettingsAuto", value: 0, l10nId: "preferences-contrast-control-use-platform-settings", }, { id: "contrastSettingsOff", value: 1, l10nId: "preferences-contrast-control-off", }, { id: "contrastSettingsOn", value: 2, l10nId: "preferences-contrast-control-custom", items: [ { id: "colors", l10nId: "preferences-colors-manage-button", control: "moz-box-button", controlAttrs: { "search-l10n-ids": "colors-text-and-background, colors-text.label, colors-text-background.label, colors-links-header, colors-links-unvisited.label, colors-links-visited.label", }, }, ], }, ], }, ], }, keyboardAndScrolling: { l10nId: "keyboard-and-scrolling-group", headingLevel: 2, items: [ { id: "useOnScreenKeyboard", l10nId: "browsing-use-onscreen-keyboard" }, { id: "useCursorNavigation", l10nId: "browsing-use-cursor-navigation" }, { id: "useFullKeyboardNavigation", l10nId: "browsing-use-full-keyboard-navigation", }, { id: "searchStartTyping", l10nId: "browsing-search-on-start-typing" }, { id: "mediaControlToggleEnabled", l10nId: "browsing-media-control", supportPage: "media-keyboard-control", }, { id: "useAutoScroll", l10nId: "browsing-use-autoscroll", }, { id: "useOverlayScrollbars", l10nId: "browsing-gtk-use-non-overlay-scrollbars", }, ], }, motionAndLink: { l10nId: "motion-and-link-group", headingLevel: 2, items: [ { id: "alwaysUnderlineLinks", l10nId: "browsing-always-underline-links" }, { id: "useSmoothScrolling", l10nId: "browsing-use-smooth-scrolling", }, ], }, });