/* 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"]; 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, nsIPrefBranch getter method name]]. In the former case, the getter // method name is inferred from the typeof the default value. const PREF_URLBAR_DEFAULTS = new Map([ // "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, "getFloatPref"]], // If true, this optimizes for replacing the full URL rather than editing // part of it. This also copies the urlbar value to the selection clipboard // on systems that support it. ["clickSelectsAll", false], // 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], // If true, this optimizes for replacing the full URL rather than selecting a // portion of it. This also copies the urlbar value to the selection // clipboard on systems that support it. ["doubleClickSelectsAll", false], // Whether telemetry events should be recorded. ["eventTelemetry.enabled", false], // 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", false], // Allows results from one search to be reused in the next search. One of the // INSERTMETHOD values. ["insertMethod", UrlbarUtils.INSERTMETHOD.MERGE_RELATED], // 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], // May be removed in the future. Usually (when this pref is at its default of // zero), search engine results do not include results from the user's local // browser history. This value can be set to include such results. ["maxHistoricalSearchSuggestions", 0], // The maximum number of results in the urlbar popup. ["maxRichResults", 10], // Whether the quantum bar displays the major design update. ["megabar", false], // Whether the megabar displays the permanent search icon. ["searchIcon", false], // One-off search buttons enabled status. ["oneOffSearches", false], // Whether addresses and search results typed into the address bar // should be opened in new tabs by default. ["openintab", false], // Whether to open the urlbar view when the input field is focused by the user. ["openViewOnFocus", false], // Whether speculative connections should be enabled. ["speculativeConnect.enabled", true], // Results will include the user's bookmarks when this is true. ["suggest.bookmark", true], // Results will include the user's history when this is true. ["suggest.history", true], // Results will include switch-to-tab results when this is true. ["suggest.openpage", true], // Results will include search suggestions when this is true. ["suggest.searches", false], // When using switch to tabs, if set to true this will move the tab into the // active window. ["switchTabs.adoptIntoActiveWindow", false], // Remove redundant portions from URLs. ["trimURLs", true], // Results will include a built-in set of popular domains when this is true. ["usepreloadedtopurls.enabled", true], // 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], // 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 strip https:// instead of http:// from URLs in the results view. ["view.stripHttps", false], ]); 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"], ["string", "Char"], ["number", "Int"], ]); // 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([ Ci.nsIObserver, Ci.nsISupportsWeakReference, ]); Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true); for (let pref of PREF_OTHER_DEFAULTS.keys()) { Services.prefs.addObserver(pref, this, true); } } /** * Returns the value for the preference with the given name. * * @param {string} pref * The name of the preference to get. * @returns {*} The preference value. */ get(pref) { if (!this._map.has(pref)) { this._map.set(pref, this._getPrefValue(pref)); } return this._map.get(pref); } /** * 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; } this._map.delete(pref); // Some prefs may influence others. if (pref == "matchBuckets") { this._map.delete("matchBucketsSearch"); } if (pref.startsWith("suggest.")) { this._map.delete("defaultBehavior"); this._map.delete("emptySearchDefaultBehavior"); } } /** * 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 prefs = Services.prefs.getBranch(PREF_URLBAR_BRANCH); let def = PREF_URLBAR_DEFAULTS.get(pref); if (def === undefined) { prefs = Services.prefs; def = PREF_OTHER_DEFAULTS.get(pref); } if (def === undefined) { throw new Error("Trying to access an unknown pref " + pref); } let getterName; if (!Array.isArray(def)) { getterName = `get${PREF_TYPES.get(typeof def)}Pref`; } else { if (def.length != 2) { throw new Error("Malformed pref def: " + pref); } [def, getterName] = def; } return prefs[getterName](pref, def); } /** * 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; } case "emptySearchDefaultBehavior": { // Further restrictions to apply for "empty searches" (searching for // ""). The empty behavior is typed history, if history is enabled. // Otherwise, it is bookmarks, if they are enabled. If both history and // bookmarks are disabled, it defaults to open pages. let val = Ci.mozIPlacesAutoComplete.BEHAVIOR_RESTRICT; if (this.get("suggest.history")) { val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY; } else if (this.get("suggest.bookmark")) { val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK; } else { val |= Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE; } return val; } } return this._readPref(pref); } } var UrlbarPrefs = new Preferences();