/* 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();