Bug 1346616 - Migrate callsites that are retrieving requested locale from pref, to use LocaleService::GetRequestedLocales. r=pike, r=jfkthame draft
authorZibi Braniecki <gandalf@mozilla.com>
Sat, 11 Mar 2017 18:43:11 -0800
changeset 563871 62c2b7d4a2b20ec7100c1d0c90fb9416e7c00479
parent 563802 a374c35469935a874fefe64d3e07003fc5bc8884
child 624596 70698740ffeef63dd58c65b187f53ce6c794269e
push id54440
push userzbraniecki@mozilla.com
push dateMon, 17 Apr 2017 23:01:25 +0000
reviewerspike, jfkthame
bugs1346616
milestone55.0a1
Bug 1346616 - Migrate callsites that are retrieving requested locale from pref, to use LocaleService::GetRequestedLocales. r=pike, r=jfkthame I'm adding a helper function mozILocaleService::GetRequestedLocale to simplify most of the callsites that are looking for the first of the requested locales. In most cases, I'm just matching the behavior of the code with reusing LocaleService API instead of direct manipulation on the prefs. That includes how I handle error case scenarios. In case of sdk/l10n/locale.js I am reusing LocaleService heuristics over the custom one from the file since the ones in LocaleService are just more correct and unified accross the whole platform. In case of FallbackEncoding I have to turn it into a nsIObserver to listen to intl:requested-locales-changed. MozReview-Commit-ID: 7rOr2CovLK
addon-sdk/source/lib/sdk/l10n/locale.js
addon-sdk/source/test/test-l10n-locale.js
browser/components/dirprovider/DirectoryProvider.cpp
browser/components/distribution.js
browser/components/extensions/test/browser/head_pageAction.js
browser/components/search/test/head.js
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/modules/DirectoryLinksProvider.jsm
browser/modules/test/unit/test_DirectoryLinksProvider.js
dom/encoding/FallbackEncoding.cpp
dom/encoding/FallbackEncoding.h
intl/locale/LocaleService.cpp
intl/locale/mozILocaleService.idl
intl/locale/tests/unit/test_localeService.js
mobile/android/chrome/content/browser.js
mobile/android/components/DirectoryProvider.js
toolkit/components/downloads/ApplicationReputation.cpp
toolkit/components/search/nsSearchService.js
toolkit/modules/Locale.jsm
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/xre/nsAppRunner.cpp
--- a/addon-sdk/source/lib/sdk/l10n/locale.js
+++ b/addon-sdk/source/lib/sdk/l10n/locale.js
@@ -6,68 +6,18 @@
 module.metadata = {
   "stability": "unstable"
 };
 
 const prefs = require("../preferences/service");
 const { Cu, Cc, Ci } = require("chrome");
 const { Services } = Cu.import("resource://gre/modules/Services.jsm");
 
-/**
- * Gets the currently selected locale for display.
- * Gets all usable locale that we can use sorted by priority of relevance
- * @return  Array of locales, begins with highest priority
- */
-const PREF_MATCH_OS_LOCALE  = "intl.locale.matchOS";
-const PREF_SELECTED_LOCALE  = "general.useragent.locale";
-const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
-
 function getPreferedLocales(caseSensitve) {
-  let locales = [];
-  function addLocale(locale) {
-    locale = locale.trim();
-    if (!caseSensitve)
-      locale = locale.toLowerCase();
-    if (locales.indexOf(locale) === -1)
-      locales.push(locale);
-  }
-
-  // Most important locale is OS one. But we use it, only if
-  // "intl.locale.matchOS" pref is set to `true`.
-  // Currently only used for multi-locales mobile builds.
-  // http://mxr.mozilla.org/mozilla-central/source/mobile/android/installer/Makefile.in#46
-  if (prefs.get(PREF_MATCH_OS_LOCALE, false)) {
-    let localeService = Cc["@mozilla.org/intl/nslocaleservice;1"].
-                        getService(Ci.nsILocaleService);
-    let osLocale = localeService.getLocaleComponentForUserAgent();
-    addLocale(osLocale);
-  }
-
-  // In some cases, mainly on Fennec and on Linux version,
-  // `general.useragent.locale` is a special 'localized' value, like:
-  // "chrome://global/locale/intl.properties"
-  let browserUiLocale = prefs.getLocalized(PREF_SELECTED_LOCALE, "") ||
-                        prefs.get(PREF_SELECTED_LOCALE, "");
-  if (browserUiLocale)
-    addLocale(browserUiLocale);
-
-  // Third priority is the list of locales used for web content
-  let contentLocales = prefs.getLocalized(PREF_ACCEPT_LANGUAGES, "") ||
-                       prefs.get(PREF_ACCEPT_LANGUAGES, "");
-  if (contentLocales) {
-    // This list is a string of locales seperated by commas.
-    // There is spaces after commas, so strip each item
-    for (let locale of contentLocales.split(","))
-      addLocale(locale.replace(/(^\s+)|(\s+$)/g, ""));
-  }
-
-  // Finally, we ensure that en-US is the final fallback if it wasn't added
-  addLocale("en-US");
-
-  return locales;
+  return Services.locale.getRequestedLocales();
 }
 exports.getPreferedLocales = getPreferedLocales;
 
 /**
  * Selects the closest matching locale from a list of locales.
  *
  * @param  aLocales
  *         An array of available locales
--- a/addon-sdk/source/test/test-l10n-locale.js
+++ b/addon-sdk/source/test/test-l10n-locale.js
@@ -1,134 +1,13 @@
 /* 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/. */
 
