diff options
Diffstat (limited to 'browser/components/urlbar/UrlbarPrefs.jsm')
-rw-r--r-- | browser/components/urlbar/UrlbarPrefs.jsm | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/browser/components/urlbar/UrlbarPrefs.jsm b/browser/components/urlbar/UrlbarPrefs.jsm new file mode 100644 index 0000000000..a73d2c959e --- /dev/null +++ b/browser/components/urlbar/UrlbarPrefs.jsm @@ -0,0 +1,467 @@ +/* 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/. */ + +"use strict"; + +/** + * This module exports the UrlbarPrefs singleton, which manages + * preferences for the urlbar. + */ + +var EXPORTED_SYMBOLS = ["UrlbarPrefs", "UrlbarPrefsObserver"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + PlacesUtils: "resource://gre/modules/PlacesUtils.jsm", + UrlbarUtils: "resource:///modules/UrlbarUtils.jsm", +}); + +const PREF_URLBAR_BRANCH = "browser.urlbar."; + +// Prefs are defined as [pref name, default value] or [pref name, [default +// value, type]]. In the former case, the getter method name is inferred from +// the typeof the default value. +const PREF_URLBAR_DEFAULTS = new Map([ + // Whether we announce to screen readers when tab-to-search results are + // inserted. + ["accessibility.tabToSearch.announceResults", true], + + // "Autofill" is the name of the feature that automatically completes domains + // and URLs that the user has visited as the user is typing them in the urlbar + // textbox. If false, autofill will be disabled. + ["autoFill", true], + + // If true, the domains of the user's installed search engines will be + // autofilled even if the user hasn't actually visited them. + ["autoFill.searchEngines", false], + + // Affects the frecency threshold of the autofill algorithm. The threshold is + // the mean of all origin frecencies plus one standard deviation multiplied by + // this value. See UnifiedComplete. + ["autoFill.stddevMultiplier", [0.0, "float"]], + + // Whether using `ctrl` when hitting return/enter in the URL bar + // (or clicking 'go') should prefix 'www.' and suffix + // browser.fixup.alternate.suffix to the URL bar value prior to + // navigating. + ["ctrlCanonizesURLs", true], + + // Whether copying the entire URL from the location bar will put a human + // readable (percent-decoded) URL on the clipboard. + ["decodeURLsOnCopy", false], + + // The amount of time (ms) to wait after the user has stopped typing before + // fetching results. However, we ignore this for the very first result (the + // "heuristic" result). We fetch it as fast as possible. + ["delay", 50], + + // Some performance tests disable this because extending the urlbar needs + // layout information that we can't get before the first paint. (Or we could + // but this would mean flushing layout.) + ["disableExtendForTests", false], + + // Controls when to DNS resolve single word search strings, after they were + // searched for. If the string is resolved as a valid host, show a + // "Did you mean to go to 'host'" prompt. + // 0 - never resolve; 1 - use heuristics (default); 2 - always resolve + ["dnsResolveSingleWordsAfterSearch", 1], + + // Whether telemetry events should be recorded. + ["eventTelemetry.enabled", false], + + // Whether we expand the font size when when the urlbar is + // focused. + ["experimental.expandTextOnFocus", false], + + // Whether the urlbar displays a permanent search button. + ["experimental.searchButton", false], + + // Whether we style the search mode indicator's close button on hover. + ["experimental.searchModeIndicatorHover", false], + + // When we send events to extensions, we wait this amount of time in + // milliseconds for them to respond before timing out. + ["extension.timeout", 400], + + // When true, `javascript:` URLs are not included in search results. + ["filter.javascript", true], + + // Applies URL highlighting and other styling to the text in the urlbar input. + ["formatting.enabled", true], + + // Whether during IME composition the results panel should be closed. + ["imeCompositionClosesPanel", true], + + // Controls the composition of search results. + ["matchBuckets", "suggestion:4,general:Infinity"], + + // If the heuristic result is a search engine result, we use this instead of + // matchBuckets. + ["matchBucketsSearch", ""], + + // For search suggestion results, we truncate the user's search string to this + // number of characters before fetching results. + ["maxCharsForSearchSuggestions", 20], + + // The maximum number of form history results to include. + ["maxHistoricalSearchSuggestions", 0], + + // The maximum number of results in the urlbar popup. + ["maxRichResults", 10], + + // Whether addresses and search results typed into the address bar + // should be opened in new tabs by default. + ["openintab", false], + + // When true, URLs in the user's history that look like search result pages + // are styled to look like search engine results instead of the usual history + // results. + ["restyleSearches", false], + + // If true, we show tail suggestions when available. + ["richSuggestions.tail", true], + + // Hidden pref. Disables checks that prevent search tips being shown, thus + // showing them every time the newtab page or the default search engine + // homepage is opened. + ["searchTips.test.ignoreShowLimits", false], + + // Whether to show each local search shortcut button in the view. + ["shortcuts.bookmarks", true], + ["shortcuts.tabs", true], + ["shortcuts.history", true], + + // Whether speculative connections should be enabled. + ["speculativeConnect.enabled", true], + + // Whether results will include the user's bookmarks. + ["suggest.bookmark", true], + + // Whether results will include the user's history. + ["suggest.history", true], + + // Whether results will include switch-to-tab results. + ["suggest.openpage", true], + + // Whether results will include search suggestions. + ["suggest.searches", false], + + // Whether results will include search engines (e.g. tab-to-search). + ["suggest.engines", true], + + // Whether results will include top sites and the view will open on focus. + ["suggest.topsites", true], + + // When using switch to tabs, if set to true this will move the tab into the + // active window. + ["switchTabs.adoptIntoActiveWindow", false], + + // The number of remaining times the user can interact with tab-to-search + // onboarding results before we stop showing them. + ["tabToSearch.onboard.interactionsLeft", 3], + + // The number of times the user has been shown the onboarding search tip. + ["tipShownCount.searchTip_onboard", 0], + + // The number of times the user has been shown the redirect search tip. + ["tipShownCount.searchTip_redirect", 0], + + // Remove redundant portions from URLs. + ["trimURLs", true], + + // Results will include a built-in set of popular domains when this is true. + ["usepreloadedtopurls.enabled", false], + + // After this many days from the profile creation date, the built-in set of + // popular domains will no longer be included in the results. + ["usepreloadedtopurls.expire_days", 14], + + // Controls the empty search behavior in Search Mode: + // 0 - Show nothing + // 1 - Show search history + // 2 - Show search and browsing history + ["update2.emptySearchBehavior", 0], +]); +const PREF_OTHER_DEFAULTS = new Map([ + ["keyword.enabled", true], + ["browser.search.suggest.enabled", true], + ["browser.search.suggest.enabled.private", false], + ["ui.popup.disable_autohide", false], + ["browser.fixup.dns_first_for_single_words", false], +]); + +// Maps preferences under browser.urlbar.suggest to behavior names, as defined +// in mozIPlacesAutoComplete. +const SUGGEST_PREF_TO_BEHAVIOR = { + history: "history", + bookmark: "bookmark", + openpage: "openpage", + searches: "search", +}; + +const PREF_TYPES = new Map([ + ["boolean", "Bool"], + ["float", "Float"], + ["number", "Int"], + ["string", "Char"], +]); + +// Buckets for result insertion. +// Every time a new result is returned, we go through each bucket in array order, +// and look for the first one having available space for the given result type. +// Each bucket is an array containing the following indices: +// 0: The result type of the acceptable entries. +// 1: available number of slots in this bucket. +// There are different matchBuckets definition for different contexts, currently +// a general one (matchBuckets) and a search one (matchBucketsSearch). +// +// First buckets. Anything with an Infinity frecency ends up here. +const DEFAULT_BUCKETS_BEFORE = [ + [UrlbarUtils.RESULT_GROUP.HEURISTIC, 1], + [ + UrlbarUtils.RESULT_GROUP.EXTENSION, + UrlbarUtils.MAXIMUM_ALLOWED_EXTENSION_MATCHES - 1, + ], +]; +// => USER DEFINED BUCKETS WILL BE INSERTED HERE <= +// +// Catch-all buckets. Anything remaining ends up here. +const DEFAULT_BUCKETS_AFTER = [ + [UrlbarUtils.RESULT_GROUP.SUGGESTION, Infinity], + [UrlbarUtils.RESULT_GROUP.GENERAL, Infinity], +]; + +/** + * Preferences class. The exported object is a singleton instance. + */ +class Preferences { + /** + * Constructor + */ + constructor() { + this._map = new Map(); + this.QueryInterface = ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]); + Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true); + for (let pref of PREF_OTHER_DEFAULTS.keys()) { + Services.prefs.addObserver(pref, this, true); + } + this._observerWeakRefs = []; + this.addObserver(this); + } + + /** + * Returns the value for the preference with the given name. + * For preferences in the "browser.urlbar."" branch, the passed-in name + * should be relative to the branch. It's also possible to get prefs from the + * PREF_OTHER_DEFAULTS Map, specifying their full name. + * + * @param {string} pref + * The name of the preference to get. + * @returns {*} The preference value. + */ + get(pref) { + let value = this._map.get(pref); + if (value === undefined) { + value = this._getPrefValue(pref); + this._map.set(pref, value); + } + return value; + } + + /** + * Sets the value for the preference with the given name. + * For preferences in the "browser.urlbar."" branch, the passed-in name + * should be relative to the branch. It's also possible to set prefs from the + * PREF_OTHER_DEFAULTS Map, specifying their full name. + * + * @param {string} pref + * The name of the preference to set. + * @param {*} value The preference value. + */ + set(pref, value) { + let { defaultValue, setter } = this._getPrefDescriptor(pref); + if (typeof value != typeof defaultValue) { + throw new Error(`Invalid value type ${typeof value} for pref ${pref}`); + } + setter(pref, value); + } + + /** + * Adds a preference observer. Observers are held weakly. + * + * @param {object} observer + * An object that must have a method named `onPrefChanged`, which will + * be called when a urlbar preference changes. It will be passed the + * pref name. For prefs in the `browser.urlbar.` branch, the name will + * be relative to the branch. For other prefs, the name will be the + * full name. + */ + addObserver(observer) { + this._observerWeakRefs.push(Cu.getWeakReference(observer)); + } + + /** + * Observes preference changes. + * + * @param {nsISupports} subject + * @param {string} topic + * @param {string} data + */ + observe(subject, topic, data) { + let pref = data.replace(PREF_URLBAR_BRANCH, ""); + if (!PREF_URLBAR_DEFAULTS.has(pref) && !PREF_OTHER_DEFAULTS.has(pref)) { + return; + } + for (let i = 0; i < this._observerWeakRefs.length; ) { + let observer = this._observerWeakRefs[i].get(); + if (!observer) { + // The observer has been GC'ed, so remove it from our list. + this._observerWeakRefs.splice(i, 1); + } else { + observer.onPrefChanged(pref); + ++i; + } + } + } + + /** + * Called when a pref tracked by UrlbarPrefs changes. + * + * @param {string} pref + * The name of the pref, relative to `browser.urlbar.` if the pref is + * in that branch. + */ + onPrefChanged(pref) { + this._map.delete(pref); + // Some prefs may influence others. + if (pref == "matchBuckets") { + this._map.delete("matchBucketsSearch"); + } + if (pref.startsWith("suggest.")) { + this._map.delete("defaultBehavior"); + } + } + + /** + * Returns the raw value of the given preference straight from Services.prefs. + * + * @param {string} pref + * The name of the preference to get. + * @returns {*} The raw preference value. + */ + _readPref(pref) { + let { defaultValue, getter } = this._getPrefDescriptor(pref); + return getter(pref, defaultValue); + } + + /** + * Returns a validated and/or fixed-up value of the given preference. The + * value may be validated for correctness, or it might be converted into a + * different value that is easier to work with than the actual value stored in + * the preferences branch. Not all preferences require validation or fixup. + * + * The values returned from this method are the values that are made public by + * this module. + * + * @param {string} pref + * The name of the preference to get. + * @returns {*} The validated and/or fixed-up preference value. + */ + _getPrefValue(pref) { + switch (pref) { + case "matchBuckets": { + // Convert from pref char format to an array and add the default + // buckets. + let val = this._readPref(pref); + try { + val = PlacesUtils.convertMatchBucketsStringToArray(val); + } catch (ex) { + val = PlacesUtils.convertMatchBucketsStringToArray( + PREF_URLBAR_DEFAULTS.get(pref) + ); + } + return [...DEFAULT_BUCKETS_BEFORE, ...val, ...DEFAULT_BUCKETS_AFTER]; + } + case "matchBucketsSearch": { + // Convert from pref char format to an array and add the default + // buckets. + let val = this._readPref(pref); + if (val) { + // Convert from pref char format to an array and add the default + // buckets. + try { + val = PlacesUtils.convertMatchBucketsStringToArray(val); + return [ + ...DEFAULT_BUCKETS_BEFORE, + ...val, + ...DEFAULT_BUCKETS_AFTER, + ]; + } catch (ex) { + /* invalid format, will just return matchBuckets */ + } + } + return this.get("matchBuckets"); + } + case "defaultBehavior": { + let val = 0; + for (let type of Object.keys(SUGGEST_PREF_TO_BEHAVIOR)) { + let behavior = `BEHAVIOR_${SUGGEST_PREF_TO_BEHAVIOR[ + type + ].toUpperCase()}`; + val |= + this.get("suggest." + type) && Ci.mozIPlacesAutoComplete[behavior]; + } + return val; + } + } + return this._readPref(pref); + } + + /** + * Returns a descriptor of the given preference. + * @param {string} pref The preference to examine. + * @returns {object} An object describing the pref with the following shape: + * { defaultValue, getter, setter } + */ + _getPrefDescriptor(pref) { + let branch = Services.prefs.getBranch(PREF_URLBAR_BRANCH); + let defaultValue = PREF_URLBAR_DEFAULTS.get(pref); + if (defaultValue === undefined) { + branch = Services.prefs; + defaultValue = PREF_OTHER_DEFAULTS.get(pref); + } + if (defaultValue === undefined) { + throw new Error("Trying to access an unknown pref " + pref); + } + + let type; + if (!Array.isArray(defaultValue)) { + type = PREF_TYPES.get(typeof defaultValue); + } else { + if (defaultValue.length != 2) { + throw new Error("Malformed pref def: " + pref); + } + [defaultValue, type] = defaultValue; + type = PREF_TYPES.get(type); + } + if (!type) { + throw new Error("Unknown pref type: " + pref); + } + return { + defaultValue, + getter: branch[`get${type}Pref`], + // Float prefs are stored as Char. + setter: branch[`set${type == "Float" ? "Char" : type}Pref`], + }; + } +} + +var UrlbarPrefs = new Preferences(); |