tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UrlbarPrefs.sys.mjs (48979B)


      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
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /**
      6 * This module exports the UrlbarPrefs singleton, which manages preferences for
      7 * the urlbar. It also provides access to urlbar Nimbus variables as if they are
      8 * preferences, but only for variables with fallback prefs.
      9 */
     10 
     11 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
     12 
     13 const lazy = XPCOMUtils.declareLazy({
     14  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     15  UrlbarUtils: "moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs",
     16  CustomizableUI:
     17    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     18 });
     19 
     20 const PREF_URLBAR_BRANCH = "browser.urlbar.";
     21 
     22 /**
     23 * @typedef {boolean|number|string|[number, string]} PreferenceDefaultAndType
     24 * Prefs are defined as [pref name, default value] or [pref name, [default
     25 * value, type]]. In the former case, the getter method name is inferred from
     26 * the typeof the default value.
     27 *
     28 * @typedef {[string, PreferenceDefaultAndType]} PreferenceDefinition
     29 */
     30 
     31 // NOTE: Don't name prefs (relative to the `browser.urlbar` branch) the same as
     32 // Nimbus urlbar features. Doing so would cause a name collision because pref
     33 // names and Nimbus feature names are both kept as keys in UrlbarPref's map. For
     34 // a list of Nimbus features, see toolkit/components/nimbus/FeatureManifest.yaml.
     35 const PREF_URLBAR_DEFAULTS = /** @type {PreferenceDefinition[]} */ ([
     36  // Whether we announce to screen readers when tab-to-search results are
     37  // inserted.
     38  ["accessibility.tabToSearch.announceResults", true],
     39 
     40  // Feature gate pref for addon suggestions in the urlbar.
     41  ["addons.featureGate", false],
     42 
     43  // The number of times the user has clicked the "Show less frequently" command
     44  // for addon suggestions.
     45  ["addons.showLessFrequentlyCount", 0],
     46 
     47  // Feature gate pref for AMP suggestions in the urlbar.
     48  ["amp.featureGate", false],
     49 
     50  // "Autofill" is the name of the feature that automatically completes domains
     51  // and URLs that the user has visited as the user is typing them in the urlbar
     52  // textbox.  If false, autofill will be disabled.
     53  ["autoFill", true],
     54 
     55  // Whether enabling adaptive history autofill. This pref is a fallback for the
     56  // Nimbus variable `autoFillAdaptiveHistoryEnabled`.
     57  ["autoFill.adaptiveHistory.enabled", false],
     58 
     59  // Minimum char length of the user's search string to enable adaptive history
     60  // autofill. This pref is a fallback for the Nimbus variable
     61  // `autoFillAdaptiveHistoryMinCharsThreshold`.
     62  ["autoFill.adaptiveHistory.minCharsThreshold", 0],
     63 
     64  // Threshold for use count of input history that we handle as adaptive history
     65  // autofill. If the use count is this value or more, it will be a candidate.
     66  // Set the threshold to not be candidate the input history passed approximately
     67  // 30 days since user input it as the default.
     68  ["autoFill.adaptiveHistory.useCountThreshold", [0.47, "float"]],
     69 
     70  // Affects the frecency threshold of the autofill algorithm.  The threshold is
     71  // the mean of all origin frecencies plus one standard deviation multiplied by
     72  // this value.  See UrlbarProviderPlaces.
     73  ["autoFill.stddevMultiplier", [0.0, "float"]],
     74 
     75  // Feature gate pref for clipboard suggestions in the urlbar.
     76  ["clipboard.featureGate", false],
     77 
     78  // Whether to close other panels when the urlbar panel opens.
     79  // This feature gate exists just as an emergency rollback in case of
     80  // unexpected issues in Release. We normally want this behavior.
     81  ["closeOtherPanelsOnOpen", true],
     82 
     83  // Whether to show a link for using the search functionality provided by the
     84  // active view if the the view utilizes OpenSearch.
     85  ["contextualSearch.enabled", true],
     86 
     87  // Whether using `ctrl` when hitting return/enter in the URL bar
     88  // (or clicking 'go') should prefix 'www.' and suffix
     89  // browser.fixup.alternate.suffix to the URL bar value prior to
     90  // navigating.
     91  ["ctrlCanonizesURLs", true],
     92 
     93  // Whether copying the entire URL from the location bar will put a human
     94  // readable (percent-decoded) URL on the clipboard.
     95  ["decodeURLsOnCopy", false],
     96 
     97  // The amount of time (ms) to wait after the user has stopped typing before
     98  // fetching results.  However, we ignore this for the very first result (the
     99  // "heuristic" result).  We fetch it as fast as possible.
    100  ["delay", 50],
    101 
    102  // Ensure we use trailing dots for DNS lookups for single words that could
    103  // be hosts.
    104  ["dnsResolveFullyQualifiedNames", true],
    105 
    106  // Controls when to DNS resolve single word search strings, after they were
    107  // searched for. If the string is resolved as a valid host, show a
    108  // "Did you mean to go to 'host'" prompt.
    109  // 0 - never resolve; 1 - use heuristics (default); 2 - always resolve
    110  ["dnsResolveSingleWordsAfterSearch", 0],
    111 
    112  // If Suggest is disabled before these seconds from a search, then send a
    113  // disable event.
    114  ["events.disableSuggest.maxSecondsFromLastSearch", 300],
    115 
    116  // If a page is interacted with for less than these seconds, before navigating
    117  // away via browser chrome, then send a bounce event.
    118  ["events.bounce.maxSecondsFromLastSearch", 10],
    119 
    120  // Whether the heuristic result is hidden.
    121  ["experimental.hideHeuristic", false],
    122 
    123  // Comma-separated list of `source.providers` combinations, that are used to
    124  // determine if an exposure event should be fired. This can be set by a
    125  // Nimbus variable and is expected to be set via nimbus experiment
    126  // configuration.
    127  ["exposureResults", ""],
    128 
    129  // When we send events to (privileged) extensions (urlbar API), we wait this
    130  // amount of time in milliseconds for them to respond before timing out.
    131  ["extension.timeout", 400],
    132 
    133  // When we send events to extensions that use the omnibox API, we wait this
    134  // amount of time in milliseconds for them to respond before timing out.
    135  ["extension.omnibox.timeout", 3000],
    136 
    137  // When true, `javascript:` URLs are not included in search results.
    138  ["filter.javascript", true],
    139 
    140  // Feature gate pref for flight status suggestions in the urlbar.
    141  ["flightStatus.featureGate", false],
    142 
    143  // The minimum prefix length of a flight status keyword the user must type to
    144  // trigger the suggestion. 0 means the min length should be taken from Nimbus
    145  // or remote settings.
    146  ["flightStatus.minKeywordLength", 0],
    147 
    148  // The number of times the user has clicked the "Show less frequently" command
    149  // for flight status suggestions.
    150  ["flightStatus.showLessFrequentlyCount", 0],
    151 
    152  // Focus the content document when pressing the Escape key, if there's no
    153  // remaining typed history.
    154  ["focusContentDocumentOnEsc", true],
    155 
    156  // Applies URL highlighting and other styling to the text in the urlbar input.
    157  ["formatting.enabled", true],
    158 
    159  // Whether Firefox Suggest group labels are shown in the urlbar view in en-*
    160  // locales. Labels are not shown in other locales but likely will be in the
    161  // future.
    162  ["groupLabels.enabled", true],
    163 
    164  // Feature gate pref for important-dates suggestions in the urlbar.
    165  ["importantDates.featureGate", false],
    166 
    167  // Set default intent threshold value of 0.5
    168  ["intentThreshold", [0.5, "float"]],
    169 
    170  // Whether the results panel should be kept open during IME composition.
    171  ["keepPanelOpenDuringImeComposition", false],
    172 
    173  // Comma-separated list of result types that should trigger keyword-exposure
    174  // telemetry. Only applies to results with an `exposureTelemetry` value other
    175  // than `NONE`.
    176  ["keywordExposureResults", ""],
    177 
    178  // Feature gate pref for stock market suggestions in the urlbar.
    179  ["market.featureGate", false],
    180 
    181  // The minimum prefix length of a market keyword the user must type to
    182  // trigger the suggestion. 0 means the min length should be taken from Nimbus
    183  // or remote settings.
    184  ["market.minKeywordLength", 0],
    185 
    186  // The number of times the user has clicked the "Show less frequently" command
    187  // for Market suggestions.
    188  ["market.showLessFrequentlyCount", 0],
    189 
    190  // As a user privacy measure, don't fetch results from remote services for
    191  // searches that start by pasting a string longer than this. The pref name
    192  // indicates search suggestions, but this is used for all remote results.
    193  ["maxCharsForSearchSuggestions", 100],
    194 
    195  // The maximum number of form history results to include.
    196  ["maxHistoricalSearchSuggestions", 0],
    197 
    198  // The maximum number of results in the urlbar popup.
    199  ["maxRichResults", 10],
    200 
    201  // Feature gate pref for mdn suggestions in the urlbar.
    202  ["mdn.featureGate", true],
    203 
    204  // Comma-separated list of client variants to send to Merino
    205  ["merino.clientVariants", ""],
    206 
    207  // The Merino endpoint URL, not including parameters.
    208  ["merino.endpointURL", "https://merino.services.mozilla.com/api/v1/suggest"],
    209 
    210  // OHTTP config URL for Merino requests.
    211  ["merino.ohttpConfigURL", ""],
    212 
    213  // OHTTP relay URL for Merino requests.
    214  ["merino.ohttpRelayURL", ""],
    215 
    216  // Comma-separated list of providers to request from Merino
    217  ["merino.providers", ""],
    218 
    219  // Timeout for Merino fetches (ms).
    220  ["merino.timeoutMs", 200],
    221 
    222  // Set default NER threshold value of 0.5
    223  ["nerThreshold", [0.5, "float"]],
    224 
    225  // Whether addresses and search results typed into the address bar
    226  // should be opened in new tabs by default.
    227  ["openintab", false],
    228 
    229  // Once Perplexity has entered search mode at least once,
    230  // we no longer show the Perplexity onboarding callout.
    231  // This pref will be set to true when perplexity search mode is detected.
    232  ["perplexity.hasBeenInSearchMode", false],
    233 
    234  // If disabled, QuickActions will not be included in either the default search
    235  // mode or the QuickActions search mode.
    236  ["quickactions.enabled", true],
    237 
    238  // The number of times we should show the actions onboarding label.
    239  ["quickactions.timesToShowOnboardingLabel", 3],
    240 
    241  // The number of times we have shown the actions onboarding label.
    242  ["quickactions.timesShownOnboardingLabel", 0],
    243 
    244  // Whether we will match QuickActions within a phrase and not only a prefix.
    245  ["quickactions.matchInPhrase", true],
    246 
    247  // The minumum amount of characters required for the user to input before
    248  // matching actions. Setting this to 0 will show the actions in the
    249  // zero prefix state.
    250  ["quickactions.minimumSearchString", 3],
    251 
    252  // Whether we show the Actions section in about:preferences.
    253  ["quickactions.showPrefs", false],
    254 
    255  // When non-zero, this is the character-count threshold (inclusive) for
    256  // showing AMP suggestions as top picks. If an AMP suggestion is triggered by
    257  // a keyword at least this many characters long, it will be shown as a top
    258  // pick.
    259  ["quicksuggest.ampTopPickCharThreshold", 5],
    260 
    261  // Whether the Firefox Suggest data collection opt-in result is enabled.
    262  ["quicksuggest.contextualOptIn", false],
    263 
    264  // The last time (as seconds) the user dismissed the Firefox Suggest contextual
    265  // opt-in result.
    266  ["quicksuggest.contextualOptIn.lastDismissedTime", 0],
    267 
    268  // Number that the user dismissed the Firefox Suggest contextual opt-in result.
    269  ["quicksuggest.contextualOptIn.dismissedCount", 0],
    270 
    271  // Period until reshow the Firefox Suggest contextual opt-in result when first dismissed.
    272  ["quicksuggest.contextualOptIn.firstReshowAfterPeriodDays", 7],
    273 
    274  // Period until reshow the Firefox Suggest contextual opt-in result when second dismissed.
    275  ["quicksuggest.contextualOptIn.secondReshowAfterPeriodDays", 14],
    276 
    277  // Period until reshow the Firefox Suggest contextual opt-in result when third dismissed.
    278  ["quicksuggest.contextualOptIn.thirdReshowAfterPeriodDays", 60],
    279 
    280  // Number of impression for the Firefox Suggest contextual opt-in result.
    281  ["quicksuggest.contextualOptIn.impressionCount", 0],
    282 
    283  // Limit for impression to dismiss the Firefox Suggest contextual opt-in
    284  // result.
    285  ["quicksuggest.contextualOptIn.impressionLimit", 20],
    286 
    287  // The first impression time (seconds) for the Firefox Suggest contextual
    288  // opt-in result.
    289  ["quicksuggest.contextualOptIn.firstImpressionTime", 0],
    290 
    291  // Days until dismiss the Firefox Suggest contextual opt-in result after first
    292  // impression.
    293  ["quicksuggest.contextualOptIn.impressionDaysLimit", 5],
    294 
    295  // Comma-separated list of Suggest dynamic suggestion types to enable.
    296  ["quicksuggest.dynamicSuggestionTypes", ""],
    297 
    298  // Global toggle for whether the quick suggest feature is enabled, i.e.,
    299  // sponsored and recommended results related to the user's search string.
    300  ["quicksuggest.enabled", false],
    301 
    302  // Whether non-sponsored quick suggest results are subject to impression
    303  // frequency caps. This pref is a fallback for the Nimbus variable
    304  // `quickSuggestImpressionCapsNonSponsoredEnabled`.
    305  ["quicksuggest.impressionCaps.nonSponsoredEnabled", false],
    306 
    307  // Whether sponsored quick suggest results are subject to impression frequency
    308  // caps. This pref is a fallback for the Nimbus variable
    309  // `quickSuggestImpressionCapsSponsoredEnabled`.
    310  ["quicksuggest.impressionCaps.sponsoredEnabled", false],
    311 
    312  // JSON'ed object of quick suggest impression stats. Used for implementing
    313  // impression frequency caps for quick suggest suggestions.
    314  ["quicksuggest.impressionCaps.stats", ""],
    315 
    316  // If the user has gone through a quick suggest prefs migration, then this
    317  // pref will have a user-branch value that records the latest prefs version.
    318  // See `QuickSuggest` for details on each version.
    319  ["quicksuggest.migrationVersion", 0],
    320 
    321  // Whether Suggest will use the ML backend in addition to Rust.
    322  ["quicksuggest.mlEnabled", false],
    323 
    324  // NOTE: You should most likely access this pref via its Nimbus variable
    325  // instead: `UrlbarPrefs.get("quickSuggestOnlineAvailable"). It's listed here
    326  // mainly so tests can access it easily via `UrlbarPrefs`.
    327  //
    328  // Whether online Suggest is available to the user. This is only relevant when
    329  // Suggest overall is enabled.
    330  ["quicksuggest.online.available", false],
    331 
    332  // Whether online Suggest is enabled for the user. This is only relevant when
    333  // Suggest overall is enabled and online Suggest is available to the user.
    334  ["quicksuggest.online.enabled", true],
    335 
    336  // The last time (as seconds) the user selected 'Not Now' on Realtime
    337  // suggestion opt-in result.
    338  ["quicksuggest.realtimeOptIn.notNowTimeSeconds", 0],
    339 
    340  // Period until reshow Realtime suggestion opt-in after selecting "Not Now".
    341  ["quicksuggest.realtimeOptIn.notNowReshowAfterPeriodDays", 7],
    342 
    343  // The type of Realtime suggestion that the user selected 'Not Now' as CSV.
    344  ["quicksuggest.realtimeOptIn.notNowTypes", ""],
    345 
    346  // The type of Realtime suggestion that the user selected 'Dismiss' as CSV.
    347  ["quicksuggest.realtimeOptIn.dismissTypes", ""],
    348 
    349  // Whether Firefox Suggest will use the new Rust backend instead of the
    350  // original JS backend.
    351  ["quicksuggest.rustEnabled", true],
    352 
    353  // The Suggest Rust backend will ingest remote settings every N seconds as
    354  // defined by this pref. Ingestion uses nsIUpdateTimerManager so the interval
    355  // will persist across app restarts. The default value is 24 hours, same as
    356  // the interval used by the desktop remote settings client.
    357  ["quicksuggest.rustIngestIntervalSeconds", 60 * 60 * 24],
    358 
    359  // Which Suggest settings to show in the settings UI. See
    360  // `QuickSuggest.SETTINGS_UI` for values.
    361  ["quicksuggest.settingsUi", 0],
    362 
    363  // We only show recent searches within the past 3 days by default.
    364  // Stored as a string as some code handle timestamp sized int's.
    365  ["recentsearches.expirationMs", (1000 * 60 * 60 * 24 * 3).toString()],
    366 
    367  // Feature gate pref for recent searches being shown in the urlbar.
    368  ["recentsearches.featureGate", true],
    369 
    370  // Store the time the last default engine changed so we can only show
    371  // recent searches since then.
    372  // Stored as a string as some code handle timestamp sized int's.
    373  ["recentsearches.lastDefaultChanged", "-1"],
    374 
    375  // The maximum number of recent searches we will show.
    376  ["recentsearches.maxResults", 5],
    377 
    378  // When true, URLs in the user's history that look like search result pages
    379  // are styled to look like search engine results instead of the usual history
    380  // results.
    381  ["restyleSearches", false],
    382 
    383  // Allow the result menu button to be reached with the Tab key.
    384  ["resultMenu.keyboardAccessible", true],
    385 
    386  // Feature gate pref for rich suggestions being shown in the urlbar.
    387  ["richSuggestions.featureGate", true],
    388 
    389  // If true, we show tail suggestions when available.
    390  ["richSuggestions.tail", true],
    391 
    392  // Disable the urlbar OneOff panel from being shown.
    393  ["scotchBonnet.disableOneOffs", false],
    394 
    395  // A short-circuit pref to enable all the features that are part of a
    396  // grouped release.
    397  ["scotchBonnet.enableOverride", true],
    398 
    399  // Allow searchmode to be persisted as the user navigates the
    400  // search host.
    401  ["scotchBonnet.persistSearchMode", false],
    402 
    403  // Feature gate pref for search restrict keywords being shown in the urlbar.
    404  ["searchRestrictKeywords.featureGate", false],
    405 
    406  // Hidden pref. Disables checks that prevent search tips being shown, thus
    407  // showing them every time the newtab page or the default search engine
    408  // homepage is opened.
    409  ["searchTips.test.ignoreShowLimits", false],
    410 
    411  // Feature gate pref for secondary actions being shown in the urlbar.
    412  ["secondaryActions.featureGate", false],
    413 
    414  // Maximum number of actions shown.
    415  ["secondaryActions.maxActionsShown", 3],
    416 
    417  // Alternative switch to tab implementation using secondaryActions.
    418  ["secondaryActions.switchToTab", false],
    419 
    420  // Whether to show each local search shortcut button in the view.
    421  ["shortcuts.bookmarks", true],
    422  ["shortcuts.tabs", true],
    423  ["shortcuts.history", true],
    424  ["shortcuts.actions", true],
    425 
    426  // Boolean to determine if the providers defined in `exposureResults`
    427  // should be displayed in search results. This can be set by a
    428  // Nimbus variable and is expected to be set via nimbus experiment
    429  // configuration. For the control branch of an experiment this would be
    430  // false and true for the treatment.
    431  ["showExposureResults", false],
    432 
    433  // Whether to show search suggestions before general results.
    434  ["showSearchSuggestionsFirst", true],
    435 
    436  // If true, show the search term in the Urlbar while on
    437  // a default search engine results page.
    438  ["showSearchTerms.enabled", true],
    439 
    440  // Global toggle for whether the show search terms feature
    441  // can be used at all, and enabled/disabled by the user.
    442  ["showSearchTerms.featureGate", false],
    443 
    444  // Whether speculative connections should be enabled.
    445  ["speculativeConnect.enabled", true],
    446 
    447  // If true, top sites may include sponsored ones.
    448  ["sponsoredTopSites", false],
    449 
    450  // Feature gate pref for realtime sports suggestions in the urlbar.
    451  ["sports.featureGate", false],
    452 
    453  // The minimum prefix length of sports keyword the user must type to trigger
    454  // the suggestion. 0 means the min length should be taken from Nimbus or
    455  // remote settings.
    456  ["sports.minKeywordLength", 0],
    457 
    458  // The number of times the user has clicked the "Show less frequently" command
    459  // for sports suggestions.
    460  ["sports.showLessFrequentlyCount", 0],
    461 
    462  // If `browser.urlbar.addons.featureGate` is true, this controls whether
    463  // addon suggestions are turned on.
    464  ["suggest.addons", true],
    465 
    466  // If `browser.urlbar.amp.featureGate` is true, this controls whether AMP
    467  // suggestions are turned on.
    468  ["suggest.amp", true],
    469 
    470  // Whether results will include the user's bookmarks.
    471  ["suggest.bookmark", true],
    472 
    473  // Whether results will include a calculator.
    474  ["suggest.calculator", false],
    475 
    476  // Whether results will include clipboard results.
    477  ["suggest.clipboard", true],
    478 
    479  // Whether results will include search engines (e.g. tab-to-search).
    480  ["suggest.engines", true],
    481 
    482  // If `browser.urlbar.flightStatus.featureGate` is true, this controls whether
    483  // flight status suggestions are turned on.
    484  ["suggest.flightStatus", true],
    485 
    486  // Whether results will include the user's history.
    487  ["suggest.history", true],
    488 
    489  // If `browser.urlbar.importantDates.featureGate` is true, this controls
    490  // whether important-dates suggestions are turned on.
    491  ["suggest.importantDates", true],
    492 
    493  // Whether results will include Market suggestions.
    494  ["suggest.market", true],
    495 
    496  // If `browser.urlbar.mdn.featureGate` is true, this controls whether
    497  // mdn suggestions are turned on.
    498  ["suggest.mdn", true],
    499 
    500  // Whether results will include switch-to-tab results.
    501  ["suggest.openpage", true],
    502 
    503  // Whether results will include QuickActions in the default search mode.
    504  ["suggest.quickactions", false],
    505 
    506  // Whether results will include Suggest suggestions.
    507  ["suggest.quicksuggest.all", false],
    508 
    509  // Whether results will include sponsored Suggest suggestions. Only relevant
    510  // if the `all` pref is true.
    511  ["suggest.quicksuggest.sponsored", false],
    512 
    513  // Whether results will include Realtime suggestion opt-in result.
    514  ["suggest.realtimeOptIn", true],
    515 
    516  // If `browser.urlbar.recentsearches.featureGate` is true, this controls whether
    517  // recentsearches are turned on.
    518  ["suggest.recentsearches", true],
    519 
    520  // Whether results will include synced tab results. The syncing of open tabs
    521  // must also be enabled, from Sync preferences.
    522  ["suggest.remotetab", true],
    523 
    524  // Whether results will include search suggestions.
    525  ["suggest.searches", false],
    526 
    527  // Whether results will include realtime sports suggestions.
    528  ["suggest.sports", true],
    529 
    530  // Whether results will include top sites and the view will open on focus.
    531  ["suggest.topsites", true],
    532 
    533  // If `browser.urlbar.trending.featureGate` is true, this controls whether
    534  // trending suggestions are turned on.
    535  ["suggest.trending", true],
    536 
    537  // If `browser.urlbar.weather.featureGate` is true, this controls whether
    538  // weather suggestions are turned on.
    539  ["suggest.weather", true],
    540 
    541  // If `browser.urlbar.wikipedia.featureGate` is true, this controls whether
    542  // Wikipedia suggestions are turned on.
    543  ["suggest.wikipedia", true],
    544 
    545  // If `browser.urlbar.yelp.featureGate` is true, this controls whether
    546  // Yelp suggestions are turned on.
    547  ["suggest.yelp", true],
    548 
    549  // If `browser.urlbar.yelpRealtime.featureGate` is true, this controls whether
    550  // Yelp realtime suggestions are turned on.
    551  ["suggest.yelpRealtime", true],
    552 
    553  // Whether history results with the same title and URL excluding the ref
    554  // will be deduplicated.
    555  ["deduplication.enabled", true],
    556 
    557  // How old history results have to be to be deduplicated.
    558  ["deduplication.thresholdDays", 0],
    559 
    560  // semanticHistory search query minLength threshold to be enabled.
    561  ["suggest.semanticHistory.minLength", 5],
    562 
    563  // When using switch to tabs, if set to true this will move the tab into the
    564  // active window.
    565  ["switchTabs.adoptIntoActiveWindow", false],
    566 
    567  // Controls whether searching for open tabs returns tabs from any container
    568  // or only from the current container.
    569  ["switchTabs.searchAllContainers", true],
    570 
    571  // The minimum number of characters needed to match a tab group name.
    572  ["tabGroups.minSearchLength", 1],
    573 
    574  // The number of remaining times the user can interact with tab-to-search
    575  // onboarding results before we stop showing them.
    576  ["tabToSearch.onboard.interactionsLeft", 3],
    577 
    578  // The number of times the user has been shown the onboarding search tip.
    579  ["tipShownCount.searchTip_onboard", 0],
    580 
    581  // The number of times the user has been shown the redirect search tip.
    582  ["tipShownCount.searchTip_redirect", 0],
    583 
    584  // Feature gate pref for trending suggestions in the urlbar.
    585  ["trending.featureGate", true],
    586 
    587  // The maximum number of trending results to show while not in search mode.
    588  ["trending.maxResultsNoSearchMode", 10],
    589 
    590  // The maximum number of trending results to show in search mode.
    591  ["trending.maxResultsSearchMode", 10],
    592 
    593  // Whether to only show trending results when the urlbar is in search
    594  // mode or when the user initially opens the urlbar without selecting
    595  // an engine.
    596  ["trending.requireSearchMode", false],
    597 
    598  // Remove 'https://' from url when urlbar is focused.
    599  ["trimHttps", false],
    600 
    601  // Remove redundant portions from URLs.
    602  ["trimURLs", true],
    603 
    604  // Enable the updated design combining the privacy and shield icon
    605  // and panels in the Urlbar.
    606  ["trustPanel.featureGate", false],
    607 
    608  // Whether unit conversion is enabled.
    609  ["unitConversion.enabled", false],
    610 
    611  // The index where we show unit conversion results.
    612  ["unitConversion.suggestedIndex", 1],
    613 
    614  // Untrim url, when urlbar is focused.
    615  // Note: This pref will be removed once the feature is stable.
    616  ["untrimOnUserInteraction.featureGate", false],
    617 
    618  // Whether or not Unified Search Button is shown always.
    619  ["unifiedSearchButton.always", false],
    620 
    621  // Feature gate pref for weather suggestions in the urlbar.
    622  ["weather.featureGate", true],
    623 
    624  // The minimum prefix length of a weather keyword the user must type to
    625  // trigger the suggestion. 0 means the min length should be taken from Nimbus
    626  // or remote settings.
    627  ["weather.minKeywordLength", 0],
    628 
    629  // The number of times the user has clicked the "Show less frequently" command
    630  // for weather suggestions.
    631  ["weather.showLessFrequentlyCount", 0],
    632 
    633  // Feature gate pref for Wikipedia suggestions (part of Suggest).
    634  ["wikipedia.featureGate", false],
    635 
    636  // Feature gate pref for Yelp suggestions in the urlbar.
    637  ["yelp.featureGate", false],
    638 
    639  // The minimum prefix length of a Yelp keyword the user must type to trigger
    640  // the suggestion. 0 means the min length should be taken from Nimbus.
    641  ["yelp.minKeywordLength", 4],
    642 
    643  // Whether Yelp suggestions will be served from the Suggest ML backend instead
    644  // of Rust.
    645  ["yelp.mlEnabled", false],
    646 
    647  // Whether Yelp suggestions should be shown as top picks. This is a fallback
    648  // pref for the `yelpSuggestPriority` Nimbus variable.
    649  ["yelp.priority", false],
    650 
    651  // Whether to distinguish service type subjects. If true, we show special
    652  // titile for the suggestion. This is a fallback pref for the
    653  // `yelpServiceResultDistinction` Nimbus variable.
    654  ["yelp.serviceResultDistinction", false],
    655 
    656  // The number of times the user has clicked the "Show less frequently" command
    657  // for Yelp suggestions.
    658  ["yelp.showLessFrequentlyCount", 0],
    659 
    660  // Feature gate pref for Yelp realtime suggestions in the urlbar.
    661  ["yelpRealtime.featureGate", false],
    662 
    663  // The minimum prefix length of a Yelp keyword the user must type to trigger
    664  // the suggestion. 0 means the min length should be taken from Nimbus or remote
    665  // settings.
    666  ["yelpRealtime.minKeywordLength", 0],
    667 
    668  // The number of times the user has clicked the "Show less frequently" command
    669  // for Yelp realtime suggestions.
    670  ["yelpRealtime.showLessFrequentlyCount", 0],
    671 ]);
    672 
    673 // This is defined separately to PREF_URLBAR_DEFAULTS, to avoid re-indenting
    674 // the array so that we can preserve blame.
    675 const PREF_URLBAR_DEFAULTS_MAP = new Map(PREF_URLBAR_DEFAULTS);
    676 
    677 const PREF_OTHER_DEFAULTS = new Map([
    678  ["browser.fixup.dns_first_for_single_words", false],
    679  ["browser.ml.enable", false],
    680  ["browser.search.openintab", false],
    681  ["browser.search.suggest.enabled", true],
    682  ["browser.search.suggest.enabled.private", false],
    683  ["browser.search.widget.new", false],
    684  ["keyword.enabled", true],
    685  ["security.insecure_connection_text.enabled", true],
    686  ["ui.popup.disable_autohide", false],
    687 ]);
    688 
    689 // Default values for Nimbus urlbar variables that do not have fallback prefs.
    690 // Variables with fallback prefs do not need to be defined here because their
    691 // defaults are the values of their fallbacks.
    692 const NIMBUS_DEFAULTS = {
    693  addonsShowLessFrequentlyCap: 0,
    694  quickSuggestScoreMap: null,
    695  realtimeMinKeywordLength: null,
    696  realtimeShowLessFrequentlyCap: null,
    697  weatherKeywordsMinimumLength: null,
    698  weatherShowLessFrequentlyCap: null,
    699  yelpMinKeywordLength: null,
    700  yelpSuggestNonPriorityIndex: null,
    701 };
    702 
    703 // Maps preferences under browser.urlbar.suggest to behavior names, as defined
    704 // in mozIPlacesAutoComplete.
    705 const SUGGEST_PREF_TO_BEHAVIOR = {
    706  history: "history",
    707  bookmark: "bookmark",
    708  openpage: "openpage",
    709  searches: "search",
    710 };
    711 
    712 const PREF_TYPES = new Map([
    713  ["boolean", "Bool"],
    714  ["float", "Float"],
    715  ["number", "Int"],
    716  ["string", "Char"],
    717 ]);
    718 
    719 /**
    720 * Builds the default result groups and returns the root group.  Result
    721 * groups determine the composition of results in the muxer, i.e., how they're
    722 * grouped and sorted.  Each group is an object that looks like this:
    723 *
    724 * @typedef {object} ResultGroup
    725 * @property {Values<typeof lazy.UrlbarUtils.RESULT_GROUP>} [group]
    726 *     This is defined only on groups without children, and it determines the
    727 *     result group that the group will contain.
    728 * @property {number} [maxResultCount]
    729 *     An optional maximum number of results the group can contain.  If it's
    730 *     not defined and the parent group does not define `flexChildren: true`,
    731 *     then the max is the parent's max.  If the parent group defines
    732 *     `flexChildren: true`, then `maxResultCount` is ignored.
    733 * @property {boolean} [flexChildren]
    734 *     If true, then child groups are "flexed", similar to flex in HTML.  Each
    735 *     child group should define the `flex` property (or, if they don't, `flex`
    736 *     is assumed to be zero).  `flex` is a number that defines the ratio of a
    737 *     child's result count to the total result count of all children.  More
    738 *     specifically, `flex: X` on a child means that the initial maximum result
    739 *     count of the child is `parentMaxResultCount * (X / N)`, where `N` is the
    740 *     sum of the `flex` values of all children.  If there are any child groups
    741 *     that cannot be completely filled, then the muxer will attempt to overfill
    742 *     the children that were completely filled, while still respecting their
    743 *     relative `flex` values.
    744 * @property {number} [flex]
    745 *     The flex value of the group.  This should be defined only on groups
    746 *     where the parent defines `flexChildren: true`.  See `flexChildren` for a
    747 *     discussion of flex.
    748 * @property {ResultGroup[]} [children]
    749 *     An array of child group objects.
    750 * @property {string} [orderBy]
    751 *     Results should be ordered descending by this payload property.
    752 *
    753 * @param {object} options
    754 * @param {boolean} options.showSearchSuggestionsFirst
    755 *   If true, the suggestions group will come before the general group.
    756 */
    757 function makeDefaultResultGroups({ showSearchSuggestionsFirst }) {
    758  /**
    759   * @type {ResultGroup}
    760   */
    761  let rootGroup = {
    762    children: [
    763      // heuristic
    764      {
    765        maxResultCount: 1,
    766        children: [
    767          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
    768          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION },
    769          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP },
    770          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX },
    771          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_ENGINE_ALIAS },
    772          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD },
    773          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
    774          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE },
    775          {
    776            group:
    777              lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_RESTRICT_KEYWORD_AUTOFILL,
    778          },
    779          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_HISTORY_URL },
    780          { group: lazy.UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
    781        ],
    782      },
    783      // extensions using the omnibox API
    784      {
    785        group: lazy.UrlbarUtils.RESULT_GROUP.OMNIBOX,
    786      },
    787    ],
    788  };
    789 
    790  // Prepare the parent group for suggestions and general.
    791  let mainGroup = {
    792    flexChildren: true,
    793    children: [
    794      // suggestions
    795      {
    796        children: [
    797          {
    798            flexChildren: true,
    799            children: [
    800              {
    801                // If `maxHistoricalSearchSuggestions` == 0, the muxer forces
    802                // `maxResultCount` to be zero and flex is ignored, per query.
    803                flex: 2,
    804                group: lazy.UrlbarUtils.RESULT_GROUP.FORM_HISTORY,
    805              },
    806              {
    807                flex: 99,
    808                group: lazy.UrlbarUtils.RESULT_GROUP.RECENT_SEARCH,
    809              },
    810              {
    811                flex: 4,
    812                group: lazy.UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION,
    813              },
    814            ],
    815          },
    816          {
    817            group: lazy.UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION,
    818          },
    819        ],
    820      },
    821      // general
    822      {
    823        group: lazy.UrlbarUtils.RESULT_GROUP.GENERAL_PARENT,
    824        children: [
    825          {
    826            availableSpan: 3,
    827            group: lazy.UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
    828          },
    829          {
    830            flexChildren: true,
    831            children: [
    832              {
    833                flex: 1,
    834                group: lazy.UrlbarUtils.RESULT_GROUP.REMOTE_TAB,
    835              },
    836              {
    837                flex: 2,
    838                group: lazy.UrlbarUtils.RESULT_GROUP.GENERAL,
    839                orderBy: "frecency",
    840              },
    841              {
    842                // We show relatively many about-page results because they're
    843                // only added for queries starting with "about:".
    844                flex: 2,
    845                group: lazy.UrlbarUtils.RESULT_GROUP.ABOUT_PAGES,
    846              },
    847              {
    848                flex: 99,
    849                group: lazy.UrlbarUtils.RESULT_GROUP.RESTRICT_SEARCH_KEYWORD,
    850              },
    851            ],
    852          },
    853          {
    854            group: lazy.UrlbarUtils.RESULT_GROUP.INPUT_HISTORY,
    855          },
    856        ],
    857      },
    858    ],
    859  };
    860  if (!showSearchSuggestionsFirst) {
    861    mainGroup.children.reverse();
    862  }
    863  mainGroup.children[0].flex = 2;
    864  mainGroup.children[1].flex = 1;
    865  rootGroup.children.push(mainGroup);
    866 
    867  return rootGroup;
    868 }
    869 
    870 /**
    871 * Preferences class.  The exported object is a singleton instance.
    872 */
    873 class Preferences {
    874  /**
    875   * Constructor
    876   */
    877  constructor() {
    878    this._map = new Map();
    879    this.QueryInterface = ChromeUtils.generateQI([
    880      "nsIObserver",
    881      "nsISupportsWeakReference",
    882    ]);
    883 
    884    Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true);
    885    for (let pref of PREF_OTHER_DEFAULTS.keys()) {
    886      Services.prefs.addObserver(pref, this, true);
    887    }
    888    this._observerWeakRefs = [];
    889    this.addObserver(this);
    890 
    891    // These prefs control the value of the shouldHandOffToSearchMode pref. They
    892    // are exposed as a class variable so UrlbarPrefs observers can watch for
    893    // changes in these prefs.
    894    this.shouldHandOffToSearchModePrefs = [
    895      "keyword.enabled",
    896      "suggest.searches",
    897    ];
    898 
    899    lazy.NimbusFeatures.urlbar.onUpdate(() => this._onNimbusUpdate());
    900  }
    901 
    902  /**
    903   * Returns the value for the preference with the given name.
    904   * For preferences in the "browser.urlbar."" branch, the passed-in name
    905   * should be relative to the branch. It's also possible to get prefs from the
    906   * PREF_OTHER_DEFAULTS Map, specifying their full name.
    907   *
    908   * @param {string} pref
    909   *        The name of the preference to get.
    910   * @returns {*} The preference value.
    911   */
    912  get(pref) {
    913    let value = this._map.get(pref);
    914    if (value === undefined) {
    915      value = this._getPrefValue(pref);
    916      this._map.set(pref, value);
    917    }
    918    return value;
    919  }
    920 
    921  /**
    922   * Sets the value for the preference with the given name.
    923   * For preferences in the "browser.urlbar."" branch, the passed-in name
    924   * should be relative to the branch. It's also possible to set prefs from the
    925   * PREF_OTHER_DEFAULTS Map, specifying their full name.
    926   *
    927   * @param {string} pref
    928   *        The name of the preference to set.
    929   * @param {*} value The preference value.
    930   */
    931  set(pref, value) {
    932    let { defaultValue, set } = this._getPrefDescriptor(pref);
    933    if (typeof value != typeof defaultValue) {
    934      throw new Error(`Invalid value type ${typeof value} for pref ${pref}`);
    935    }
    936    set(pref, value);
    937  }
    938 
    939  /**
    940   * Adds a value to a preference that handles multiple comma-separated values.
    941   * Throws an error if the preference does not have a comma-separated value.
    942   *
    943   * @param {string} pref
    944   *   The name of the preference to set.
    945   * @param {*} value
    946   *   The preference value.
    947   */
    948  add(pref, value) {
    949    let maybeSet = this._getPrefValue(pref);
    950 
    951    if (!(maybeSet instanceof Set)) {
    952      throw new Error(
    953        `The pref ${pref} should handle the values as Set but '${typeof maybeSet}'`
    954      );
    955    }
    956 
    957    maybeSet.add(value);
    958    this.set(pref, [...maybeSet].join(","));
    959  }
    960 
    961  /**
    962   * Clears the value for the preference with the given name.
    963   *
    964   * @param {string} pref
    965   *        The name of the preference to clear.
    966   */
    967  clear(pref) {
    968    let { clear } = this._getPrefDescriptor(pref);
    969    clear(pref);
    970  }
    971 
    972  /**
    973   * Returns whether the given preference has a value on the user branch.
    974   *
    975   * @param {string} pref
    976   *   The name of the preference.
    977   * @returns {boolean}
    978   *   Whether the pref has a value on the user branch.
    979   */
    980  hasUserValue(pref) {
    981    let { hasUserValue } = this._getPrefDescriptor(pref);
    982    return hasUserValue(pref);
    983  }
    984 
    985  /**
    986   * Gets a pref but allows the `scotchBonnet.enableOverride` pref to
    987   * short circuit them so one pref can be used to enable multiple
    988   * features.
    989   *
    990   * @param {string} pref
    991   *        The name of the preference to clear.
    992   * @returns {*} The preference value.
    993   */
    994  getScotchBonnetPref(pref) {
    995    return this.get("scotchBonnet.enableOverride") || this.get(pref);
    996  }
    997 
    998  getResultGroups({ context }) {
    999    // We try to cache result groups so we don't have to rebuild them each time.
   1000    // This key may be modified per each SAP, and will track the cached groups,
   1001    // any additionally used condition must be added to the key.
   1002    let key = context.sapName;
   1003    switch (context.sapName) {
   1004      case "urlbar": {
   1005        let showSearchSuggestionsFirst =
   1006          context.searchString ||
   1007          (!this.get("suggest.trending") &&
   1008            !this.get("suggest.recentsearches"));
   1009 
   1010        let inSearchEngineMode = !!context.searchMode?.engineName;
   1011 
   1012        // If we're in a case were search suggestions would be shown first, but not
   1013        // in search engine mode, then just use the user preference.
   1014        if (!inSearchEngineMode && showSearchSuggestionsFirst) {
   1015          showSearchSuggestionsFirst = this.get("showSearchSuggestionsFirst");
   1016        }
   1017 
   1018        key += showSearchSuggestionsFirst;
   1019        return this.#getOrCacheResultGroups(key, () =>
   1020          makeDefaultResultGroups({ showSearchSuggestionsFirst })
   1021        );
   1022      }
   1023      case "searchbar": {
   1024        // This is a temporary placeholder until searchbar gets its own config.
   1025        return this.#getOrCacheResultGroups(key, () =>
   1026          makeDefaultResultGroups({ showSearchSuggestionsFirst: true })
   1027        );
   1028      }
   1029      case "smartbar": {
   1030        // This is a temporary placeholder until smartbar gets its own config.
   1031        return this.#getOrCacheResultGroups(key, () =>
   1032          makeDefaultResultGroups({ showSearchSuggestionsFirst: false })
   1033        );
   1034      }
   1035      default: {
   1036        throw new Error(`Unknown SAP name: ${context.sapName}`);
   1037      }
   1038    }
   1039  }
   1040 
   1041  /**
   1042   * Adds a preference observer.  Observers are held weakly.
   1043   *
   1044   * @param {object} observer
   1045   *        An object that may optionally implement one or both methods:
   1046   *         - `onPrefChanged` invoked when one of the preferences listed here
   1047   *           change. It will be passed the pref name.  For prefs in the
   1048   *           `browser.urlbar.` branch, the name will be relative to the branch.
   1049   *           For other prefs, the name will be the full name.
   1050   *         - `onNimbusChanged` invoked when a Nimbus value changes. It will be
   1051   *           passed the name of the changed Nimbus variable and the new value.
   1052   */
   1053  addObserver(observer) {
   1054    this._observerWeakRefs.push(Cu.getWeakReference(observer));
   1055  }
   1056 
   1057  /**
   1058   * Removes a preference observer.
   1059   *
   1060   * @param {object} observer
   1061   *   An observer previously added with `addObserver()`.
   1062   */
   1063  removeObserver(observer) {
   1064    for (let i = 0; i < this._observerWeakRefs.length; i++) {
   1065      let obs = this._observerWeakRefs[i].get();
   1066      if (obs && obs == observer) {
   1067        this._observerWeakRefs.splice(i, 1);
   1068        break;
   1069      }
   1070    }
   1071  }
   1072 
   1073  /**
   1074   * Observes preference changes.
   1075   *
   1076   * @param {nsISupports} subject
   1077   *   The subject of the notification.
   1078   * @param {string} topic
   1079   *   The topic of the notification.
   1080   * @param {string} data
   1081   *   The data attached to the notification.
   1082   */
   1083  observe(subject, topic, data) {
   1084    let pref = data.replace(PREF_URLBAR_BRANCH, "");
   1085    if (!PREF_URLBAR_DEFAULTS_MAP.has(pref) && !PREF_OTHER_DEFAULTS.has(pref)) {
   1086      return;
   1087    }
   1088    this.#notifyObservers("onPrefChanged", pref);
   1089  }
   1090 
   1091  /**
   1092   * Called when a pref tracked by UrlbarPrefs changes.
   1093   *
   1094   * @param {string} pref
   1095   *        The name of the pref, relative to `browser.urlbar.` if the pref is
   1096   *        in that branch.
   1097   */
   1098  onPrefChanged(pref) {
   1099    this._map.delete(pref);
   1100 
   1101    // Some prefs may influence others.
   1102    switch (pref) {
   1103      case "autoFill.adaptiveHistory.useCountThreshold":
   1104        this._map.delete("autoFillAdaptiveHistoryUseCountThreshold");
   1105        return;
   1106      case "showSearchSuggestionsFirst":
   1107        this.#cachedResultGroups.clear();
   1108        return;
   1109    }
   1110 
   1111    if (pref.startsWith("suggest.")) {
   1112      this._map.delete("defaultBehavior");
   1113    }
   1114 
   1115    if (this.shouldHandOffToSearchModePrefs.includes(pref)) {
   1116      this._map.delete("shouldHandOffToSearchMode");
   1117    }
   1118  }
   1119 
   1120  /**
   1121   * Called when the `NimbusFeatures.urlbar` value changes.
   1122   */
   1123  _onNimbusUpdate() {
   1124    let oldNimbus = this._clearNimbusCache();
   1125    let newNimbus = this._nimbus;
   1126 
   1127    // Callback to observers having onNimbusChanged.
   1128    let variableNames = new Set(Object.keys(oldNimbus));
   1129    for (let name of Object.keys(newNimbus)) {
   1130      variableNames.add(name);
   1131    }
   1132    for (let name of variableNames) {
   1133      if (
   1134        oldNimbus.hasOwnProperty(name) != newNimbus.hasOwnProperty(name) ||
   1135        oldNimbus[name] !== newNimbus[name]
   1136      ) {
   1137        this.#notifyObservers("onNimbusChanged", name, newNimbus[name]);
   1138      }
   1139    }
   1140  }
   1141 
   1142  /**
   1143   * Clears cached Nimbus variables. The cache will be repopulated the next time
   1144   * `_nimbus` is accessed.
   1145   *
   1146   * @returns {object}
   1147   *   The value of the cache before it was cleared. It's an object that maps
   1148   *   from variable names to values.
   1149   */
   1150  _clearNimbusCache() {
   1151    let nimbus = this.__nimbus;
   1152    if (nimbus) {
   1153      for (let key of Object.keys(nimbus)) {
   1154        this._map.delete(key);
   1155      }
   1156      this.__nimbus = null;
   1157    }
   1158    return nimbus || {};
   1159  }
   1160 
   1161  get _nimbus() {
   1162    if (!this.__nimbus) {
   1163      this.__nimbus = lazy.NimbusFeatures.urlbar.getAllVariables({
   1164        defaultValues: NIMBUS_DEFAULTS,
   1165      });
   1166    }
   1167    return this.__nimbus;
   1168  }
   1169 
   1170  /**
   1171   * Returns the raw value of the given preference straight from Services.prefs.
   1172   *
   1173   * @param {string} pref
   1174   *        The name of the preference to get.
   1175   * @returns {*} The raw preference value.
   1176   */
   1177  _readPref(pref) {
   1178    let { defaultValue, get } = this._getPrefDescriptor(pref);
   1179    return get(pref, defaultValue);
   1180  }
   1181 
   1182  /**
   1183   * Returns a validated and/or fixed-up value of the given preference.  The
   1184   * value may be validated for correctness, or it might be converted into a
   1185   * different value that is easier to work with than the actual value stored in
   1186   * the preferences branch.  Not all preferences require validation or fixup.
   1187   *
   1188   * The values returned from this method are the values that are made public by
   1189   * this module.
   1190   *
   1191   * @param {string} pref
   1192   *        The name of the preference to get.
   1193   * @returns {*} The validated and/or fixed-up preference value.
   1194   */
   1195  _getPrefValue(pref) {
   1196    switch (pref) {
   1197      case "shortcuts.actions": {
   1198        return this.get("scotchBonnet.enableOverride") && this._readPref(pref);
   1199      }
   1200      case "defaultBehavior": {
   1201        let val = 0;
   1202        for (let type of Object.keys(SUGGEST_PREF_TO_BEHAVIOR)) {
   1203          let behavior = `BEHAVIOR_${SUGGEST_PREF_TO_BEHAVIOR[
   1204            type
   1205          ].toUpperCase()}`;
   1206          val |=
   1207            this.get("suggest." + type) && Ci.mozIPlacesAutoComplete[behavior];
   1208        }
   1209        return val;
   1210      }
   1211      case "shouldHandOffToSearchMode":
   1212        return this.shouldHandOffToSearchModePrefs.some(
   1213          prefName => !this.get(prefName)
   1214        );
   1215      case "autoFillAdaptiveHistoryUseCountThreshold": {
   1216        const nimbusValue =
   1217          this._nimbus.autoFillAdaptiveHistoryUseCountThreshold;
   1218        return nimbusValue === undefined
   1219          ? this.get("autoFill.adaptiveHistory.useCountThreshold")
   1220          : parseFloat(nimbusValue);
   1221      }
   1222      case "exposureResults":
   1223      case "keywordExposureResults":
   1224      case "quicksuggest.dynamicSuggestionTypes":
   1225      case "quicksuggest.realtimeOptIn.dismissTypes":
   1226      case "quicksuggest.realtimeOptIn.notNowTypes":
   1227        return new Set(
   1228          this._readPref(pref)
   1229            .split(",")
   1230            .map(s => s.trim())
   1231            .filter(s => !!s)
   1232        );
   1233    }
   1234    return this._readPref(pref);
   1235  }
   1236 
   1237  /**
   1238   * Returns a descriptor of the given preference.
   1239   *
   1240   * @param {string} pref The preference to examine.
   1241   * @returns {object} An object describing the pref with the following shape:
   1242   *          { defaultValue, get, set, clear }
   1243   */
   1244  _getPrefDescriptor(pref) {
   1245    let branch = Services.prefs.getBranch(PREF_URLBAR_BRANCH);
   1246    let defaultValue = PREF_URLBAR_DEFAULTS_MAP.get(pref);
   1247    if (defaultValue === undefined) {
   1248      branch = Services.prefs;
   1249      defaultValue = PREF_OTHER_DEFAULTS.get(pref);
   1250      if (defaultValue === undefined) {
   1251        let nimbus = this._getNimbusDescriptor(pref);
   1252        if (nimbus) {
   1253          return nimbus;
   1254        }
   1255        throw new Error("Trying to access an unknown pref " + pref);
   1256      }
   1257    }
   1258 
   1259    let type;
   1260    if (!Array.isArray(defaultValue)) {
   1261      type = PREF_TYPES.get(typeof defaultValue);
   1262    } else {
   1263      if (defaultValue.length != 2) {
   1264        throw new Error("Malformed pref def: " + pref);
   1265      }
   1266      [defaultValue, type] = defaultValue;
   1267      type = PREF_TYPES.get(type);
   1268    }
   1269    if (!type) {
   1270      throw new Error("Unknown pref type: " + pref);
   1271    }
   1272    return {
   1273      defaultValue,
   1274      get: branch[`get${type}Pref`],
   1275      // Float prefs are stored as Char.
   1276      set: branch[`set${type == "Float" ? "Char" : type}Pref`],
   1277      clear: branch.clearUserPref,
   1278      hasUserValue: branch.prefHasUserValue,
   1279    };
   1280  }
   1281 
   1282  /**
   1283   * Returns a descriptor for the given Nimbus property, if it exists.
   1284   *
   1285   * @param {string} name
   1286   *   The name of the desired property in the object returned from
   1287   *   NimbusFeatures.urlbar.getAllVariables().
   1288   * @returns {object}
   1289   *   An object describing the property's value with the following shape (same
   1290   *   as _getPrefDescriptor()):
   1291   *     { defaultValue, get, set, clear }
   1292   *   If the property doesn't exist, null is returned.
   1293   */
   1294  _getNimbusDescriptor(name) {
   1295    if (!this._nimbus.hasOwnProperty(name)) {
   1296      return null;
   1297    }
   1298    return {
   1299      defaultValue: this._nimbus[name],
   1300      get: () => this._nimbus[name],
   1301      set() {
   1302        throw new Error(`'${name}' is a Nimbus value and cannot be set`);
   1303      },
   1304      clear() {
   1305        throw new Error(`'${name}' is a Nimbus value and cannot be cleared`);
   1306      },
   1307      hasUserValue() {
   1308        throw new Error(
   1309          `'${name}' is a Nimbus value and does not have a user value`
   1310        );
   1311      },
   1312    };
   1313  }
   1314 
   1315  /**
   1316   * Return whether or not persisted search terms is enabled.
   1317   *
   1318   * @returns {boolean} true: if enabled.
   1319   */
   1320  isPersistedSearchTermsEnabled() {
   1321    return (
   1322      this.getScotchBonnetPref("showSearchTerms.featureGate") &&
   1323      this.get("showSearchTerms.enabled") &&
   1324      !lazy.CustomizableUI.getPlacementOfWidget("search-container")
   1325    );
   1326  }
   1327 
   1328  /**
   1329   * @type {Map<string, ResultGroup>}
   1330   * Result groups cached by search access point and params used to build them.
   1331   */
   1332  #cachedResultGroups = new Map();
   1333  #getOrCacheResultGroups(key, builder) {
   1334    let groups = this.#cachedResultGroups.get(key);
   1335    if (!groups) {
   1336      groups = builder();
   1337      this.#cachedResultGroups.set(key, groups);
   1338    }
   1339    return groups;
   1340  }
   1341 
   1342  #notifyObservers(method, changed, ...rest) {
   1343    for (let i = 0; i < this._observerWeakRefs.length; ) {
   1344      let observer = this._observerWeakRefs[i].get();
   1345      if (!observer) {
   1346        // The observer has been GC'ed, so remove it from our list.
   1347        this._observerWeakRefs.splice(i, 1);
   1348        continue;
   1349      }
   1350      if (method in observer) {
   1351        try {
   1352          observer[method](changed, ...rest);
   1353        } catch (ex) {
   1354          console.error(ex);
   1355        }
   1356      }
   1357      ++i;
   1358    }
   1359  }
   1360 }
   1361 
   1362 export var UrlbarPrefs = new Preferences();