-const { getPreferedLocales, findClosestLocale } = require("sdk/l10n/locale");
-const prefs = require("sdk/preferences/service");
-const { Cc, Ci, Cu } = require("chrome");
-const { Services } = Cu.import("resource://gre/modules/Services.jsm");
-const BundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
-
-const PREF_MATCH_OS_LOCALE  = "intl.locale.matchOS";
-const PREF_SELECTED_LOCALE  = "general.useragent.locale";
-const PREF_ACCEPT_LANGUAGES = "intl.accept_languages";
-
-function assertPrefered(assert, expected, msg) {
-  assert.equal(JSON.stringify(getPreferedLocales()), JSON.stringify(expected),
-                   msg);
-}
-
-exports.testGetPreferedLocales = function(assert) {
-  prefs.set(PREF_MATCH_OS_LOCALE, false);
-  prefs.set(PREF_SELECTED_LOCALE, "");
-  prefs.set(PREF_ACCEPT_LANGUAGES, "");
-  assertPrefered(assert, ["en-us"],
-                 "When all preferences are empty, we only have en-us");
-
-  prefs.set(PREF_SELECTED_LOCALE, "fr");
-  prefs.set(PREF_ACCEPT_LANGUAGES, "jp");
-  assertPrefered(assert, ["fr", "jp", "en-us"],
-                 "We first have useragent locale, then web one and finally en-US");
-
-  prefs.set(PREF_SELECTED_LOCALE, "en-US");
-  prefs.set(PREF_ACCEPT_LANGUAGES, "en-US");
-  assertPrefered(assert, ["en-us"],
-                 "We do not have duplicates");
-
-  prefs.set(PREF_SELECTED_LOCALE, "en-US");
-  prefs.set(PREF_ACCEPT_LANGUAGES, "fr");
-  assertPrefered(assert, ["en-us", "fr"],
-                 "en-US can be first if specified by higher priority preference");
-
-  // Reset what we changed
-  prefs.reset(PREF_MATCH_OS_LOCALE);
-  prefs.reset(PREF_SELECTED_LOCALE);
-  prefs.reset(PREF_ACCEPT_LANGUAGES);
-}
-
-// In some cases, mainly on Fennec and on Linux version,
-// `general.useragent.locale` is a special 'localized' value, like:
-// "chrome://global/locale/intl.properties"
-exports.testPreferedLocalizedLocale = function(assert) {
-  prefs.set(PREF_MATCH_OS_LOCALE, false);
-  let bundleURL = "chrome://global/locale/intl.properties";
-  prefs.setLocalized(PREF_SELECTED_LOCALE, bundleURL);
-  let contentLocale = "ja";
-  prefs.set(PREF_ACCEPT_LANGUAGES, contentLocale);
-
-  // Read manually the expected locale value from the property file
-  let expectedLocale = BundleService.createBundle(bundleURL).
-    GetStringFromName(PREF_SELECTED_LOCALE).
-    toLowerCase();
-
-  // First add the useragent locale
-  let expectedLocaleList = [expectedLocale];
-
-  // Then the content locale
-  if (expectedLocaleList.indexOf(contentLocale) == -1)
-    expectedLocaleList.push(contentLocale);
-
-  // Add default "en-us" fallback if the main language is not already en-us
-  if (expectedLocaleList.indexOf("en-us") == -1)
-    expectedLocaleList.push("en-us");
-
-  assertPrefered(assert, expectedLocaleList, "test localized pref value");
-
-  // Reset what we have changed
-  prefs.reset(PREF_MATCH_OS_LOCALE);
-  prefs.reset(PREF_SELECTED_LOCALE);
-  prefs.reset(PREF_ACCEPT_LANGUAGES);
-}
-
-// On Linux the PREF_ACCEPT_LANGUAGES pref can be a localized pref.
-exports.testPreferedContentLocale = function(assert) {
-  prefs.set(PREF_MATCH_OS_LOCALE, false);
-  let noLocale = "",
-      bundleURL = "chrome://global/locale/intl.properties";
-  prefs.set(PREF_SELECTED_LOCALE, noLocale);
-  prefs.setLocalized(PREF_ACCEPT_LANGUAGES, bundleURL);
-
-  // Read the expected locale values from the property file
-  let expectedLocaleList = BundleService.createBundle(bundleURL).
-    GetStringFromName(PREF_ACCEPT_LANGUAGES).
-    split(",").
-    map(locale => locale.trim().toLowerCase());
-
-  // Add default "en-us" fallback if the main language is not already en-us
-  if (expectedLocaleList.indexOf("en-us") == -1)
-    expectedLocaleList.push("en-us");
-
-  assertPrefered(assert, expectedLocaleList, "test localized content locale pref value");
-
-  // Reset what we have changed
-  prefs.reset(PREF_MATCH_OS_LOCALE);
-  prefs.reset(PREF_SELECTED_LOCALE);
-  prefs.reset(PREF_ACCEPT_LANGUAGES);
-}
-
-exports.testPreferedOsLocale = function(assert) {
-  prefs.set(PREF_MATCH_OS_LOCALE, true);
-  prefs.set(PREF_SELECTED_LOCALE, "");
-  prefs.set(PREF_ACCEPT_LANGUAGES, "");
-
-  let expectedLocale = Services.locale.getAppLocaleAsLangTag().toLowerCase();
-  let expectedLocaleList = [expectedLocale];
-
-  // Add default "en-us" fallback if the main language is not already en-us
-  if (expectedLocale != "en-us")
-    expectedLocaleList.push("en-us");
-
-  assertPrefered(assert, expectedLocaleList, "Ensure that we select OS locale when related preference is set");
-
-  // Reset what we have changed
-  prefs.reset(PREF_MATCH_OS_LOCALE);
-  prefs.reset(PREF_SELECTED_LOCALE);
-  prefs.reset(PREF_ACCEPT_LANGUAGES);
-}
+const { findClosestLocale } = require("sdk/l10n/locale");
 
 exports.testFindClosestLocale = function(assert) {
   // Second param of findClosestLocale (aMatchLocales) have to be in lowercase
   assert.equal(findClosestLocale([], []), null,
                    "When everything is empty we get null");
 
   assert.equal(findClosestLocale(["en", "en-US"], ["en"]),
                    "en", "We always accept exact match first 1/5");
--- a/browser/components/dirprovider/DirectoryProvider.cpp
+++ b/browser/components/dirprovider/DirectoryProvider.cpp
@@ -14,21 +14,24 @@
 #include "nsEnumeratorUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMArray.h"
 #include "nsDirectoryServiceUtils.h"
 #include "mozilla/ModuleUtils.h"
+#include "mozilla/intl/LocaleService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsXULAppAPI.h"
 #include "nsIPrefLocalizedString.h"
 
+using mozilla::intl::LocaleService;
+
 namespace mozilla {
 namespace browser {
 
 NS_IMPL_ISUPPORTS(DirectoryProvider,
                   nsIDirectoryServiceProvider,
                   nsIDirectoryServiceProvider2)
 
 NS_IMETHODIMP
@@ -120,41 +123,28 @@ AppendDistroSearchDirs(nsIProperties* aD
         if (NS_SUCCEEDED(rv) && exists) {
           array.AppendObject(defLocalePlugins);
           return; // all done
         }
       }
     }
 
     // we didn't have a defaultLocale, use the user agent locale
-    nsCString locale;
-    nsCOMPtr<nsIPrefLocalizedString> prefString;
-    rv = prefs->GetComplexValue("general.useragent.locale",
-                                NS_GET_IID(nsIPrefLocalizedString),
-                                getter_AddRefs(prefString));
-    if (NS_SUCCEEDED(rv)) {
-      nsAutoString wLocale;
-      prefString->GetData(getter_Copies(wLocale));
-      CopyUTF16toUTF8(wLocale, locale);
-    } else {
-      rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale));
-    }
+    nsAutoCString locale;
+    LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
 
