Bug 668838 - Revise the flow of the locale picker. r=mfinkle
authorWes Johnston <wjohnston@mozilla.com>
Fri, 02 Sep 2011 09:37:50 -0700
changeset 76471 a9b98324d35aec81dd0c8610fb330dfe9cfd342e
parent 76470 cd28b53198eac2bc553853477e4a34f542c334fc
child 76472 b47c8ec93ccb9beaea96be9ddf180601b76e25ae
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersmfinkle
bugs668838
milestone9.0a1
Bug 668838 - Revise the flow of the locale picker. r=mfinkle
mobile/chrome/content/extensions.js
mobile/chrome/content/localePicker.js
mobile/chrome/content/localePicker.xul
mobile/locales/en-US/chrome/localepicker.properties
mobile/themes/core/gingerbread/localePicker.css
mobile/themes/core/localePicker.css
--- a/mobile/chrome/content/extensions.js
+++ b/mobile/chrome/content/extensions.js
@@ -425,16 +425,20 @@ var ExtensionsView = {
     let opType;
     if (aItem.getAttribute("type") == "search") {
       aItem.setAttribute("isDisabled", true);
       aItem._engine.hidden = true;
       opType = "needs-disable";
     } else if (aItem.getAttribute("type") == "theme") {
       aItem.addon.userDisabled = true;
       aItem.setAttribute("isDisabled", true);
+    } else if (aItem.getAttribute("type") == "locale") {
+      Services.prefs.clearUserPref("general.useragent.locale");
+      aItem.addon.userDisabled = true;
+      aItem.setAttribute("isDisabled", true);
     } else {
       aItem.addon.userDisabled = true;
       opType = this._getOpTypeForOperations(aItem.addon.pendingOperations);
 
       if (aItem.addon.pendingOperations & AddonManager.PENDING_DISABLE) {
         this.showRestart();
       } else {
         aItem.setAttribute("isDisabled", !aItem.addon.isActive);
--- a/mobile/chrome/content/localePicker.js
+++ b/mobile/chrome/content/localePicker.js
@@ -3,25 +3,26 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource:///modules/LocaleRepository.jsm");
 
 let stringPrefs = [
-  { selector: "#continue-in-button", pref: "continueIn", data: ["CURRENT_LANGUAGE"] },
+  { selector: "#continue-in-button", pref: "continueIn", data: ["CURRENT_LOCALE"] },
   { selector: "#change-language", pref: "choose", data: null },
   { selector: "#picker-title", pref: "chooseLanguage", data: null },
   { selector: "#continue-button", pref: "continue", data: null },
   { selector: "#cancel-button", pref: "cancel", data: null },
-  { selector: "#intalling-message", pref: "installing", data: ["CURRENT_LANGUAGE"]  },
+  { selector: "#intalling-message", pref: "installing", data: ["CURRENT_LOCALE"]  },
   { selector: "#cancel-install-button", pref: "cancel", data: null },
   { selector: "#installing-error", pref: "installerror", data: null },
-  { selector: "#install-continue", pref: "continue", data: null }
+  { selector: "#install-continue", pref: "continue", data: null },
+  { selector: "#loading-label", pref: "loading", data: null }
 ];
 
 let LocaleUI = {
   _strings: null,
 
   get strings() {
     if (!this._strings)
       this._strings = Services.strings.createBundle("chrome://browser/locale/localepicker.properties");
@@ -47,16 +48,36 @@ let LocaleUI = {
     return this._installerPage = document.getElementById("installer-page");
   },
 
   get _deck() {
     delete this._deck;
     return this._deck = document.getElementById("language-deck");
   },
 
+  _availableLocales: null,
+  get availableLocales() {
+    if (!this._availableLocales) {
+      let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
+      chrome.QueryInterface(Ci.nsIToolkitChromeRegistry);
+      let strings = Services.strings.createBundle("chrome://browser/content/languages.properties");
+      let availableLocales = chrome.getLocalesForPackage("browser");
+
+      this._availableLocales = [];
+      while (availableLocales.hasMore()) {
+        let locale = availableLocales.getNext();
+        let label = locale;
+        try { label = strings.GetStringFromName(locale); }
+        catch (e) { }
+        this._availableLocales.push({ addon: { id: locale, name: label, targetLocale: locale }});
+      }
+    }
+    return this._availableLocales;
+  },
+
   _currentInstall: null, // used to cancel an install
 
   get selectedPanel() {
     return this._deck.selectedPanel;
   },
 
   set selectedPanel(aPanel) {
     this._deck.selectedPanel = aPanel;
@@ -70,17 +91,17 @@ let LocaleUI = {
   _createItem: function(aId, aText, aLocale) {
     let item = document.createElement("richlistitem");
     item.setAttribute("id", aId);
 
     let description = document.createElement("description");
     description.appendChild(document.createTextNode(aText));
     description.setAttribute('flex', 1);
     item.appendChild(description);
-    item.setAttribute("locale", getTargetLanguage(aLocale.addon));
+    item.setAttribute("locale", getTargetLocale(aLocale.addon));
 
     if (aLocale) {
       item.locale = aLocale.addon;
       let checkbox = document.createElement("image");
       checkbox.classList.add("checkbox");
       item.appendChild(checkbox);
     } else {
       item.classList.add("message");
@@ -89,22 +110,22 @@ let LocaleUI = {
   },
 
   addLocales: function(aLocales) {
     let fragment = document.createDocumentFragment();
     let selectedItem = null;
     let bestMatch = NO_MATCH;
 
     for each (let locale in aLocales) {
-      let targetLang = getTargetLanguage(locale.addon);
-      if (document.querySelector('[locale="' + targetLang + '"]'))
+      let targetLocale = getTargetLocale(locale.addon);
+      if (document.querySelector('[locale="' + targetLocale + '"]'))
         continue;
 
-      let item = this._createItem(targetLang, locale.addon.name, locale);
-      let match = localesMatch(targetLang, this.language);
+      let item = this._createItem(targetLocale, locale.addon.name, locale);
+      let match = localesMatch(targetLocale, this.locale);
       if (match > bestMatch) {
         bestMatch = match;
         selectedItem = item;
       }
       fragment.appendChild(item);
     }
     this.list.appendChild(fragment);
     if (selectedItem && !this.list.selectedItem);
@@ -121,55 +142,55 @@ let LocaleUI = {
   showPicker: function() {
     LocaleUI.selectedPanel = LocaleUI._pickerPage;
     LocaleUI.loadLocales();
   },
 
   closePicker: function() {
     if (this._currentInstall) {
       Services.prefs.setBoolPref("intl.locale.matchOS", false);
-      Services.prefs.setCharPref("general.useragent.locale", getTargetLanguage(this._currentInstall));
+      Services.prefs.setCharPref("general.useragent.locale", getTargetLocale(this._currentInstall));
     }
     this.selectedPanel = this._mainPage;
   },
 
-  _language: "",
+  _locale: "",
 
-  set language(aVal) {
-    if (aVal == this._language)
+  set locale(aVal) {
+    if (aVal == this._locale)
       return;
 
     Services.prefs.setBoolPref("intl.locale.matchOS", false);
     Services.prefs.setCharPref("general.useragent.locale", aVal);
-    this._language = aVal;
+    this._locale = aVal;
 
     this.strings = null;
     this.updateStrings();
   },
 
-  get language() {
-    return this._language;
+  get locale() {
+    return this._locale;
   },
 
   set installStatus(aVal) {
     this._installerPage.selectedPanel = document.getElementById("installer-page-" + aVal);
   },
 
   clearInstallError: function() {
     this.installStatus = "installing";
     this.selectedPanel = this._pickerPage;
   },
 
-  selectLanguage: function(aEvent) {
+  selectLocale: function(aEvent) {
     let locale = this.list.selectedItem.locale;
     if (locale.install) {
       LocaleUI.strings = new FakeStringBundle(locale);
       this.updateStrings();
     } else {
-      this.language = getTargetLanguage(locale);
+      this.locale = getTargetLocale(locale);
       if (this._currentInstall)
         this._currentInstall = null;
     }
   },
 
   installAddon: function() {
     let locale = LocaleUI.list.selectedItem.locale;
     LocaleUI._currentInstall = locale;
@@ -182,32 +203,31 @@ let LocaleUI = {
       this.closePicker();
     }
   },
 
   cancelPicker: function() {
     if (this._currentInstall)
       this._currentInstall = null;
     // restore the last known "good" locale
-    this.language = this.defaultLanguage;
+    this.locale = this.defaultLocale;
     this.updateStrings();
     this.closePicker();
   },
 
   closeWindow : function() {
     // Trying to close this window and open a new one results in a corrupt UI.
     if (LocaleUI._currentInstall) {
       // a new locale was installed, restart the browser
       let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
     
       if (cancelQuit.data == false) {
         let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
         appStartup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eForceQuit);
-        Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", false);
       }
     } else {
       // selected locale is already installed, just open the window
       let argString = null;
       if (window.arguments) {
         argString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
         argString.data = window.arguments.join(",");
       }
@@ -218,17 +238,17 @@ let LocaleUI = {
 
   cancelInstall: function () {
     if (LocaleUI._currentInstall) {
       let addonInstall = LocaleUI._currentInstall.install;
       try { addonInstall.cancel(); }
       catch(ex) { }
       LocaleUI._currentInstall = null;
 
-      this.language = this.defaultLanguage;
+      this.locale = this.defaultLocale;
     }
   },
 
   updateStrings: function (aAddon) {
     stringPrefs.forEach(function(aPref) {
       if (!aPref.element)
         aPref.element = document.querySelector(aPref.selector);
   
@@ -236,32 +256,32 @@ let LocaleUI = {
       try {
         string = getString(aPref.pref, aPref.data, aAddon);
       } catch(ex) { }
       aPref.element.textContent = string;
     });
   }
 }
 
-// Gets the target language for a locale addon
+// Gets the target locale for an addon
 // For now this returns the targetLocale, although if and addon doesn't
 // specify a targetLocale we could attempt to guess the locale from the addon's name
-function getTargetLanguage(aAddon) {
+function getTargetLocale(aAddon) {
   return aAddon.targetLocale;
 }
 
 // Gets a particular string for the passed in locale
 // Parameters: aStringName - The name of the string property to get
 //             aDataset - an array of properties to use in a formatted string
 //             aAddon - An addon to attempt to get dataset properties from
 function getString(aStringName, aDataSet, aAddon) {
   if (aDataSet) {
     let databundle = aDataSet.map(function(aData) {
       switch (aData) {
-        case "CURRENT_LANGUAGE" :
+        case "CURRENT_LOCALE" :
           if (aAddon)
             return aAddon.name;
           try { return LocaleUI.strings.GetStringFromName("name"); }
           catch(ex) { return null; }
           break;
         default :
       }
       return "";
@@ -284,18 +304,18 @@ let installListener = {
     LocaleUI.showPicker();
   },
   onDownloadFailed: function(install) {
     LocaleUI.cancelInstall();
     LocaleUI.installStatus = "error";
   },
   onInstallStarted: function(install) { },
   onInstallEnded: function(install, addon) {
-    LocaleUI.updateStrings();
-    LocaleUI.closePicker();
+    LocaleUI.locale = getTargetLocale(LocaleUI._currentInstall);
+    LocaleUI.closeWindow();
   },
   onInstallCancelled: function(install) {
     LocaleUI.cancelInstall();
     LocaleUI.showPicker();
   },
   onInstallFailed: function(install) {
     LocaleUI.cancelInstall();
     LocaleUI.installStatus = "error";
@@ -317,35 +337,62 @@ function localesMatch(aLocale1, aLocale2
   return (short1 == short2) ? GOOD_MATCH : NO_MATCH;
 }
 
 function start() {
   let mouseModule = new MouseModule();
 
   // if we have gotten this far, we can assume that we don't have anything matching the system
   // locale and we should show the locale picker
+  LocaleUI._mainPage.setAttribute("mode", "loading");
   let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry);
   chrome.QueryInterface(Ci.nsIToolkitChromeRegistry);
-  let availableLocales = chrome.getLocalesForPackage("browser");
-  let strings = Services.strings.createBundle("chrome://browser/content/languages.properties");
-  LocaleUI.availableLocales = [];
-  while (availableLocales.hasMore()) {
-    let locale = availableLocales.getNext();
-
-    let label = locale;
-    try { label = strings.GetStringFromName(locale); }
-    catch (e) { }
-    LocaleUI.availableLocales.push({addon: { id: locale, name: label, targetLocale: locale }});
-  }
-
-  LocaleUI._language = chrome.getSelectedLocale("browser");
+  LocaleUI._locale = chrome.getSelectedLocale("browser");
   LocaleUI.updateStrings();
 
+  // if we haven't gotten the list of available locales from AMO within 5 seconds, we give up
+  // users can try downloading the list again by selecting "Choose another locale"
+  let timeout = setTimeout(function() {
+    LocaleUI._mainPage.removeAttribute("mode");
+    timeout = null;
+  }, 5000);
+
+  // Look on AMO for something that matches the system locale
+  LocaleRepository.getLocales(function lp_initalDownload(aLocales) {
+    if (!LocaleUI._mainPage.hasAttribute("mode")) return;
+
+    clearTimeout(timeout);
+    LocaleUI._mainPage.removeAttribute("mode");
+
+    let currentLocale = Services.prefs.getCharPref("general.useragent.locale");
+    let match = NO_MATCH;
+    let matchingLocale = null;
+
+    for each (let locale in aLocales) {
+      let targetLocale = getTargetLocale(locale.addon);
+      let newMatch = localesMatch(targetLocale, currentLocale);
+      if (newMatch > match) {
+        matchingLocale = locale;
+        match = newMatch;
+      }
+    }
+    if (matchingLocale) {
+      // if we found something, try to install it automatically
+      LocaleUI.strings = new FakeStringBundle(matchingLocale.addon);
+      LocaleUI.updateStrings();
+      LocaleUI._currentInstall = matchingLocale.addon;
+
+      LocaleUI.selectedPanel = LocaleUI._installerPage;
+      matchingLocale.addon.install.addListener(installListener);
+      matchingLocale.addon.install.install();
+    }
+  });
+
   // update the page strings and show the correct page
-  LocaleUI.defaultLanguage = LocaleUI._language;
+  LocaleUI.defaultLocale = LocaleUI._locale;
   window.addEventListener("resize", resizeHandler, false);
 }
 
 function resizeHandler() {
   let elements = document.getElementsByClassName("window-width");
   for (let i = 0; i < elements.length; i++)
     elements[i].setAttribute("width", Math.min(800, window.innerWidth));
 }
--- a/mobile/chrome/content/localePicker.xul
+++ b/mobile/chrome/content/localePicker.xul
@@ -10,22 +10,23 @@
         height="800">
   <script src="chrome://browser/content/Util.js" type="application/javascript;version=1.8"/>
   <script src="chrome://browser/content/input.js" type="application/javascript;version=1.8"/>
   <script src="chrome://browser/content/localePicker.js" type="application/javascript;version=1.8"/>
   <deck id="language-deck" flex="1">
     <vbox id="main-page" class="pane" flex="1">
       <spacer flex="1"/>
       <button class="continue" id="continue-in-button" onclick="LocaleUI.closeWindow();" crop="center"/>
+      <label class="loadingLabel" id="loading-label"/>
       <description id="change-language" class="link" onclick="LocaleUI.showPicker();" role="button"/>
     </vbox>
 
     <vbox id="picker-page" class="pane" flex="1">
       <description id="picker-title"/>
-      <richlistbox id="language-list" onclick="LocaleUI.selectLanguage(event);" flex="1" class="window-width"/>
+      <richlistbox id="language-list" onclick="LocaleUI.selectLocale(event);" flex="1" class="window-width"/>
       <hbox class="footer">
         <button id="cancel-button" class="cancel" onclick="LocaleUI.cancelPicker();" crop="center"/>
         <button id="continue-button" class="continue" onclick="LocaleUI.installAddon();" crop="center"/>
       </hbox>
     </vbox>
 
     <deck id="installer-page" class="pane" flex="1">
       <vbox id="installer-page-installing" flex="1" pack="center" align="center">
--- a/mobile/locales/en-US/chrome/localepicker.properties
+++ b/mobile/locales/en-US/chrome/localepicker.properties
@@ -5,8 +5,9 @@ continueIn=Continue in %S
 # Do not just translate the word 'English'
 name=English
 choose=Choose a different language
 chooseLanguage=Choose a Language
 cancel=Cancel
 continue=Continue
 installing=Installing %S
 installerror=Error installing language
+loading=Loading…
--- a/mobile/themes/core/gingerbread/localePicker.css
+++ b/mobile/themes/core/gingerbread/localePicker.css
@@ -45,16 +45,22 @@
 }
 
 #main-page {
   background-image: url("chrome://branding/content/logo.png");
   background-repeat: no-repeat;
   background-position: center center;
 }
 
+#main-page:not([mode="loading"]) #loading-label,
+#main-page[mode] #continue-in-button,
+#main-page[mode] #change-language {
+  visibility: hidden;
+}
+
 #picker-title {
   font-weight: bold;
   font-size: @font_normal@;
 }
 
 .link {
   padding: @padding_xlarge@ 0px;
   font-weight: bold;
--- a/mobile/themes/core/localePicker.css
+++ b/mobile/themes/core/localePicker.css
@@ -45,16 +45,22 @@
 }
 
 #main-page {
   background-image: url("chrome://branding/content/logo.png");
   background-repeat: no-repeat;
   background-position: center center;
 }
 
+#main-page:not([mode="loading"]) #loading-label,
+#main-page[mode] #continue-in-button,
+#main-page[mode] #change-language {
+  visibility: hidden;
+}
+
 #picker-title {
   font-weight: bold;
   font-size: @font_normal@;
 }
 
 .link {
   padding: @padding_xlarge@ 0px;
   font-weight: bold;