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"use strict"; 6 7/** 8 * This module exports the UrlbarPrefs singleton, which manages preferences for 9 * the urlbar. It also provides access to urlbar Nimbus features as if they are 10 * preferences. 11 */ 12 13var EXPORTED_SYMBOLS = ["UrlbarPrefs", "UrlbarPrefsObserver"]; 14 15const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); 16const { XPCOMUtils } = ChromeUtils.import( 17 "resource://gre/modules/XPCOMUtils.jsm" 18); 19 20XPCOMUtils.defineLazyModuleGetters(this, { 21 NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm", 22 UrlbarUtils: "resource:///modules/UrlbarUtils.jsm", 23}); 24 25const PREF_URLBAR_BRANCH = "browser.urlbar."; 26 27// Prefs are defined as [pref name, default value] or [pref name, [default 28// value, type]]. In the former case, the getter method name is inferred from 29// the typeof the default value. 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.js 35const PREF_URLBAR_DEFAULTS = new Map([ 36 // Whether we announce to screen readers when tab-to-search results are 37 // inserted. 38 ["accessibility.tabToSearch.announceResults", true], 39 40 // "Autofill" is the name of the feature that automatically completes domains 41 // and URLs that the user has visited as the user is typing them in the urlbar 42 // textbox. If false, autofill will be disabled. 43 ["autoFill", true], 44 45 // If true, the domains of the user's installed search engines will be 46 // autofilled even if the user hasn't actually visited them. 47 ["autoFill.searchEngines", false], 48 49 // Affects the frecency threshold of the autofill algorithm. The threshold is 50 // the mean of all origin frecencies plus one standard deviation multiplied by 51 // this value. See UrlbarProviderPlaces. 52 ["autoFill.stddevMultiplier", [0.0, "float"]], 53 54 // Whether using `ctrl` when hitting return/enter in the URL bar 55 // (or clicking 'go') should prefix 'www.' and suffix 56 // browser.fixup.alternate.suffix to the URL bar value prior to 57 // navigating. 58 ["ctrlCanonizesURLs", true], 59 60 // Whether copying the entire URL from the location bar will put a human 61 // readable (percent-decoded) URL on the clipboard. 62 ["decodeURLsOnCopy", false], 63 64 // The amount of time (ms) to wait after the user has stopped typing before 65 // fetching results. However, we ignore this for the very first result (the 66 // "heuristic" result). We fetch it as fast as possible. 67 ["delay", 50], 68 69 // Some performance tests disable this because extending the urlbar needs 70 // layout information that we can't get before the first paint. (Or we could 71 // but this would mean flushing layout.) 72 ["disableExtendForTests", false], 73 74 // Ensure we use trailing dots for DNS lookups for single words that could 75 // be hosts. 76 ["dnsResolveFullyQualifiedNames", true], 77 78 // Controls when to DNS resolve single word search strings, after they were 79 // searched for. If the string is resolved as a valid host, show a 80 // "Did you mean to go to 'host'" prompt. 81 // 0 - never resolve; 1 - use heuristics (default); 2 - always resolve 82 ["dnsResolveSingleWordsAfterSearch", 1], 83 84 // Whether telemetry events should be recorded. 85 ["eventTelemetry.enabled", false], 86 87 // Whether we expand the font size when when the urlbar is 88 // focused. 89 ["experimental.expandTextOnFocus", false], 90 91 // Whether the urlbar displays a permanent search button. 92 ["experimental.searchButton", false], 93 94 // When we send events to extensions, we wait this amount of time in 95 // milliseconds for them to respond before timing out. 96 ["extension.timeout", 400], 97 98 // When true, `javascript:` URLs are not included in search results. 99 ["filter.javascript", true], 100 101 // Applies URL highlighting and other styling to the text in the urlbar input. 102 ["formatting.enabled", true], 103 104 // Whether the results panel should be kept open during IME composition. 105 ["keepPanelOpenDuringImeComposition", false], 106 107 // For search suggestion results, we truncate the user's search string to this 108 // number of characters before fetching results. 109 ["maxCharsForSearchSuggestions", 20], 110 111 // The maximum number of form history results to include. 112 ["maxHistoricalSearchSuggestions", 0], 113 114 // The maximum number of results in the urlbar popup. 115 ["maxRichResults", 10], 116 117 // Whether addresses and search results typed into the address bar 118 // should be opened in new tabs by default. 119 ["openintab", false], 120 121 // When true, URLs in the user's history that look like search result pages 122 // are styled to look like search engine results instead of the usual history 123 // results. 124 ["restyleSearches", false], 125 126 // Controls the composition of results. The default value is computed by 127 // calling: 128 // makeResultBuckets({ 129 // showSearchSuggestionsFirst: UrlbarPrefs.get( 130 // "showSearchSuggestionsFirst" 131 // ), 132 // }); 133 // The value of this pref is a JSON string of the root bucket. See below. 134 ["resultGroups", ""], 135 136 // If true, we show tail suggestions when available. 137 ["richSuggestions.tail", true], 138 139 // Hidden pref. Disables checks that prevent search tips being shown, thus 140 // showing them every time the newtab page or the default search engine 141 // homepage is opened. 142 ["searchTips.test.ignoreShowLimits", false], 143 144 // Whether to show each local search shortcut button in the view. 145 ["shortcuts.bookmarks", true], 146 ["shortcuts.tabs", true], 147 ["shortcuts.history", true], 148 149 // Whether to show search suggestions before general results. 150 ["showSearchSuggestionsFirst", true], 151 152 // Whether speculative connections should be enabled. 153 ["speculativeConnect.enabled", true], 154 155 // Whether results will include the user's bookmarks. 156 ["suggest.bookmark", true], 157 158 // Whether results will include the user's history. 159 ["suggest.history", true], 160 161 // Whether results will include switch-to-tab results. 162 ["suggest.openpage", true], 163 164 // Whether results will include search suggestions. 165 ["suggest.searches", false], 166 167 // Whether results will include search engines (e.g. tab-to-search). 168 ["suggest.engines", true], 169 170 // Whether results will include top sites and the view will open on focus. 171 ["suggest.topsites", true], 172 173 // Whether results will include a calculator. 174 ["suggest.calculator", false], 175 176 // Whether results will include QuickSuggest suggestions. 177 ["suggest.quicksuggest", true], 178 179 // Whether the user has seen the onboarding dialog. 180 ["quicksuggest.showedOnboardingDialog", false], 181 182 // Count the restarts before showing the onboarding dialog. 183 ["quicksuggest.seenRestarts", 0], 184 185 // Whether to show QuickSuggest related logs. 186 ["quicksuggest.log", false], 187 188 // When using switch to tabs, if set to true this will move the tab into the 189 // active window. 190 ["switchTabs.adoptIntoActiveWindow", false], 191 192 // The number of remaining times the user can interact with tab-to-search 193 // onboarding results before we stop showing them. 194 ["tabToSearch.onboard.interactionsLeft", 3], 195 196 // The number of times the user has been shown the onboarding search tip. 197 ["tipShownCount.searchTip_onboard", 0], 198 199 // The number of times the user has been shown the redirect search tip. 200 ["tipShownCount.searchTip_redirect", 0], 201 202 // Remove redundant portions from URLs. 203 ["trimURLs", true], 204 205 // If true, top sites may include sponsored ones. 206 ["sponsoredTopSites", false], 207 208 // Whether unit conversion is enabled. 209 ["unitConversion.enabled", false], 210 211 // The index where we show unit conversion results. 212 ["unitConversion.suggestedIndex", 1], 213 214 // Results will include a built-in set of popular domains when this is true. 215 ["usepreloadedtopurls.enabled", false], 216 217 // After this many days from the profile creation date, the built-in set of 218 // popular domains will no longer be included in the results. 219 ["usepreloadedtopurls.expire_days", 14], 220 221 // Controls the empty search behavior in Search Mode: 222 // 0 - Show nothing 223 // 1 - Show search history 224 // 2 - Show search and browsing history 225 ["update2.emptySearchBehavior", 0], 226]); 227const PREF_OTHER_DEFAULTS = new Map([ 228 ["browser.fixup.dns_first_for_single_words", false], 229 ["browser.search.suggest.enabled", true], 230 ["browser.search.suggest.enabled.private", false], 231 ["keyword.enabled", true], 232 ["ui.popup.disable_autohide", false], 233]); 234 235// Maps preferences under browser.urlbar.suggest to behavior names, as defined 236// in mozIPlacesAutoComplete. 237const SUGGEST_PREF_TO_BEHAVIOR = { 238 history: "history", 239 bookmark: "bookmark", 240 openpage: "openpage", 241 searches: "search", 242}; 243 244const PREF_TYPES = new Map([ 245 ["boolean", "Bool"], 246 ["float", "Float"], 247 ["number", "Int"], 248 ["string", "Char"], 249]); 250 251/** 252 * Builds the standard result buckets and returns the root bucket. Result 253 * buckets determine the composition of results in the muxer, i.e., how they're 254 * grouped and sorted. Each bucket is an object that looks like this: 255 * 256 * { 257 * {UrlbarUtils.RESULT_GROUP} [group] 258 * This is defined only on buckets without children, and it determines the 259 * result group that the bucket will contain. 260 * {number} [maxResultCount] 261 * An optional maximum number of results the bucket can contain. If it's 262 * not defined and the parent bucket does not define `flexChildren: true`, 263 * then the max is the parent's max. If the parent bucket defines 264 * `flexChildren: true`, then `maxResultCount` is ignored. 265 * {boolean} [flexChildren] 266 * If true, then child buckets are "flexed", similar to flex in HTML. Each 267 * child bucket should define the `flex` property (or, if they don't, `flex` 268 * is assumed to be zero). `flex` is a number that defines the ratio of a 269 * child's result count to the total result count of all children. More 270 * specifically, `flex: X` on a child means that the initial maximum result 271 * count of the child is `parentMaxResultCount * (X / N)`, where `N` is the 272 * sum of the `flex` values of all children. If there are any child buckets 273 * that cannot be completely filled, then the muxer will attempt to overfill 274 * the children that were completely filled, while still respecting their 275 * relative `flex` values. 276 * {number} [flex] 277 * The flex value of the bucket. This should be defined only on buckets 278 * where the parent defines `flexChildren: true`. See `flexChildren` for a 279 * discussion of flex. 280 * {array} [children] 281 * An array of child bucket objects. 282 * } 283 * 284 * @param {boolean} showSearchSuggestionsFirst 285 * If true, the suggestions bucket will come before the general bucket. 286 * @returns {object} 287 * The root bucket. 288 */ 289function makeResultBuckets({ showSearchSuggestionsFirst }) { 290 let rootBucket = { 291 children: [ 292 // heuristic 293 { 294 maxResultCount: 1, 295 children: [ 296 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST }, 297 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION }, 298 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP }, 299 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX }, 300 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_ENGINE_ALIAS }, 301 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_BOOKMARK_KEYWORD }, 302 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL }, 303 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_PRELOADED }, 304 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE }, 305 { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK }, 306 ], 307 }, 308 // extensions using the omnibox API 309 { 310 group: UrlbarUtils.RESULT_GROUP.OMNIBOX, 311 availableSpan: UrlbarUtils.MAX_OMNIBOX_RESULT_COUNT - 1, 312 }, 313 ], 314 }; 315 316 // Prepare the parent bucket for suggestions and general. 317 let mainBucket = { 318 flexChildren: true, 319 children: [ 320 // suggestions 321 { 322 children: [ 323 { 324 flexChildren: true, 325 children: [ 326 { 327 // If `maxHistoricalSearchSuggestions` == 0, the muxer forces 328 // `maxResultCount` to be zero and flex is ignored, per query. 329 flex: 2, 330 group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY, 331 }, 332 { 333 flex: 4, 334 group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION, 335 }, 336 ], 337 }, 338 { 339 group: UrlbarUtils.RESULT_GROUP.TAIL_SUGGESTION, 340 }, 341 ], 342 }, 343 // general 344 { 345 children: [ 346 { 347 availableSpan: 3, 348 group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY, 349 }, 350 { 351 flexChildren: true, 352 children: [ 353 { 354 flex: 1, 355 group: UrlbarUtils.RESULT_GROUP.REMOTE_TAB, 356 }, 357 { 358 flex: 2, 359 group: UrlbarUtils.RESULT_GROUP.GENERAL, 360 }, 361 { 362 // We show relatively many about-page results because they're 363 // only added for queries starting with "about:". 364 flex: 2, 365 group: UrlbarUtils.RESULT_GROUP.ABOUT_PAGES, 366 }, 367 { 368 flex: 1, 369 group: UrlbarUtils.RESULT_GROUP.PRELOADED, 370 }, 371 ], 372 }, 373 { 374 group: UrlbarUtils.RESULT_GROUP.INPUT_HISTORY, 375 }, 376 ], 377 }, 378 ], 379 }; 380 if (!showSearchSuggestionsFirst) { 381 mainBucket.children.reverse(); 382 } 383 mainBucket.children[0].flex = 2; 384 mainBucket.children[1].flex = 1; 385 rootBucket.children.push(mainBucket); 386 387 return rootBucket; 388} 389 390/** 391 * Preferences class. The exported object is a singleton instance. 392 */ 393class Preferences { 394 /** 395 * Constructor 396 */ 397 constructor() { 398 this._map = new Map(); 399 this.QueryInterface = ChromeUtils.generateQI([ 400 "nsIObserver", 401 "nsISupportsWeakReference", 402 ]); 403 Services.prefs.addObserver(PREF_URLBAR_BRANCH, this, true); 404 for (let pref of PREF_OTHER_DEFAULTS.keys()) { 405 Services.prefs.addObserver(pref, this, true); 406 } 407 this._observerWeakRefs = []; 408 this.addObserver(this); 409 NimbusFeatures.urlbar.onUpdate(() => this._onNimbusUpdate()); 410 } 411 412 /** 413 * Returns the value for the preference with the given name. 414 * For preferences in the "browser.urlbar."" branch, the passed-in name 415 * should be relative to the branch. It's also possible to get prefs from the 416 * PREF_OTHER_DEFAULTS Map, specifying their full name. 417 * 418 * @param {string} pref 419 * The name of the preference to get. 420 * @returns {*} The preference value. 421 */ 422 get(pref) { 423 let value = this._map.get(pref); 424 if (value === undefined) { 425 value = this._getPrefValue(pref); 426 this._map.set(pref, value); 427 } 428 return value; 429 } 430 431 /** 432 * Sets the value for the preference with the given name. 433 * For preferences in the "browser.urlbar."" branch, the passed-in name 434 * should be relative to the branch. It's also possible to set prefs from the 435 * PREF_OTHER_DEFAULTS Map, specifying their full name. 436 * 437 * @param {string} pref 438 * The name of the preference to set. 439 * @param {*} value The preference value. 440 */ 441 set(pref, value) { 442 let { defaultValue, set } = this._getPrefDescriptor(pref); 443 if (typeof value != typeof defaultValue) { 444 throw new Error(`Invalid value type ${typeof value} for pref ${pref}`); 445 } 446 set(pref, value); 447 } 448 449 /** 450 * Clears the value for the preference with the given name. 451 * 452 * @param {string} pref 453 * The name of the preference to clear. 454 */ 455 clear(pref) { 456 let { clear } = this._getPrefDescriptor(pref); 457 clear(pref); 458 } 459 460 /** 461 * Builds the standard result buckets. See makeResultBuckets. 462 * 463 * @param {object} options 464 * See makeResultBuckets. 465 * @returns {object} 466 * The root bucket. 467 */ 468 makeResultBuckets(options) { 469 return makeResultBuckets(options); 470 } 471 472 /** 473 * Sets the value of the resultGroups pref to the current default buckets. 474 * This should be called from BrowserGlue._migrateUI when the default buckets 475 * are modified. 476 */ 477 migrateResultBuckets() { 478 this.set( 479 "resultGroups", 480 JSON.stringify( 481 makeResultBuckets({ 482 showSearchSuggestionsFirst: this.get("showSearchSuggestionsFirst"), 483 }) 484 ) 485 ); 486 } 487 488 /** 489 * Adds a preference observer. Observers are held weakly. 490 * 491 * @param {object} observer 492 * An object that must have a method named `onPrefChanged`, which will 493 * be called when a urlbar preference changes. It will be passed the 494 * pref name. For prefs in the `browser.urlbar.` branch, the name will 495 * be relative to the branch. For other prefs, the name will be the 496 * full name. 497 */ 498 addObserver(observer) { 499 this._observerWeakRefs.push(Cu.getWeakReference(observer)); 500 } 501 502 /** 503 * Observes preference changes. 504 * 505 * @param {nsISupports} subject 506 * @param {string} topic 507 * @param {string} data 508 */ 509 observe(subject, topic, data) { 510 let pref = data.replace(PREF_URLBAR_BRANCH, ""); 511 if (!PREF_URLBAR_DEFAULTS.has(pref) && !PREF_OTHER_DEFAULTS.has(pref)) { 512 return; 513 } 514 for (let i = 0; i < this._observerWeakRefs.length; ) { 515 let observer = this._observerWeakRefs[i].get(); 516 if (!observer) { 517 // The observer has been GC'ed, so remove it from our list. 518 this._observerWeakRefs.splice(i, 1); 519 } else { 520 observer.onPrefChanged(pref); 521 ++i; 522 } 523 } 524 } 525 526 /** 527 * Called when a pref tracked by UrlbarPrefs changes. 528 * 529 * @param {string} pref 530 * The name of the pref, relative to `browser.urlbar.` if the pref is 531 * in that branch. 532 */ 533 onPrefChanged(pref) { 534 this._map.delete(pref); 535 536 // Some prefs may influence others. 537 switch (pref) { 538 case "showSearchSuggestionsFirst": 539 this.set( 540 "resultGroups", 541 JSON.stringify( 542 makeResultBuckets({ showSearchSuggestionsFirst: this.get(pref) }) 543 ) 544 ); 545 return; 546 } 547 548 if (pref.startsWith("suggest.")) { 549 this._map.delete("defaultBehavior"); 550 } 551 } 552 553 /** 554 * Called when the `NimbusFeatures.urlbar` value changes. 555 */ 556 _onNimbusUpdate() { 557 for (let key of Object.keys(this._nimbus)) { 558 this._map.delete(key); 559 } 560 this.__nimbus = null; 561 } 562 563 get _nimbus() { 564 if (!this.__nimbus) { 565 this.__nimbus = NimbusFeatures.urlbar.getAllVariables(); 566 } 567 return this.__nimbus; 568 } 569 570 /** 571 * Returns the raw value of the given preference straight from Services.prefs. 572 * 573 * @param {string} pref 574 * The name of the preference to get. 575 * @returns {*} The raw preference value. 576 */ 577 _readPref(pref) { 578 let { defaultValue, get } = this._getPrefDescriptor(pref); 579 return get(pref, defaultValue); 580 } 581 582 /** 583 * Returns a validated and/or fixed-up value of the given preference. The 584 * value may be validated for correctness, or it might be converted into a 585 * different value that is easier to work with than the actual value stored in 586 * the preferences branch. Not all preferences require validation or fixup. 587 * 588 * The values returned from this method are the values that are made public by 589 * this module. 590 * 591 * @param {string} pref 592 * The name of the preference to get. 593 * @returns {*} The validated and/or fixed-up preference value. 594 */ 595 _getPrefValue(pref) { 596 switch (pref) { 597 case "defaultBehavior": { 598 let val = 0; 599 for (let type of Object.keys(SUGGEST_PREF_TO_BEHAVIOR)) { 600 let behavior = `BEHAVIOR_${SUGGEST_PREF_TO_BEHAVIOR[ 601 type 602 ].toUpperCase()}`; 603 val |= 604 this.get("suggest." + type) && Ci.mozIPlacesAutoComplete[behavior]; 605 } 606 return val; 607 } 608 case "resultGroups": 609 try { 610 return JSON.parse(this._readPref(pref)); 611 } catch (ex) {} 612 return makeResultBuckets({ 613 showSearchSuggestionsFirst: this.get("showSearchSuggestionsFirst"), 614 }); 615 } 616 return this._readPref(pref); 617 } 618 619 /** 620 * Returns a descriptor of the given preference. 621 * @param {string} pref The preference to examine. 622 * @returns {object} An object describing the pref with the following shape: 623 * { defaultValue, get, set, clear } 624 */ 625 _getPrefDescriptor(pref) { 626 let branch = Services.prefs.getBranch(PREF_URLBAR_BRANCH); 627 let defaultValue = PREF_URLBAR_DEFAULTS.get(pref); 628 if (defaultValue === undefined) { 629 branch = Services.prefs; 630 defaultValue = PREF_OTHER_DEFAULTS.get(pref); 631 if (defaultValue === undefined) { 632 let nimbus = this._getNimbusDescriptor(pref); 633 if (nimbus) { 634 return nimbus; 635 } 636 throw new Error("Trying to access an unknown pref " + pref); 637 } 638 } 639 640 let type; 641 if (!Array.isArray(defaultValue)) { 642 type = PREF_TYPES.get(typeof defaultValue); 643 } else { 644 if (defaultValue.length != 2) { 645 throw new Error("Malformed pref def: " + pref); 646 } 647 [defaultValue, type] = defaultValue; 648 type = PREF_TYPES.get(type); 649 } 650 if (!type) { 651 throw new Error("Unknown pref type: " + pref); 652 } 653 return { 654 defaultValue, 655 get: branch[`get${type}Pref`], 656 // Float prefs are stored as Char. 657 set: branch[`set${type == "Float" ? "Char" : type}Pref`], 658 clear: branch.clearUserPref, 659 }; 660 } 661 662 /** 663 * Returns a descriptor for the given Nimbus property, if it exists. 664 * 665 * @param {string} name 666 * The name of the desired property in the object returned from 667 * NimbusFeatures.urlbar.getAllVariables(). 668 * @returns {object} 669 * An object describing the property's value with the following shape (same 670 * as _getPrefDescriptor()): 671 * { defaultValue, get, set, clear } 672 * If the property doesn't exist, null is returned. 673 */ 674 _getNimbusDescriptor(name) { 675 if (!this._nimbus.hasOwnProperty(name)) { 676 return null; 677 } 678 return { 679 defaultValue: this._nimbus[name], 680 get: () => this._nimbus[name], 681 set() { 682 throw new Error(`'${name}' is a Nimbus value and cannot be set`); 683 }, 684 clear() { 685 throw new Error(`'${name}' is a Nimbus value and cannot be cleared`); 686 }, 687 }; 688 } 689 690 /** 691 * Initializes the showSearchSuggestionsFirst pref based on the matchBuckets 692 * pref. This function can be removed when the corresponding UI migration in 693 * BrowserGlue.jsm is no longer needed. 694 */ 695 initializeShowSearchSuggestionsFirstPref() { 696 let matchBuckets = []; 697 let pref = Services.prefs.getCharPref("browser.urlbar.matchBuckets", ""); 698 try { 699 matchBuckets = pref.split(",").map(v => { 700 let bucket = v.split(":"); 701 return [bucket[0].trim().toLowerCase(), Number(bucket[1])]; 702 }); 703 } catch (ex) {} 704 let bucketNames = matchBuckets.map(bucket => bucket[0]); 705 let suggestionIndex = bucketNames.indexOf("suggestion"); 706 let generalIndex = bucketNames.indexOf("general"); 707 let showSearchSuggestionsFirst = 708 generalIndex < 0 || 709 (suggestionIndex >= 0 && suggestionIndex < generalIndex); 710 let oldValue = Services.prefs.getBoolPref( 711 "browser.urlbar.showSearchSuggestionsFirst" 712 ); 713 Services.prefs.setBoolPref( 714 "browser.urlbar.showSearchSuggestionsFirst", 715 showSearchSuggestionsFirst 716 ); 717 718 // Pref observers aren't called when a pref is set to its current value, but 719 // we always want to set matchBuckets to the appropriate default value via 720 // onPrefChanged, so call it now if necessary. This is really only 721 // necessary for tests since the only time this function is called outside 722 // of tests is by a UI migration in BrowserGlue. 723 if (oldValue == showSearchSuggestionsFirst) { 724 this.onPrefChanged("showSearchSuggestionsFirst"); 725 } 726 } 727} 728 729var UrlbarPrefs = new Preferences(); 730