+    nsCOMPtr<nsIFile> curLocalePlugins;
+    rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins));
     if (NS_SUCCEEDED(rv)) {
 
-      nsCOMPtr<nsIFile> curLocalePlugins;
-      rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins));
-      if (NS_SUCCEEDED(rv)) {
-
-        curLocalePlugins->AppendNative(locale);
-        rv = curLocalePlugins->Exists(&exists);
-        if (NS_SUCCEEDED(rv) && exists) {
-          array.AppendObject(curLocalePlugins);
-          return; // all done
-        }
+      curLocalePlugins->AppendNative(locale);
+      rv = curLocalePlugins->Exists(&exists);
+      if (NS_SUCCEEDED(rv) && exists) {
+        array.AppendObject(curLocalePlugins);
+        return; // all done
       }
     }
   }
 }
 
 NS_IMETHODIMP
 DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
 {
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -52,17 +52,17 @@ DistributionCustomizer.prototype = {
       // Unable to parse INI.
       Cu.reportError("Unable to parse distribution.ini");
     }
     this.__defineGetter__("_ini", () => ini);
     return this._ini;
   },
 
   get _locale() {
-    let locale = this._prefs.getCharPref("general.useragent.locale", "en-US");
+    const locale = Services.locale.getRequestedLocale() || "en-US";
     this.__defineGetter__("_locale", () => locale);
     return this._locale;
   },
 
   get _language() {
     let language = this._locale.split("-")[0];
     this.__defineGetter__("_language", () => language);
     return this._language;
--- a/browser/components/extensions/test/browser/head_pageAction.js
+++ b/browser/components/extensions/test/browser/head_pageAction.js
@@ -128,25 +128,26 @@ function* runTests(options) {
           extension.sendMessage("runTests");
         });
       } else {
         resolve();
       }
     });
   });
 
-  yield SpecialPowers.pushPrefEnv({set: [["general.useragent.locale", "es-ES"]]});
+  let reqLoc = Services.locale.getRequestedLocales();
+  Services.locale.setRequestedLocales(["es-ES"]);
 
   yield extension.startup();
 
   yield awaitFinish;
 
   yield extension.unload();
 
-  yield SpecialPowers.popPrefEnv();
+  Services.locale.setRequestedLocales(reqLoc);
 
   let node = document.getElementById(pageActionId);
   is(node, null, "pageAction image removed from document");
 
   currentWindow = null;
   for (let win of windows.splice(0)) {
     node = win.document.getElementById(pageActionId);
     is(node, null, "pageAction image removed from second document");
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -16,32 +16,17 @@ function isSubObjectOf(expectedObj, actu
       isSubObjectOf(expectedObj[prop], actualObj[prop], name + "[" + prop + "]");
     } else {
       is(actualObj[prop], expectedObj[prop], name + "[" + prop + "]");
     }
   }
 }
 
 function getLocale() {
-  const localePref = "general.useragent.locale";
-  return getLocalizedPref(localePref, Services.prefs.getCharPref(localePref));
-}
-
-/**
- * Wrapper for nsIPrefBranch::getComplexValue.
- * @param aPrefName
- *        The name of the pref to get.
- * @returns aDefault if the requested pref doesn't exist.
- */
-function getLocalizedPref(aPrefName, aDefault) {
-  try {
-    return Services.prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
-  } catch (ex) {
-    return aDefault;
-  }
+  return Services.locale.getRequestedLocale() || undefined;
 }
 
 function promiseEvent(aTarget, aEventName, aPreventDefault) {
   function cancelEvent(event) {
     if (aPreventDefault) {
       event.preventDefault();
     }
 
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -309,17 +309,17 @@ class ChromeActions {
         }
       };
 
       channel.asyncOpen2(listener);
     });
   }
 
   getLocale() {
-    return getStringPref("general.useragent.locale", "en-US");
+    return Services.locale.getRequestedLocale() || "en-US";
   }
 
   getStrings(data) {
     try {
       // Lazy initialization of localizedStrings
       if (!("localizedStrings" in this)) {
         this.localizedStrings = getLocalizedStrings("viewer.properties");
       }
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -44,22 +44,16 @@ XPCOMUtils.defineLazyGetter(this, "gUnic
   return converter;
 });
 
 
 // The filename where directory links are stored locally
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_LINKS_TYPE = "application/json";
 
-// The preference that tells whether to match the OS locale
-const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
-
-// The preference that tells what locale the user selected
-const PREF_SELECTED_LOCALE = "general.useragent.locale";
-
 // The preference that tells where to obtain directory links
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
 // The preference that tells where to send click/view pings
 const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
 // The preference that tells if newtab is enhanced
 const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
@@ -161,18 +155,16 @@ var DirectoryLinksProvider = {
    * an inadjacent site in the new tab
    */
   _newTabHasInadjacentSite: false,
 
   get _observedPrefs() {
     return Object.freeze({
       enhanced: PREF_NEWTAB_ENHANCED,
       linksURL: PREF_DIRECTORY_SOURCE,
-      matchOSLocale: PREF_MATCH_OS_LOCALE,
-      prefSelectedLocale: PREF_SELECTED_LOCALE,
     });
   },
 
   get _linksURL() {
     if (!this.__linksURL) {
       try {
         this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
         this.__linksURLModified = Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"]);
@@ -183,36 +175,17 @@ var DirectoryLinksProvider = {
     return this.__linksURL;
   },
 
   /**
    * Gets the currently selected locale for display.
    * @return  the selected locale or "en-US" if none is selected
    */
   get locale() {
-    let matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE, false);
-
-    if (matchOS) {
-      return Cc["@mozilla.org/intl/ospreferences;1"].
-             getService(Ci.mozIOSPreferences).systemLocale;
-    }
-
-    try {
-      let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
-                                                  Ci.nsIPrefLocalizedString);
-      if (locale) {
-        return locale.data;
-      }
-    } catch (e) {}
-
-    try {
-      return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
-    } catch (e) {}
-
-    return "en-US";
+    return Services.locale.getRequestedLocale() || "en-US";
   },
 
   /**
    * Set appropriate default ping behavior controlled by enhanced pref
    */
   _setDefaultEnhanced: function DirectoryLinksProvider_setDefaultEnhanced() {
     if (!Services.prefs.prefHasUserValue(PREF_NEWTAB_ENHANCED)) {
       let enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
@@ -231,24 +204,21 @@ var DirectoryLinksProvider = {
       switch (aData) {
         // Re-set the default in case the user clears the pref
         case this._observedPrefs.enhanced:
           this._setDefaultEnhanced();
           break;
 
         case this._observedPrefs.linksURL:
           delete this.__linksURL;
-          // fallthrough
-
-        // Force directory download on changes to fetch related prefs
-        case this._observedPrefs.matchOSLocale:
-        case this._observedPrefs.prefSelectedLocale:
           this._fetchAndCacheLinksIfNecessary(true);
           break;
       }
+    } else if (aTopic === "intl:requested-locales-changed") {
+      this._fetchAndCacheLinksIfNecessary(true);
     }
   },
 
   _addPrefsObserver: function DirectoryLinksProvider_addObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.addObserver(prefName, this);
     }
@@ -685,16 +655,17 @@ var DirectoryLinksProvider = {
       aCallback(links);
       this._populatePlacesLinks();
     });
   },
 
   init: function DirectoryLinksProvider_init() {
     this._setDefaultEnhanced();
     this._addPrefsObserver();
+    Services.obs.addObserver(this, "intl:requested-locales-changed");
     // setup directory file path and last download timestamp
     this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
     this._lastDownloadMS = 0;
 
     // setup frequency cap file path
     this._frequencyCapFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, FREQUENCY_CAP_FILE);
     // setup inadjacent sites URL
     this._inadjacentSitesUrl = INADJACENCY_SOURCE;
@@ -1208,16 +1179,17 @@ var DirectoryLinksProvider = {
 
   /**
    * Return the object to its pre-init state
    */
   reset: function DirectoryLinksProvider_reset() {
     delete this.__linksURL;
     this._removePrefsObserver();
     this._removeObservers();
+    Services.obs.removeObserver(this, "intl:requested-locales-changed");
   },
 
   addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
     this._observers.add(aObserver);
   },
 
   removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) {
     this._observers.delete(aObserver);
--- a/browser/modules/test/unit/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/unit/test_DirectoryLinksProvider.js
@@ -29,34 +29,34 @@ do_get_profile();
 
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_FRECENCY = 1000;
 const SUGGESTED_FRECENCY = Infinity;
 const kURLData = {"directory": [{"url": "http://example.com", "title": "LocalSource"}]};
 const kTestURL = "data:application/json," + JSON.stringify(kURLData);
 
 // DirectoryLinksProvider preferences
-const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
 const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
 const kPingUrlPref = "browser.newtabpage.directory.ping";
 const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
 
 // httpd settings
 var server;
 const kDefaultServerPort = 9000;
 const kBaseUrl = "http://localhost:" + kDefaultServerPort;
 const kExamplePath = "/exampleTest/";
 const kFailPath = "/fail/";
 const kPingPath = "/ping/";
 const kExampleURL = kBaseUrl + kExamplePath;
 const kFailURL = kBaseUrl + kFailPath;
 const kPingUrl = kBaseUrl + kPingPath;
 
 // app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
-Services.prefs.setCharPref(kLocalePref, "en-US");
+const origReqLocales = Services.locale.getRequestedLocales();
+Services.locale.setRequestedLocales(["en-US"]);
 Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
 Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 Services.prefs.setBoolPref(kNewtabEnhancedPref, true);
 
 const kHttpHandlerData = {};
 kHttpHandlerData[kExamplePath] = {"directory": [{"url": "http://example.com", "title": "RemoteSource"}]};
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
@@ -196,26 +196,26 @@ function promiseDirectoryDownloadOnPrefC
   }
   return Promise.resolve();
 }
 
 function promiseSetupDirectoryLinksProvider(options = {}) {
   return Task.spawn(function*() {
     let linksURL = options.linksURL || kTestURL;
     yield DirectoryLinksProvider.init();
-    yield promiseDirectoryDownloadOnPrefChange(kLocalePref, options.locale || "en-US");
+    Services.locale.setRequestedLocales([options.locale || "en-US"]);
     yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, linksURL);
     do_check_eq(DirectoryLinksProvider._linksURL, linksURL);
     DirectoryLinksProvider._lastDownloadMS = options.lastDownloadMS || 0;
   });
 }
 
 function promiseCleanDirectoryLinksProvider() {
   return Task.spawn(function*() {
-    yield promiseDirectoryDownloadOnPrefChange(kLocalePref, "en-US");
+    Services.locale.setRequestedLocales(["en-US"]);
     yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kTestURL);
     yield DirectoryLinksProvider._clearFrequencyCap();
     yield DirectoryLinksProvider._loadInadjacentSites();
     DirectoryLinksProvider._lastDownloadMS  = 0;
     DirectoryLinksProvider.reset();
   });
 }
 
@@ -228,17 +228,17 @@ function run_test() {
   NewTabUtils.init();
 
   run_next_test();
 
   // Teardown.
   do_register_cleanup(function() {
     server.stop(function() { });
     DirectoryLinksProvider.reset();
-    Services.prefs.clearUserPref(kLocalePref);
+    Services.locale.setRequestedLocales(origReqLocales);
     Services.prefs.clearUserPref(kSourceUrlPref);
     Services.prefs.clearUserPref(kPingUrlPref);
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
   });
 }
 
 
 function setTimeout(fun, timeout) {
--- a/dom/encoding/FallbackEncoding.cpp
+++ b/dom/encoding/FallbackEncoding.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/FallbackEncoding.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 #include "nsUConvPropertySearch.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "nsIObserverService.h"
 #include "mozilla/intl/LocaleService.h"
 
 using mozilla::intl::LocaleService;
 
 namespace mozilla {
 namespace dom {
 
 static constexpr nsUConvProp localesFallbacks[] = {
@@ -24,31 +25,27 @@ static constexpr nsUConvProp localesFall
 static constexpr nsUConvProp domainsFallbacks[] = {
 #include "domainsfallbacks.properties.h"
 };
 
 static constexpr nsUConvProp nonParticipatingDomains[] = {
 #include "nonparticipatingdomains.properties.h"
 };
 
+NS_IMPL_ISUPPORTS(FallbackEncoding, nsIObserver)
+
 FallbackEncoding* FallbackEncoding::sInstance = nullptr;
 bool FallbackEncoding::sGuessFallbackFromTopLevelDomain = true;
 
 FallbackEncoding::FallbackEncoding()
 {
-  MOZ_COUNT_CTOR(FallbackEncoding);
   MOZ_ASSERT(!FallbackEncoding::sInstance,
              "Singleton already exists.");
 }
 
-FallbackEncoding::~FallbackEncoding()
-{
-  MOZ_COUNT_DTOR(FallbackEncoding);
-}
-
 void
 FallbackEncoding::Get(nsACString& aFallback)
 {
   if (!mFallback.IsEmpty()) {
     aFallback = mFallback;
     return;
   }
 
@@ -113,37 +110,53 @@ FallbackEncoding::FromLocale(nsACString&
 void
 FallbackEncoding::PrefChanged(const char*, void*)
 {
   MOZ_ASSERT(FallbackEncoding::sInstance,
              "Pref callback called with null fallback cache.");
   FallbackEncoding::sInstance->Invalidate();
 }
 
+NS_IMETHODIMP
+FallbackEncoding::Observe(nsISupports *aSubject, const char *aTopic,
+                         const char16_t *aData)
+{
+  MOZ_ASSERT(FallbackEncoding::sInstance,
+             "Observe callback called with null fallback cache.");
+  FallbackEncoding::sInstance->Invalidate();
+  return NS_OK;
+}
+
 void
 FallbackEncoding::Initialize()
 {
   MOZ_ASSERT(!FallbackEncoding::sInstance,
              "Initializing pre-existing fallback cache.");
   FallbackEncoding::sInstance = new FallbackEncoding;
   Preferences::RegisterCallback(FallbackEncoding::PrefChanged,
                                 "intl.charset.fallback.override",
                                 nullptr);
-  Preferences::RegisterCallback(FallbackEncoding::PrefChanged,
-                                "general.useragent.locale",
-                                nullptr);
   Preferences::AddBoolVarCache(&sGuessFallbackFromTopLevelDomain,
                                "intl.charset.fallback.tld");
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(sInstance, "intl:requested-locales-changed", true);
+  }
 }
 
 void
 FallbackEncoding::Shutdown()
 {
   MOZ_ASSERT(FallbackEncoding::sInstance,
              "Releasing non-existent fallback cache.");
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(sInstance, "intl:requested-locales-changed");
+  }
   delete FallbackEncoding::sInstance;
   FallbackEncoding::sInstance = nullptr;
 }
 
 bool
 FallbackEncoding::IsParticipatingTopLevelDomain(const nsACString& aTLD)
 {
   nsAutoCString dummy;
--- a/dom/encoding/FallbackEncoding.h
+++ b/dom/encoding/FallbackEncoding.h
@@ -2,24 +2,27 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_dom_FallbackEncoding_h_
 #define mozilla_dom_FallbackEncoding_h_
 
+#include "nsIObserver.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
-class FallbackEncoding
+class FallbackEncoding : public nsIObserver
 {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
 
   /**
    * Whether FromTopLevelDomain() should be used.
    */
   static bool sGuessFallbackFromTopLevelDomain;
 
   /**
    * Gets the locale-dependent fallback encoding for legacy HTML and plain
@@ -63,17 +66,17 @@ public:
 private:
 
   /**
    * The fallback cache.
    */
   static FallbackEncoding* sInstance;
 
   FallbackEncoding();
-  ~FallbackEncoding();
+  virtual ~FallbackEncoding() {};
 
   /**
    * Invalidates the cache.
    */
   void Invalidate()
   {
     mFallback.Truncate();
   }
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -743,16 +743,34 @@ LocaleService::GetRequestedLocales(uint3
 
   *aCount = requestedLocales.Length();
   *aOutArray = CreateOutArray(requestedLocales);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LocaleService::GetRequestedLocale(nsACString& aRetVal)
+{
+  AutoTArray<nsCString, 16> requestedLocales;
+  bool res = GetRequestedLocales(requestedLocales);
+
+  if (!res) {
+    NS_ERROR("Couldn't retrieve selected locales from prefs!");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (requestedLocales.Length() > 0) {
+    aRetVal = requestedLocales[0];
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 LocaleService::SetRequestedLocales(const char** aRequested,
                                    uint32_t aRequestedCount)
 {
   MOZ_ASSERT(aRequestedCount < 2, "We can only handle one requested locale");
 
   if (aRequestedCount == 0) {
     Preferences::ClearUser(SELECTED_LOCALE_PREF);
   } else {
--- a/intl/locale/mozILocaleService.idl
+++ b/intl/locale/mozILocaleService.idl
@@ -139,16 +139,21 @@ interface mozILocaleService : nsISupport
    * used as a requestedLocales input list for language negotiation.
    *
    * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
    */
   void getRequestedLocales([optional] out unsigned long aCount,
                            [retval, array, size_is(aCount)] out string aLocales);
 
   /**
+   * Returns the top-requested locale from the user, or an empty string if none is set.
+   */
+  ACString getRequestedLocale();
+
+  /**
    * Sets a list of locales that the user requested the app to be
    * localized to.
    *
    * The argument is an ordered list of locale IDs which should be
    * used as a requestedLocales input list for language negotiation.
    *
    * The current implementation is limited to handle at most one
    * locale passed to the API. In the future we'll transition to support
--- a/intl/locale/tests/unit/test_localeService.js
+++ b/intl/locale/tests/unit/test_localeService.js
@@ -112,16 +112,34 @@ add_test(function test_getRequestedLocal
   };
 
   Services.obs.addObserver(observer, REQ_LOC_CHANGE_EVENT);
   Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "sr-RU");
 
   run_next_test();
 });
 
+add_test(function test_getRequestedLocale() {
+  Services.prefs.setBoolPref(PREF_MATCH_OS_LOCALE, false);
+  Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "tlh");
+
+  let requestedLocale = localeService.getRequestedLocale();
+  do_check_true(requestedLocale === "tlh", "requestedLocale returns the right value");
+
+  Services.prefs.setCharPref(PREF_SELECTED_LOCALE, "");
+
+  requestedLocale = localeService.getRequestedLocale();
+  do_check_true(requestedLocale === "", "requestedLocale returns empty value value");
+
+  Services.prefs.clearUserPref(PREF_MATCH_OS_LOCALE);
+  Services.prefs.clearUserPref(PREF_SELECTED_LOCALE);
+
+  run_next_test();
+});
+
 add_test(function test_setRequestedLocales() {
   localeService.setRequestedLocales([]);
 
   let matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
   do_check_true(matchOS === true);
 
   localeService.setRequestedLocales(['de-AT']);
 
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1642,25 +1642,17 @@ var BrowserApp = {
     if (!gotResizeWindow) {
         aBrowser.contentWindow.removeEventListener("resize", resizeWindow);
         dwu.zoomToFocusedInput();
       }
     }, 500);
   },
 
   getUALocalePref: function () {
-    try {
-      return Services.prefs.getComplexValue("general.useragent.locale", Ci.nsIPrefLocalizedString).data;
-    } catch (e) {
-      try {
-        return Services.prefs.getCharPref("general.useragent.locale");
-      } catch (ee) {
-        return undefined;
-      }
-    }
+    return Services.locale.getRequestedLocale() || undefined;
   },
 
   getOSLocalePref: function () {
     try {
       return Services.prefs.getCharPref("intl.locale.os");
     } catch (e) {
       return undefined;
     }
--- a/mobile/android/components/DirectoryProvider.js
+++ b/mobile/android/components/DirectoryProvider.js
@@ -108,24 +108,19 @@ DirectoryProvider.prototype = {
       array.push(commonPlugins);
 
     let localePlugins = searchPlugins.clone();
     localePlugins.append("locale");
     if (!localePlugins.exists())
       return;
 
     let curLocale = "";
-    try {
-      curLocale = Services.prefs.getComplexValue("general.useragent.locale", Ci.nsIPrefLocalizedString).data;
-    } catch (e) {
-      // eslint-disable-next-line mozilla/use-default-preference-values
-      try {
-        curLocale = Services.prefs.getCharPref("general.useragent.locale");
-      } catch (ee) {
-      }
+    let reqLocales = Services.locales.getRequestedLocales();
+    if (reqLocales.length > 0) {
+      curLocale = reqLocales[0];
     }
 
     if (curLocale) {
       let curLocalePlugins = localePlugins.clone();
       curLocalePlugins.append(curLocale);
       if (curLocalePlugins.exists()) {
         array.push(curLocalePlugins);
         return;
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -31,16 +31,17 @@
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/LoadContext.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/intl/LocaleService.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsDependentSubstring.h"
 #include "nsError.h"
 #include "nsNetCID.h"
 #include "nsReadableUtils.h"
@@ -57,28 +58,28 @@
 
 using namespace mozilla::downloads;
 using mozilla::ArrayLength;
 using mozilla::BasePrincipal;
 using mozilla::OriginAttributes;
 using mozilla::Preferences;
 using mozilla::TimeStamp;
 using mozilla::Telemetry::Accumulate;
+using mozilla::intl::LocaleService;
 using safe_browsing::ClientDownloadRequest;
 using safe_browsing::ClientDownloadRequest_CertificateChain;
 using safe_browsing::ClientDownloadRequest_Resource;
 using safe_browsing::ClientDownloadRequest_SignatureInfo;
 
 // Preferences that we need to initialize the query.
 #define PREF_SB_APP_REP_URL "browser.safebrowsing.downloads.remote.url"
 #define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
 #define PREF_SB_DOWNLOADS_ENABLED "browser.safebrowsing.downloads.enabled"
 #define PREF_SB_DOWNLOADS_REMOTE_ENABLED "browser.safebrowsing.downloads.remote.enabled"
 #define PREF_SB_DOWNLOADS_REMOTE_TIMEOUT "browser.safebrowsing.downloads.remote.timeout_ms"
-#define PREF_GENERAL_LOCALE "general.useragent.locale"
 #define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
 #define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
 
 // Preferences that are needed to action the verdict.
 #define PREF_BLOCK_DANGEROUS            "browser.safebrowsing.downloads.remote.block_dangerous"
 #define PREF_BLOCK_DANGEROUS_HOST       "browser.safebrowsing.downloads.remote.block_dangerous_host"
 #define PREF_BLOCK_POTENTIALLY_UNWANTED "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
 #define PREF_BLOCK_UNCOMMON             "browser.safebrowsing.downloads.remote.block_uncommon"
@@ -1314,18 +1315,18 @@ PendingLookup::SendRemoteQueryInternal()
   rv = mQuery->GetFileSize(&fileSize);
   NS_ENSURE_SUCCESS(rv, rv);
   mRequest.set_length(fileSize);
   // We have no way of knowing whether or not a user initiated the
   // download. Set it to true to lessen the chance of false positives.
   mRequest.set_user_initiated(true);
 
   nsCString locale;
-  NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_GENERAL_LOCALE, &locale),
-                    NS_ERROR_NOT_AVAILABLE);
+  rv = LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
+  NS_ENSURE_SUCCESS(rv, rv);
   mRequest.set_locale(locale.get());
   nsCString sha256Hash;
   rv = mQuery->GetSha256Hash(sha256Hash);
   NS_ENSURE_SUCCESS(rv, rv);
   mRequest.mutable_digests()->set_sha256(sha256Hash.Data());
   nsString fileName;
   rv = mQuery->GetSuggestedFileName(fileName);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -870,22 +870,17 @@ function getDir(aKey, aIFace) {
 }
 
 /**
  * Gets the current value of the locale.  It's possible for this preference to
  * be localized, so we have to do a little extra work here.  Similar code
  * exists in nsHttpHandler.cpp when building the UA string.
  */
 function getLocale() {
-  let locale = getLocalizedPref(LOCALE_PREF);
-  if (locale)
-    return locale;
-
-  // Not localized.
-  return Services.prefs.getCharPref(LOCALE_PREF);
+  return Services.locale.getRequestedLocale();
 }
 
 /**
  * Wrapper for nsIPrefBranch::getComplexValue.
  * @param aPrefName
  *        The name of the pref to get.
  * @returns aDefault if the requested pref doesn't exist.
  */
--- a/toolkit/modules/Locale.jsm
+++ b/toolkit/modules/Locale.jsm
@@ -4,36 +4,23 @@
 
 this.EXPORTED_SYMBOLS = ["Locale"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 
-const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
-const PREF_SELECTED_LOCALE            = "general.useragent.locale";
-
 this.Locale = {
   /**
    * Gets the currently selected locale for display.
    * @return  the selected locale or "en-US" if none is selected
    */
   getLocale() {
-    if (Preferences.get(PREF_MATCH_OS_LOCALE, false)) {
-      const osPrefs =
-        Cc["@mozilla.org/intl/ospreferences;1"].getService(Ci.mozIOSPreferences);
-      return osPrefs.systemLocale;
-    }
-    try {
-      let locale = Preferences.get(PREF_SELECTED_LOCALE, null, Ci.nsIPrefLocalizedString);
-      if (locale)
-        return locale;
-    } catch (e) {}
-    return Preferences.get(PREF_SELECTED_LOCALE, "en-US");
+    return Services.locale.getRequestedLocale() || "en-US";
   },
 
   /**
    * Selects the closest matching locale from a list of locales.
    *
    * @param  aLocales
    *         An array of locales
    * @return the best match for the currently selected locale
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -36,18 +36,16 @@ const PREF_EM_CHECK_UPDATE_SECURITY   = 
 const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
 const PREF_APP_UPDATE_AUTO            = "app.update.auto";
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_HOTFIX_LASTVERSION      = "extensions.hotfix.lastVersion";
 const PREF_EM_HOTFIX_URL              = "extensions.hotfix.url";
 const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
 const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";
-const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
-const PREF_SELECTED_LOCALE            = "general.useragent.locale";
 const UNKNOWN_XPCOM_ABI               = "unknownABI";
 
 const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion";
 const PREF_WEBAPI_TESTING             = "extensions.webapi.testing";
 const PREF_WEBEXT_PERM_PROMPTS        = "extensions.webextPermissionPrompts";
 
 const UPDATE_REQUEST_VERSION          = 2;
 const CATEGORY_UPDATE_PARAMS          = "extension-update-params";
@@ -321,36 +319,17 @@ function promiseCallProvider(aProvider, 
   });
 }
 
 /**
  * Gets the currently selected locale for display.
  * @return  the selected locale or "en-US" if none is selected
  */
 function getLocale() {
-  try {
-    if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) {
-      const osPrefs =
-        Cc["@mozilla.org/intl/ospreferences;1"].getService(Ci.mozIOSPreferences);
-      return osPrefs.systemLocale;
-    }
-  } catch (e) { }
-
-  try {
-    let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
-                                                Ci.nsIPrefLocalizedString);
-    if (locale)
-      return locale;
-  } catch (e) { }
-
-  try {
-    return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
-  } catch (e) { }
-
-  return "en-US";
+  return Services.locale.getRequestedLocale() || "en-US";
 }
 
 function webAPIForAddon(addon) {
   if (!addon) {
     return null;
   }
 
   let result = {};
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -43,17 +43,16 @@ const PREF_BLOCKLIST_URL              = 
 const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
 const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
 const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
 const PREF_BLOCKLIST_PINGCOUNTTOTAL   = "extensions.blocklist.pingCountTotal";
 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
 const PREF_BLOCKLIST_SUPPRESSUI       = "extensions.blocklist.suppressUI";
 const PREF_ONECRL_VIA_AMO             = "security.onecrl.via.amo";
 const PREF_BLOCKLIST_UPDATE_ENABLED   = "services.blocklist.update_enabled";
-const PREF_GENERAL_USERAGENT_LOCALE   = "general.useragent.locale";
 const PREF_APP_DISTRIBUTION           = "distribution.id";
 const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
 const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
 const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
 const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul"
 const DEFAULT_SEVERITY                = 3;
 const DEFAULT_LEVEL                   = 2;
@@ -237,24 +236,17 @@ function matchesOSABI(blocklistElement) 
 }
 
 /**
  * Gets the current value of the locale.  It's possible for this preference to
  * be localized, so we have to do a little extra work here.  Similar code
  * exists in nsHttpHandler.cpp when building the UA string.
  */
 function getLocale() {
-  try {
-      // Get the default branch
-      var defaultPrefs = gPref.getDefaultBranch(null);
-      return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE,
-                                          Ci.nsIPrefLocalizedString).data;
-  } catch (e) {}
-
-  return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
+  return Services.locale.getRequestedLocales();
 }
 
 /* Get the distribution pref values, from defaults only */
 function getDistributionPrefValue(aPrefName) {
   return gPref.getDefaultBranch(null).getCharPref(aPrefName, "default");
 }
 
 /**
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/Poison.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Printf.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/intl/LocaleService.h"
 
 #include "nsAppRunner.h"
 #include "mozilla/XREAppData.h"
 #include "mozilla/Bootstrap.h"
 #if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
 #include "nsUpdateDriver.h"
 #endif
 #include "ProfileReset.h"
@@ -283,16 +284,17 @@ namespace mozilla {
 int (*RunGTest)(int*, char**) = 0;
 } // namespace mozilla
 
 using namespace mozilla;
 using mozilla::Unused;
 using mozilla::scache::StartupCache;
 using mozilla::dom::ContentParent;
 using mozilla::dom::ContentChild;
+using mozilla::intl::LocaleService;
 
 // Save literal putenv string to environment variable.
 static void
 SaveToEnv(const char *putenv)
 {
   char *expr = strdup(putenv);
   if (expr)
     PR_SetEnv(expr);
@@ -4366,23 +4368,18 @@ XREMain::XRE_mainRun()
   }
 
   mDirProvider.DoStartup();
 
   OverrideDefaultLocaleIfNeeded();
 
 #ifdef MOZ_CRASHREPORTER
   nsCString userAgentLocale;
-  // Try a localized string first. This pref is always a localized string in
-  // Fennec, and might be elsewhere, too.
-  if (NS_SUCCEEDED(Preferences::GetLocalizedCString("general.useragent.locale", &userAgentLocale))) {
-    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale);
-  } else if (NS_SUCCEEDED(Preferences::GetCString("general.useragent.locale", &userAgentLocale))) {
-    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale);
-  }
+  LocaleService::GetInstance()->GetAppLocaleAsLangTag(userAgentLocale);
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("useragent_locale"), userAgentLocale);
 #endif
 
   appStartup->GetShuttingDown(&mShuttingDown);
 
   nsCOMPtr<nsICommandLineRunner> cmdLine;
 
   nsCOMPtr<nsIFile> workingDir;
   rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir));