merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 04 Dec 2013 09:42:09 +0100
changeset 174366 9fe5e212fa41e6f9d85abea50467003301509b42
parent 174358 8187818246ada71a574b536673231c076e9984ac (current diff)
parent 174365 f3f8f27131807410440af922f6741d5cbbbe541f (diff)
child 174367 8d31caaae0c62d2aaab0cda4bdc6a65f1835ef56
child 174376 9688476c1544689573c7d3ef805fd61c2fc0f631
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -553,20 +553,24 @@ CustomizeMode.prototype = {
                                 aNode.getAttribute("contextmenu") ? "contextmenu" : "";
     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
     let contextMenuForPlace = aPlace == "panel" ?
                                 kPanelItemContextMenu :
                                 kPaletteItemContextMenu;
     if (aPlace != "toolbar") {
       wrapper.setAttribute("context", contextMenuForPlace);
     }
-    if (currentContextMenu) {
+    // Only keep track of the menu if it is non-default.
+    if (currentContextMenu &&
+        currentContextMenu != contextMenuForPlace) {
       aNode.setAttribute("wrapped-context", currentContextMenu);
       aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName)
       aNode.removeAttribute(contextMenuAttrName);
+    } else if (currentContextMenu == contextMenuForPlace) {
+      aNode.removeAttribute(contextMenuAttrName);
     }
 
     wrapper.addEventListener("mousedown", this);
     wrapper.addEventListener("mouseup", this);
 
     return wrapper;
   },
 
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -236,16 +236,61 @@ let gTests = [
       yield hiddenContextPromise;
     },
     teardown: function() {
       yield endCustomizing(this.otherWin);
       this.otherWin.close();
       this.otherWin = null;
     }
   },
+  {
+    desc: "Bug 945191 - Combined buttons show wrong context menu options when they are in the toolbar.",
+    setup: startCustomizing,
+    run: function () {
+      let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+      let shownPromise = contextMenuShown(contextMenu);
+      let zoomControls = document.getElementById("wrapper-zoom-controls");
+      EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
+      yield shownPromise;
+      // Execute the command to move the item from the panel to the toolbar.
+      contextMenu.childNodes[0].doCommand();
+      let hiddenPromise = contextMenuHidden(contextMenu);
+      contextMenu.hidePopup();
+      yield hiddenPromise;
+      yield endCustomizing();
+
+      zoomControls = document.getElementById("zoom-controls");
+      is(zoomControls.parentNode.id, "nav-bar-customization-target", "Zoom-controls should be on the nav-bar");
+
+      contextMenu = document.getElementById("toolbar-context-menu");
+      shownPromise = contextMenuShown(contextMenu);
+      EventUtils.synthesizeMouse(zoomControls, 2, 2, {type: "contextmenu", button: 2});
+      yield shownPromise;
+
+      let expectedEntries = [
+        [".customize-context-addToPanel", true],
+        [".customize-context-removeFromToolbar", true],
+        ["---"]
+      ];
+      if (!isOSX) {
+        expectedEntries.push(["#toggle_toolbar-menubar", true]);
+      }
+      expectedEntries.push(
+        ["#toggle_PersonalToolbar", true],
+        ["---"],
+        [".viewCustomizeToolbar", true]
+      );
+      checkContextMenu(contextMenu, expectedEntries);
+
+      hiddenPromise = contextMenuHidden(contextMenu);
+      contextMenu.hidePopup();
+      yield hiddenPromise;
+    },
+    teardown: resetCustomization,
+  }
 ];
 
 function test() {
   waitForExplicitFinish();
   runTests(gTests);
 }
 
 function contextMenuShown(aContextMenu) {
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -119,25 +119,26 @@ abstract public class BrowserApp extends
     private BrowserToolbar mBrowserToolbar;
     private HomePager mHomePager;
     private View mHomePagerContainer;
     protected Telemetry.Timer mAboutHomeStartupTimer = null;
     private ActionModeCompat mActionMode;
 
     private static final int GECKO_TOOLS_MENU = -1;
     private static final int ADDON_MENU_OFFSET = 1000;
-    private class MenuItemInfo {
+    private static class MenuItemInfo {
         public int id;
         public String label;
         public String icon;
         public boolean checkable = false;
         public boolean checked = false;
         public boolean enabled = true;
         public boolean visible = true;
         public int parent;
+        public boolean added = false;    // So we can re-add after a locale change.
     }
 
     // The types of guest mdoe dialogs we show
     private static enum GuestModeDialog {
         ENTERING,
         LEAVING
     }
 
@@ -1634,16 +1635,30 @@ abstract public class BrowserApp extends
             hideHomePager();
         }
     }
 
     private void showHomePager(HomePager.Page page) {
         showHomePagerWithAnimator(page, null);
     }
 
+    @Override
+    public void onLocaleReady(final String locale) {
+        super.onLocaleReady(locale);
+        if (mHomePager != null) {
+            // Blow it away and rebuild it with the right strings.
+            mHomePager.redisplay(getSupportFragmentManager());
+        }
+
+        if (mMenu != null) {
+            mMenu.clear();
+            onCreateOptionsMenu(mMenu);
+        }
+    }
+
     private void showHomePagerWithAnimator(HomePager.Page page, PropertyAnimator animator) {
         if (isHomePagerVisible()) {
             return;
         }
 
         // Refresh toolbar height to possibly restore the toolbar padding
         refreshToolbarHeight();
 
@@ -1806,17 +1821,17 @@ abstract public class BrowserApp extends
                     mIsHidingTabs = false;
                 }
                 return true;
             }
             return false;
         }
     }
 
-    private Menu findParentMenu(Menu menu, MenuItem item) {
+    private static Menu findParentMenu(Menu menu, MenuItem item) {
         final int itemId = item.getItemId();
 
         final int count = (menu != null) ? menu.size() : 0;
         for (int i = 0; i < count; i++) {
             MenuItem menuItem = menu.getItem(i);
             if (menuItem.getItemId() == itemId) {
                 return menu;
             }
@@ -1826,84 +1841,104 @@ abstract public class BrowserApp extends
                     return parent;
                 }
             }
         }
 
         return null;
     }
 
-    private void addAddonMenuItem(final MenuItemInfo info) {
-        if (mMenu == null) {
-            if (mAddonMenuItemsCache == null)
-                mAddonMenuItemsCache = new Vector<MenuItemInfo>();
-
-            mAddonMenuItemsCache.add(info);
-            return;
-        }
-
-        Menu menu;
+    /**
+     * Add the provided item to the provided menu, which should be
+     * the root (mMenu).
+     */
+    private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
+        info.added = true;
+        
+        final Menu destination;
         if (info.parent == 0) {
-            menu = mMenu;
+            destination = menu;
         } else if (info.parent == GECKO_TOOLS_MENU) {
-            MenuItem tools = mMenu.findItem(R.id.tools);
-            menu = tools != null ? tools.getSubMenu() : mMenu;
+            MenuItem tools = menu.findItem(R.id.tools);
+            destination = tools != null ? tools.getSubMenu() : menu;
         } else {
-            MenuItem parent = mMenu.findItem(info.parent);
-            if (parent == null)
+            MenuItem parent = menu.findItem(info.parent);
+            if (parent == null) {
                 return;
+            }
 
-            Menu parentMenu = findParentMenu(mMenu, parent);
+            Menu parentMenu = findParentMenu(menu, parent);
 
             if (!parent.hasSubMenu()) {
                 parentMenu.removeItem(parent.getItemId());
-                menu = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
-                if (parent.getIcon() != null)
-                    ((SubMenu) menu).getItem().setIcon(parent.getIcon());
+                destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
+                if (parent.getIcon() != null) {
+                    ((SubMenu) destination).getItem().setIcon(parent.getIcon());
+                }
             } else {
-                menu = parent.getSubMenu();
+                destination = parent.getSubMenu();
             }
         }
 
-        MenuItem item = menu.add(Menu.NONE, info.id, Menu.NONE, info.label);
+        MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
+
         item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
             @Override
             public boolean onMenuItemClick(MenuItem item) {
-                Log.i(LOGTAG, "menu item clicked");
+                Log.i(LOGTAG, "Menu item clicked");
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET)));
                 return true;
             }
         });
 
-        if (info.icon != null) {
+        if (info.icon == null) {
+            item.setIcon(R.drawable.ic_menu_addons_filler);
+        } else {
             final int id = info.id;
             BitmapUtils.getDrawable(this, info.icon, new BitmapUtils.BitmapLoader() {
                 @Override
                 public void onBitmapFound(Drawable d) {
-                    MenuItem item = mMenu.findItem(id);
+                    // TODO: why do we re-find the item?
+                    MenuItem item = destination.findItem(id);
                     if (item == null) {
                         return;
                     }
                     if (d == null) {
                         item.setIcon(R.drawable.ic_menu_addons_filler);
                         return;
                     }
                     item.setIcon(d);
                 }
             });
-        } else {
-            item.setIcon(R.drawable.ic_menu_addons_filler);
         }
 
         item.setCheckable(info.checkable);
         item.setChecked(info.checked);
         item.setEnabled(info.enabled);
         item.setVisible(info.visible);
     }
 
+    private void addAddonMenuItem(final MenuItemInfo info) {
+        if (mAddonMenuItemsCache == null) {
+            mAddonMenuItemsCache = new Vector<MenuItemInfo>();
+        }
+
+        // Mark it as added if the menu was ready.
+        info.added = (mMenu != null);
+
+        // Always cache so we can rebuild after a locale switch.
+        mAddonMenuItemsCache.add(info);
+
+        if (mMenu == null) {
+            return;
+        }
+
+        addAddonMenuItemToMenu(mMenu, info);
+    }
+
     private void removeAddonMenuItem(int id) {
         // Remove add-on menu item from cache, if available.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
                  if (item.id == id) {
                      mAddonMenuItemsCache.remove(item);
                      break;
                  }
@@ -1923,52 +1958,55 @@ abstract public class BrowserApp extends
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
                 if (item.id == id) {
                     item.label = options.optString("name", item.label);
                     item.checkable = options.optBoolean("checkable", item.checkable);
                     item.checked = options.optBoolean("checked", item.checked);
                     item.enabled = options.optBoolean("enabled", item.enabled);
                     item.visible = options.optBoolean("visible", item.visible);
+                    item.added = (mMenu != null);
                     break;
                 }
             }
         }
 
-        if (mMenu == null)
+        if (mMenu == null) {
             return;
+        }
 
         MenuItem menuItem = mMenu.findItem(id);
         if (menuItem != null) {
             menuItem.setTitle(options.optString("name", menuItem.getTitle().toString()));
             menuItem.setCheckable(options.optBoolean("checkable", menuItem.isCheckable()));
             menuItem.setChecked(options.optBoolean("checked", menuItem.isChecked()));
             menuItem.setEnabled(options.optBoolean("enabled", menuItem.isEnabled()));
             menuItem.setVisible(options.optBoolean("visible", menuItem.isVisible()));
         }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
+        // Sets mMenu = menu.
         super.onCreateOptionsMenu(menu);
 
         // Inform the menu about the action-items bar. 
-        if (menu instanceof GeckoMenu && HardwareUtils.isTablet())
+        if (menu instanceof GeckoMenu &&
+            HardwareUtils.isTablet()) {
             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
+        }
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.browser_app_menu, mMenu);
 
-        // Add add-on menu items if any.
+        // Add add-on menu items, if any exist.
         if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
             for (MenuItemInfo item : mAddonMenuItemsCache) {
-                 addAddonMenuItem(item);
+                addAddonMenuItemToMenu(mMenu, item);
             }
-
-            mAddonMenuItemsCache.clear();
         }
 
         // Action providers are available only ICS+.
         if (Build.VERSION.SDK_INT >= 14) {
             MenuItem share = mMenu.findItem(R.id.share);
             GeckoActionProvider provider = new GeckoActionProvider(this);
             share.setActionProvider(provider);
         }
--- a/mobile/android/base/ContextGetter.java
+++ b/mobile/android/base/ContextGetter.java
@@ -1,6 +1,15 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
 package org.mozilla.gecko;
-import android.content.Context;
 
-interface ContextGetter {
-  Context getContext();
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public interface ContextGetter {
+    Context getContext();
+    SharedPreferences getSharedPreferences();
 }
+
--- a/mobile/android/base/GeckoActivity.java
+++ b/mobile/android/base/GeckoActivity.java
@@ -7,16 +7,25 @@ package org.mozilla.gecko;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.support.v4.app.FragmentActivity;
 
 public class GeckoActivity extends FragmentActivity implements GeckoActivityStatus {
     // has this activity recently started another Gecko activity?
     private boolean mGeckoActivityOpened = false;
 
+    /**
+     * Display any resources that show strings or encompass locale-specific
+     * representations.
+     *
+     * onLocaleReady must always be called on the UI thread.
+     */
+    public void onLocaleReady(final String locale) {
+    }
+
     @Override
     public void onPause() {
         super.onPause();
 
         if (getApplication() instanceof GeckoApplication) {
             ((GeckoApplication) getApplication()).onActivityPause(this);
         }
     }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -121,22 +121,28 @@ import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-abstract public class GeckoApp
-                extends GeckoActivity 
-    implements GeckoEventListener, SensorEventListener, LocationListener,
-                           Tabs.OnTabsChangedListener, GeckoEventResponder,
-                           GeckoMenu.Callback, GeckoMenu.MenuPresenter,
-                           ContextGetter, GeckoAppShell.GeckoInterface
+public abstract class GeckoApp
+    extends GeckoActivity 
+    implements
+    ContextGetter,
+    GeckoAppShell.GeckoInterface,
+    GeckoEventListener,
+    GeckoEventResponder,
+    GeckoMenu.Callback,
+    GeckoMenu.MenuPresenter,
+    LocationListener,
+    SensorEventListener,
+    Tabs.OnTabsChangedListener
 {
     private static final String LOGTAG = "GeckoApp";
 
     private static enum StartupAction {
         NORMAL,     /* normal application start */
         URL,        /* launched with a passed URL */
         PREFETCH    /* launched with a passed URL that we prefetch */
     }
@@ -228,20 +234,30 @@ abstract public class GeckoApp
             super(message);
         }
     }
 
     void toggleChrome(final boolean aShow) { }
 
     void focusChrome() { }
 
+    @Override
     public Context getContext() {
         return sAppContext;
     }
 
+    @Override
+    public SharedPreferences getSharedPreferences() {
+        return GeckoApp.getAppSharedPreferences();
+    }
+
+    public static SharedPreferences getAppSharedPreferences() {
+        return GeckoApp.sAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
+    }
+
     public Activity getActivity() {
         return this;
     }
 
     public LocationListener getLocationListener() {
         if (mShouldReportGeoData && mPhoneStateListener == null) {
             mPhoneStateListener = new PhoneStateListener() {
                 public void onSignalStrengthsChanged(SignalStrength signalStrength) {
@@ -253,20 +269,16 @@ abstract public class GeckoApp
         }
         return this;
     }
 
     public SensorEventListener getSensorEventListener() {
         return this;
     }
 
-    public static SharedPreferences getAppSharedPreferences() {
-        return GeckoApp.sAppContext.getSharedPreferences(PREFS_NAME, 0);
-    }
-
     public View getCameraView() {
         return mCameraView;
     }
 
     public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
         mAppStateListeners.add(listener);
     }
 
@@ -698,16 +710,18 @@ abstract public class GeckoApp
                 }
                 JSONObject handlersJSON = new JSONObject();
                 handlersJSON.put("apps", new JSONArray(appList));
                 mCurrentResponse = handlersJSON.toString();
             } else if (event.equals("Intent:Open")) {
                 GeckoAppShell.openUriExternal(message.optString("url"),
                     message.optString("mime"), message.optString("packageName"),
                     message.optString("className"), message.optString("action"), message.optString("title"));
+            } else if (event.equals("Locale:Set")) {
+                setLocale(message.getString("locale"));
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     public String getResponse(JSONObject origMessage) {
         String res = mCurrentResponse;
@@ -1174,36 +1188,37 @@ abstract public class GeckoApp
 
                 if (profileName != null || profilePath != null) {
                     mProfile = GeckoProfile.get(this, profileName, profilePath);
                 }
             }
         }
 
         BrowserDB.initialize(getProfile().getName());
-        ((GeckoApplication)getApplication()).initialize();
+        ((GeckoApplication) getApplication()).initialize();
 
         sAppContext = this;
         GeckoAppShell.setContextGetter(this);
         GeckoAppShell.setGeckoInterface(this);
         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
 
         Tabs.getInstance().attachToContext(this);
         try {
             Favicons.attachToContext(this);
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
         }
 
-        // When we detect a locale change, we need to restart Gecko, which
-        // actually means restarting the entire application. This logic should
-        // actually be handled elsewhere since GeckoApp may not be alive to
-        // handle this event if "Don't keep activities" is enabled (filed as
-        // bug 889082).
-        if (((GeckoApplication)getApplication()).needsRestart()) {
+        // Did the OS locale change while we were backgrounded? If so,
+        // we need to die so that Gecko will re-init add-ons that touch
+        // the UI.
+        // This is using a sledgehammer to crack a nut, but it'll do for
+        // now.
+        if (LocaleManager.systemLocaleDidChange()) {
+            Log.i(LOGTAG, "System locale changed. Restarting.");
             doRestart();
             System.exit(0);
             return;
         }
 
         if (GeckoThread.isCreated()) {
             // This happens when the GeckoApp activity is destroyed by Android
             // without killing the entire application (see Bug 769269).
@@ -1274,16 +1289,21 @@ abstract public class GeckoApp
         }
 
         // Perform background initialization.
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
 
+                // Wait until now to set this, because we'd rather throw an exception than 
+                // have a caller of LocaleManager regress startup.
+                LocaleManager.setContextGetter(GeckoApp.this);
+                LocaleManager.initialize();
+
                 SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
                 if (previousSession.wasKilled()) {
                     Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
                 }
 
                 SharedPreferences.Editor editor = prefs.edit();
                 editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
 
@@ -1294,34 +1314,70 @@ abstract public class GeckoApp
                 editor.commit();
 
                 // The lifecycle of mHealthRecorder is "shortly after onCreate"
                 // through "onDestroy" -- essentially the same as the lifecycle
                 // of the activity itself.
                 final String profilePath = getProfile().getDir().getAbsolutePath();
                 final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
                 Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
+
                 final String osLocale = Locale.getDefault().toString();
-                Log.d(LOGTAG, "Locale is " + osLocale);
-
-                // Replace the duplicate `osLocale` argument when we support switchable
-                // application locales.
+                String appLocale = LocaleManager.getAndApplyPersistedLocale();
+                Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
+
+                if (appLocale == null) {
+                    appLocale = osLocale;
+                }
+
                 mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
                                                             profilePath,
                                                             dispatcher,
                                                             osLocale,
-                                                            osLocale,    // Placeholder.
+                                                            appLocale,
                                                             previousSession);
+
+                final String uiLocale = appLocale;
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        GeckoApp.this.onLocaleReady(uiLocale);
+                    }
+                });
             }
         });
 
         GeckoAppShell.setNotificationClient(makeNotificationClient());
         NotificationHelper.init(getApplicationContext());
     }
 
+    /**
+     * At this point, the resource system and the rest of the browser are
+     * aware of the locale.
+     *
+     * Now we can display strings!
+     */
+    @Override
+    public void onLocaleReady(final String locale) {
+        if (!ThreadUtils.isOnUiThread()) {
+            throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
+        }
+
+        // The URL bar hint needs to be populated.
+        TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
+        if (urlBar == null) {
+            return;
+        }
+        final String hint = getResources().getString(R.string.url_bar_default_text);
+        urlBar.setHint(hint);
+
+        // Allow onConfigurationChanged to take care of the rest.
+        onConfigurationChanged(getResources().getConfiguration());
+    }
+
     protected void initializeChrome() {
         mDoorHangerPopup = new DoorHangerPopup(this, null);
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
 
         if (mCameraView == null) {
             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                 mCameraView = new SurfaceView(this);
@@ -1521,16 +1577,17 @@ abstract public class GeckoApp
         registerEventListener("Sanitize:ClearHistory");
         registerEventListener("Update:Check");
         registerEventListener("Update:Download");
         registerEventListener("Update:Install");
         registerEventListener("PrivateBrowsing:Data");
         registerEventListener("Contact:Add");
         registerEventListener("Intent:Open");
         registerEventListener("Intent:GetHandlers");
+        registerEventListener("Locale:Set");
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
 
         mPromptService = new PromptService(this);
@@ -1574,33 +1631,16 @@ abstract public class GeckoApp
                 final Context context = GeckoApp.this;
                 AnnouncementsBroadcastService.recordLastLaunch(context);
 
                 // Kick off our background services. We do this by invoking the broadcast
                 // receiver, which uses the system alarm infrastructure to perform tasks at
                 // intervals.
                 GeckoPreferences.broadcastAnnouncementsPref(context);
                 GeckoPreferences.broadcastHealthReportUploadPref(context);
-
-                /*
-                XXXX see Bug 635342.
-                We want to disable this code if possible.  It is about 145ms in runtime.
-
-                If this code ever becomes live again, you'll need to chain the
-                new locale into BrowserHealthRecorder correctly. See
-                GeckoAppShell.setSelectedLocale.
-                We pass the OS locale into the BHR constructor: we need to grab
-                that *before* we modify the current locale!
-
-                SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
-                String localeCode = settings.getString(getPackageName() + ".locale", "");
-                if (localeCode != null && localeCode.length() > 0)
-                    GeckoAppShell.setSelectedLocale(localeCode);
-                */
-
                 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
                     return;
                 }
             }
         }, 50);
 
         if (mIsRestoringActivity) {
             GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
@@ -2135,16 +2175,18 @@ abstract public class GeckoApp
             return;
         for (File file : files) {
             file.delete();
         }
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
+        Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
+        LocaleManager.correctLocale(getResources(), newConfig);
         super.onConfigurationChanged(newConfig);
 
         if (mOrientation != newConfig.orientation) {
             mOrientation = newConfig.orientation;
             if (mFormAssistPopup != null)
                 mFormAssistPopup.hide();
             refreshChrome();
         }
@@ -2720,9 +2762,57 @@ abstract public class GeckoApp
         int versionCode = 0;
         try {
             versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
         } catch (NameNotFoundException e) {
             Log.wtf(LOGTAG, getPackageName() + " not found", e);
         }
         return versionCode;
     }
+
+    // FHR reason code for a session end prior to a restart for a
+    // locale change.
+    private static final String SESSION_END_LOCALE_CHANGED = "L";
+
+    /**
+     * Use LocaleManager to change our persisted and current locales,
+     * and poke BrowserHealthRecorder to tell it of our changed state.
+     */
+    private void setLocale(final String locale) {
+        if (locale == null) {
+            return;
+        }
+        final String resultant = LocaleManager.setSelectedLocale(locale);
+        if (resultant == null) {
+            return;
+        }
+
+        final BrowserHealthRecorder rec = mHealthRecorder;
+        if (rec == null) {
+            return;
+        }
+
+        final boolean startNewSession = true;
+        final boolean shouldRestart = false;
+        rec.onAppLocaleChanged(resultant);
+        rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
+
+        if (!shouldRestart) {
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    GeckoApp.this.onLocaleReady(resultant);
+                }
+            });
+            return;
+        }
+
+        // Do this in the background so that the health recorder has its
+        // time to finish.
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                GeckoApp.this.doRestart();
+                GeckoApp.this.finish();
+            }
+        });
+    }
 }
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -224,18 +224,17 @@ public class GeckoAppShell
                     if (mainThread != null && thread != mainThread) {
                         Log.e(LOGTAG, "Main thread stack:");
                         for (StackTraceElement ste : mainThread.getStackTrace()) {
                             Log.e(LOGTAG, ste.toString());
                         }
                     }
 
                     if (e instanceof OutOfMemoryError) {
-                        SharedPreferences prefs =
-                            getContext().getSharedPreferences(GeckoApp.PREFS_NAME, 0);
+                        SharedPreferences prefs = getSharedPreferences();
                         SharedPreferences.Editor editor = prefs.edit();
                         editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
                         editor.commit();
                     }
                 } finally {
                     reportJavaCrash(getStackTraceString(e));
                 }
             }
@@ -1582,55 +1581,16 @@ public class GeckoAppShell
                 return LINK_TYPE_4G; // 3.9G
             case TelephonyManager.NETWORK_TYPE_UNKNOWN:
             default:
                 Log.w(LOGTAG, "Connected to an unknown mobile network!");
                 return LINK_TYPE_UNKNOWN;
         }
     }
 
-    @WrapElementForJNI
-    public static void setSelectedLocale(String localeCode) {
-        /* Bug 713464: This method is still called from Gecko side.
-           Earlier we had an option to run Firefox in a language other than system's language.
-           However, this is not supported as of now.
-           Gecko resets the locale to en-US by calling this function with an empty string.
-           This affects GeckoPreferences activity in multi-locale builds.
-
-        N.B., if this code ever becomes live again, you need to hook it up to locale
-        recording in BrowserHealthRecorder: we track the current app and OS locales
-        as part of the recorded environment.
-
-        See similar note in GeckoApp.java for the startup path.
-
-        //We're not using this, not need to save it (see bug 635342)
-        SharedPreferences settings =
-            getContext().getPreferences(Activity.MODE_PRIVATE);
-        settings.edit().putString(getContext().getPackageName() + ".locale",
-                                  localeCode).commit();
-        Locale locale;
-        int index;
-        if ((index = localeCode.indexOf('-')) != -1 ||
-            (index = localeCode.indexOf('_')) != -1) {
-            String langCode = localeCode.substring(0, index);
-            String countryCode = localeCode.substring(index + 1);
-            locale = new Locale(langCode, countryCode);
-        } else {
-            locale = new Locale(localeCode);
-        }
-        Locale.setDefault(locale);
-
-        Resources res = getContext().getBaseContext().getResources();
-        Configuration config = res.getConfiguration();
-        config.locale = locale;
-        res.updateConfiguration(config, res.getDisplayMetrics());
-        */
-    }
-
-
     @WrapElementForJNI(stubName = "GetSystemColoursWrapper")
     public static int[] getSystemColors() {
         // attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
         final int[] attrsAppearance = {
             android.R.attr.textColor,
             android.R.attr.textColorPrimary,
             android.R.attr.textColorPrimaryInverse,
             android.R.attr.textColorSecondary,
@@ -2141,16 +2101,23 @@ public class GeckoAppShell
     public static Context getContext() {
         return sContextGetter.getContext();
     }
 
     public static void setContextGetter(ContextGetter cg) {
         sContextGetter = cg;
     }
 
+    public static SharedPreferences getSharedPreferences() {
+        if (sContextGetter == null) {
+            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
+        }
+        return sContextGetter.getSharedPreferences();
+    }
+
     public interface AppStateListener {
         public void onPause();
         public void onResume();
         public void onOrientationChanged();
     }
 
     public interface GeckoInterface {
         public GeckoProfile getProfile();
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -7,30 +7,56 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Application;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.util.Log;
 
 public class GeckoApplication extends Application {
+    private static final String LOG_TAG = "GeckoApplication";
 
     private boolean mInited;
     private boolean mInBackground;
     private boolean mPausedGecko;
-    private boolean mNeedsRestart;
 
     private LightweightTheme mLightweightTheme;
 
+    /**
+     * We need to do locale work here, because we need to intercept
+     * each hit to onConfigurationChanged.
+     */
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        Log.d(LOG_TAG, "onConfigurationChanged: " + config.locale +
+                       ", background: " + mInBackground);
+
+        // Do nothing if we're in the background. It'll simply cause a loop
+        // (Bug 936756 Comment 11), and it's not necessary.
+        if (mInBackground) {
+            super.onConfigurationChanged(config);
+            return;
+        }
+
+        // Otherwise, correct the locale. This catches some cases that GeckoApp
+        // doesn't get a chance to.
+        try {
+            LocaleManager.correctLocale(getResources(), config);
+        } catch (IllegalStateException ex) {
+            // GeckoApp hasn't started, so we have no ContextGetter in LocaleManager.
+            Log.w(LOG_TAG, "Couldn't correct locale.", ex);
+        }
+
+        super.onConfigurationChanged(config);
+    }
+
     protected void initialize() {
         if (mInited)
             return;
 
         // workaround for http://code.google.com/p/android/issues/detail?id=20915
         try {
             Class.forName("android.os.AsyncTask");
         } catch (ClassNotFoundException e) {}
@@ -38,24 +64,16 @@ public class GeckoApplication extends Ap
         mLightweightTheme = new LightweightTheme(this);
 
         GeckoConnectivityReceiver.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().init(getApplicationContext());
         GeckoBatteryManager.getInstance().start();
         GeckoNetworkManager.getInstance().init(getApplicationContext());
         MemoryMonitor.getInstance().init(getApplicationContext());
 
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                mNeedsRestart = true;
-            }
-        };
-        registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
-
         mInited = true;
     }
 
     public void onActivityPause(GeckoActivityStatus activity) {
         mInBackground = true;
 
         if ((activity.isFinishing() == false) &&
             (activity.isGeckoActivityOpened() == false)) {
@@ -85,20 +103,16 @@ public class GeckoApplication extends Ap
             mPausedGecko = false;
         }
         GeckoConnectivityReceiver.getInstance().start();
         GeckoNetworkManager.getInstance().start();
 
         mInBackground = false;
     }
 
-    protected boolean needsRestart() {
-        return mNeedsRestart;
-    }
-
     @Override
     public void onCreate() {
         HardwareUtils.init(getApplicationContext());
         Clipboard.init(getApplicationContext());
         GeckoLoader.loadMozGlue();
         super.onCreate();
     }
 
--- a/mobile/android/base/GeckoView.java
+++ b/mobile/android/base/GeckoView.java
@@ -14,27 +14,29 @@ import org.mozilla.gecko.util.GeckoEvent
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.TypedArray;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class GeckoView extends LayerView
     implements GeckoEventListener, ContextGetter {
 
+    private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
     private static final String LOGTAG = "GeckoView";
 
     private ChromeDelegate mChromeDelegate;
     private ContentDelegate mContentDelegate;
 
     public GeckoView(Context context, AttributeSet attrs) {
         super(context, attrs);
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GeckoView);
@@ -300,16 +302,24 @@ public class GeckoView extends LayerView
     public static void setGeckoInterface(final BaseGeckoInterface geckoInterface) {
         GeckoAppShell.setGeckoInterface(geckoInterface);
     }
 
     public static GeckoAppShell.GeckoInterface getGeckoInterface() {
         return GeckoAppShell.getGeckoInterface();
     }
 
+    protected String getSharedPreferencesFile() {
+        return DEFAULT_SHARED_PREFERENCES_FILE;
+    }
+
+    public SharedPreferences getSharedPreferences() {
+        return getContext().getSharedPreferences(getSharedPreferencesFile(), 0);
+    }
+
     /**
     * Wrapper for a browser in the GeckoView container. Associated with a browser
     * element in the Gecko system.
     */
     public class Browser {
         private final int mId;
         private Browser(int Id) {
             mId = Id;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/LocaleManager.java
@@ -0,0 +1,216 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.util.Locale;
+
+/**
+ * This class manages persistence, application, and otherwise handling of
+ * user-specified locales.
+ *
+ * Of note:
+ * 
+ * * It's a singleton, because its scope extends to that of the application,
+ *   and definitionally all changes to the locale of the app must go through
+ *   this.
+ * * It's lazy.
+ * * It has ties into the Gecko event system, because it has to tell Gecko when
+ *   to switch locale.
+ * * It relies on using the SharedPreferences file owned by the browser (in
+ *   Fennec's case, "GeckoApp") for performance.
+ */
+public class LocaleManager {
+    private static final String LOG_TAG = "GeckoLocales";
+
+    // These are both volatile because we don't impose restrictions
+    // over which thread calls our methods.
+    private static volatile ContextGetter getter = null;
+    private static volatile Locale currentLocale = null;
+
+    private static volatile boolean inited = false;
+    private static boolean systemLocaleDidChange = false;
+    private static BroadcastReceiver receiver;
+
+    public static void setContextGetter(ContextGetter getter) {
+        Log.d(LOG_TAG, "Calling setContextGetter: " + getter);
+        LocaleManager.getter = getter;
+    }
+
+    public static void initialize() {
+        if (inited) {
+            return;
+        }
+
+        receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                systemLocaleDidChange = true;
+            }
+        };
+        getContext().registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        inited = true;
+    }
+
+    public static boolean systemLocaleDidChange() {
+        return systemLocaleDidChange;
+    }
+
+    private static Context getContext() {
+        if (getter == null) {
+            throw new IllegalStateException("No ContextGetter; cannot fetch context.");
+        }
+        return getter.getContext();
+    }
+
+    private static SharedPreferences getSharedPreferences() {
+        if (getter == null) {
+            throw new IllegalStateException("No ContextGetter; cannot fetch prefs.", new RuntimeException("No prefs."));
+        }
+        return getter.getSharedPreferences();
+    }
+
+    /**
+     * Every time the system gives us a new configuration, it
+     * carries the external locale. Fix it.
+     */
+    public static void correctLocale(Resources res, Configuration config) {
+        Locale current = getCurrentLocale();
+        if (current == null) {
+            return;
+        }
+
+        // I know it's tempting to short-circuit here if the config seems to be
+        // up-to-date, but the rest is necessary.
+
+        config.locale = current;
+
+        // The following two lines are heavily commented in case someone
+        // decides to chase down performance improvements and decides to
+        // question what's going on here.
+        // Both lines should be cheap, *but*...
+
+        // This is unnecessary for basic string choice, but it almost
+        // certainly comes into play when rendering numbers, deciding on RTL,
+        // etc. Take it out if you can prove that's not the case.
+        Locale.setDefault(current);
+
+        // This seems to be a no-op, but every piece of documentation under the
+        // sun suggests that it's necessary, and it certainly makes sense.
+        res.updateConfiguration(config, res.getDisplayMetrics());
+    }
+
+    private static Locale parseLocaleCode(final String localeCode) {
+        int index;
+        if ((index = localeCode.indexOf('-')) != -1 ||
+            (index = localeCode.indexOf('_')) != -1) {
+            final String langCode = localeCode.substring(0, index);
+            final String countryCode = localeCode.substring(index + 1);
+            return new Locale(langCode, countryCode);
+        } else {
+            return new Locale(localeCode);
+        }
+    }
+
+    public static Locale getCurrentLocale() {
+        if (currentLocale != null) {
+            return currentLocale;
+        }
+
+        final String current = getPersistedLocale();
+        if (current == null) {
+            return null;
+        }
+        return currentLocale = parseLocaleCode(current);
+    }
+
+    /**
+     * Returns the persisted locale if it differed from the current.
+     */
+    public static String updateLocale(String localeCode) {
+        // Fast path.
+        final Locale defaultLocale = Locale.getDefault();
+        if (defaultLocale.toString().equals(localeCode)) {
+            return null;
+        }
+
+        final Locale locale = parseLocaleCode(localeCode);
+
+        // Fast path.
+        if (defaultLocale.equals(locale)) {
+            return null;
+        }
+
+        Locale.setDefault(locale);
+        currentLocale = locale;
+
+        // Update resources.
+        Resources res = getContext().getResources();
+        Configuration config = res.getConfiguration();
+        config.locale = locale;
+        res.updateConfiguration(config, res.getDisplayMetrics());
+
+        // Tell Gecko.
+        GeckoEvent ev = GeckoEvent.createBroadcastEvent("Locale:Changed", locale.toString());
+        GeckoAppShell.sendEventToGecko(ev);
+
+        return locale.toString();
+    }
+
+    private static String getPrefName() {
+        return getContext().getPackageName() + ".locale";
+    }
+
+    public static String getPersistedLocale() {
+        final SharedPreferences settings = getSharedPreferences();
+
+        // N.B., it is expected that any per-profile settings will be
+        // implemented via SharedPreferences multiplexing in ContextGetter, not
+        // via profile-annotated preference names.
+        final String locale = settings.getString(getPrefName(), "");
+
+        if ("".equals(locale)) {
+            return null;
+        }
+        return locale;
+    }
+
+    private static void persistLocale(String localeCode) {
+        final SharedPreferences settings = getSharedPreferences();
+        settings.edit().putString(getPrefName(), localeCode).commit();
+    }
+
+    public static String getAndApplyPersistedLocale() {
+        final long t1 = android.os.SystemClock.uptimeMillis();
+        final String localeCode = getPersistedLocale();
+        if (localeCode == null) {
+            return null;
+        }
+
+        updateLocale(localeCode);
+        final long t2 = android.os.SystemClock.uptimeMillis();
+        Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
+        return localeCode;
+    }
+
+    /**
+     * Returns the set locale if it changed. Always persists.
+     */
+    public static String setSelectedLocale(String localeCode) {
+        final String resultant = updateLocale(localeCode);
+        persistLocale(localeCode);
+        return resultant;
+    }
+}
+
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -314,16 +314,17 @@ public class BrowserHealthRecorder imple
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this);
         this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this);
         this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this);
         this.dispatcher.unregisterEventListener(EVENT_SEARCH, this);
     }
 
     public void onAppLocaleChanged(String to) {
+        Log.d(LOG_TAG, "Setting health recorder app locale to " + to);
         this.profileCache.beginInitialization();
         this.profileCache.setAppLocale(to);
     }
 
     public void onAddonChanged(String id, JSONObject json) {
         this.profileCache.beginInitialization();
         try {
             this.profileCache.updateJSONForAddon(id, json);
@@ -344,20 +345,29 @@ public class BrowserHealthRecorder imple
     /**
      * Call this when a material change might have occurred in the running
      * environment, such that a new environment should be computed and prepared
      * for use in future events.
      *
      * Invoke this method after calls that mutate the environment.
      *
      * If this change resulted in a transition between two environments, {@link
-     * #onEnvironmentTransition(int, int)} will be invoked on the background
+     * #onEnvironmentTransition(int, int, boolean, String)} will be invoked on the background
      * thread.
      */
     public synchronized void onEnvironmentChanged() {
+        onEnvironmentChanged(true, "E");
+    }
+
+    /**
+     * If `startNewSession` is false, it means no new session should begin
+     * (e.g., because we're about to restart, and we don't want to create
+     * an orphan).
+     */
+    public synchronized void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) {
         final int previousEnv = this.env;
         this.env = -1;
         try {
             profileCache.completeInitialization();
         } catch (java.io.IOException e) {
             Log.e(LOG_TAG, "Error completing profile cache initialization.", e);
             this.state = State.INITIALIZATION_FAILED;
             return;
@@ -369,17 +379,17 @@ public class BrowserHealthRecorder imple
             updatedEnv == previousEnv) {
             Log.v(LOG_TAG, "Environment didn't change.");
             return;
         }
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
             public void run() {
                 try {
-                    onEnvironmentTransition(previousEnv, updatedEnv);
+                    onEnvironmentTransition(previousEnv, updatedEnv, startNewSession, sessionEndReason);
                 } catch (Exception e) {
                     Log.w(LOG_TAG, "Could not record environment transition.", e);
                 }
             }
         });
     }
 
     protected synchronized int ensureEnvironment() {
@@ -638,26 +648,31 @@ public class BrowserHealthRecorder imple
             }
         });
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
      */
-    protected void onEnvironmentTransition(int prev, int env) {
+    protected void onEnvironmentTransition(int prev, int env, boolean startNewSession, String sessionEndReason) {
         if (this.state != State.INITIALIZED) {
             Log.d(LOG_TAG, "Not initialized: not recording env transition (" + prev + " => " + env + ").");
             return;
         }
 
         final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
         final SharedPreferences.Editor editor = prefs.edit();
 
-        recordSessionEnd("E", editor, prev);
+        recordSessionEnd(sessionEndReason, editor, prev);
+
+        if (!startNewSession) {
+            editor.commit();
+            return;
+        }
 
         final SessionInformation newSession = SessionInformation.forRuntimeTransition();
         setCurrentSession(newSession);
         newSession.recordBegin(editor);
         editor.commit();
     }
 
     @Override
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -124,16 +124,21 @@ public class HomePager extends ViewPager
                 @Override
                 public void onPageScrollStateChanged(int state) { }
             });
         }
 
         super.addView(child, index, params);
     }
 
+    public void redisplay(FragmentManager fm) {
+        final TabsAdapter adapter = (TabsAdapter) getAdapter();
+        show(fm, adapter.getCurrentPage(), null);
+    }
+
     /**
      * Loads and initializes the pager.
      *
      * @param fm FragmentManager for the adapter
      */
     public void show(FragmentManager fm, Page page, PropertyAnimator animator) {
         mLoaded = true;
         final TabsAdapter adapter = new TabsAdapter(fm);
@@ -276,16 +281,22 @@ public class HomePager extends ViewPager
                 if (info.page == page) {
                     return i;
                 }
             }
 
             return -1;
         }
 
+        public Page getCurrentPage() {
+            int currentItem = getCurrentItem();
+            TabInfo info = mTabs.get(currentItem);
+            return info.page;
+        }
+
         @Override
         public int getCount() {
             return mTabs.size();
         }
 
         @Override
         public Fragment getItem(int position) {
             TabInfo info = mTabs.get(position);
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -90,17 +90,17 @@ public class TabMenuStrip extends Linear
                     }
 
                     mPrevProgress = position;
                 }
             });
         }
     }
 
-    // Page scroll animates the drawable and it's bounds from the previous to next child view.
+    // Page scroll animates the drawable and its bounds from the previous to next child view.
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
         if (mStrip == null) {
             return;
         }
 
         setScrollingData(position, positionOffset);
 
--- a/mobile/android/base/home/TwoLinePageRow.java
+++ b/mobile/android/base/home/TwoLinePageRow.java
@@ -213,24 +213,24 @@ public class TwoLinePageRow extends Line
                 } else {
                     setBookmarkIcon(R.drawable.ic_url_bar_star);
                 }
             } else {
                 setBookmarkIcon(NO_ICON);
             }
         }
 
+        // Use the URL instead of an empty title for consistency with the normal URL
+        // bar view - this is the equivalent of getDisplayTitle() in Tab.java
+        setTitle(TextUtils.isEmpty(title) ? url : title);
+
         // No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
         if (url.equals(mPageUrl)) {
             return;
         }
 
-        // Use the URL instead of an empty title for consistency with the normal URL
-        // bar view - this is the equivalent of getDisplayTitle() in Tab.java
-        setTitle(TextUtils.isEmpty(title) ? url : title);
-
         // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
         mFavicon.clearImage();
         mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(url, mFaviconListener);
 
         updateDisplayedUrl(url);
     }
 }
--- a/mobile/android/base/menu/GeckoMenu.java
+++ b/mobile/android/base/menu/GeckoMenu.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.menu;
 
 import org.mozilla.gecko.R;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.ActionProvider;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
@@ -109,17 +110,17 @@ public class GeckoMenu extends ListView
         // Attach an adapter.
         mAdapter = new MenuItemsAdapter();
         setAdapter(mAdapter);
         setOnItemClickListener(this);
 
         mItems = new ArrayList<GeckoMenuItem>();
         mActionItems = new HashMap<GeckoMenuItem, View>();
 
-        mActionItemBarPresenter =  (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
+        mActionItemBarPresenter = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
     }
 
     @Override
     public MenuItem add(CharSequence title) {
         GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
         addItem(menuItem);
         return menuItem;
     }
@@ -214,24 +215,36 @@ public class GeckoMenu extends ListView
         ((GeckoMenuItem) menuItem).setSubMenu(subMenu);
         return subMenu;
     }
 
     @Override
     public void clear() {
         for (GeckoMenuItem menuItem : mItems) {
             if (menuItem.hasSubMenu()) {
-                menuItem.getSubMenu().clear();
+                SubMenu sub = menuItem.getSubMenu();
+                if (sub == null) {
+                    continue;
+                }
+                try {
+                    sub.clear();
+                } catch (Exception ex) {
+                    Log.e(LOGTAG, "Couldn't clear submenu.", ex);
+                }
             }
         }
 
         mAdapter.clear();
-
         mItems.clear();
 
+        /*
+         * Reinflating the menu will re-add any action items to the toolbar, so
+         * remove the old ones. This also ensures that any text associated with
+         * these is switched to the correct locale.
+         */
         if (mActionItemBarPresenter != null) {
             for (View item : mActionItems.values()) {
                 mActionItemBarPresenter.removeActionItem(item);
             }
         }
         mActionItems.clear();
     }
 
--- a/mobile/android/base/menu/GeckoMenuItem.java
+++ b/mobile/android/base/menu/GeckoMenuItem.java
@@ -137,17 +137,21 @@ public class GeckoMenuItem implements Me
 
     @Override
     public int getOrder() {
         return mOrder;
     }
 
     @Override
     public SubMenu getSubMenu() {
-        return mSubMenu;
+        // For consistency with hasSubMenu.
+        if (mActionProvider == null) {
+            return mSubMenu;
+        }
+        return null;
     }
 
     @Override
     public CharSequence getTitle() {
         return mTitle;
     }
 
     @Override
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -227,16 +227,17 @@ gbjar.sources += [
     'home/TopSitesGridView.java',
     'home/TopSitesPage.java',
     'home/TopSitesThumbnailView.java',
     'home/TwoLinePageRow.java',
     'InputMethods.java',
     'JavaAddonManager.java',
     'LightweightTheme.java',
     'LightweightThemeDrawable.java',
+    'LocaleManager.java',
     'MemoryMonitor.java',
     'menu/GeckoMenu.java',
     'menu/GeckoMenuInflater.java',
     'menu/GeckoMenuItem.java',
     'menu/GeckoSubMenu.java',
     'menu/MenuItemActionBar.java',
     'menu/MenuItemActionView.java',
     'menu/MenuItemDefault.java',
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -99,17 +99,16 @@
                                                 android:layout_width="fill_parent"
                                                 android:layout_height="fill_parent"
                                                 android:layout_weight="1.0"
                                                 android:singleLine="true"
                                                 android:paddingRight="8dp"
                                                 android:textColor="@color/url_bar_title"
                                                 android:textColorHint="@color/url_bar_title_hint"
                                                 android:gravity="center_vertical|left"
-                                                android:hint="@string/url_bar_default_text"
                                                 android:layout_gravity="center_vertical"
                                                 gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
                                                     android:layout_width="wrap_content"
                                                     android:layout_height="match_parent"
                                                     android:layout_marginRight="@dimen/browser_toolbar_button_padding"
                                                     android:visibility="gone"
--- a/mobile/android/base/tests/testAboutPage.java
+++ b/mobile/android/base/tests/testAboutPage.java
@@ -7,42 +7,47 @@ import org.mozilla.gecko.*;
  *  - check that about: loads from Settings/About...
  */
 public class testAboutPage extends PixelTest {
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
 
+    private void ensureTitleMatches(final String regex) {
+        Element urlBarTitle = mDriver.findElement(getActivity(), URL_BAR_TITLE_ID);
+        mAsserter.isnot(urlBarTitle, null, "Got the URL bar title");
+        assertMatches(urlBarTitle.getText(), regex, "page title match");
+    }
+
     public void testAboutPage() {
         blockForGeckoReady();
 
-        // Load the about: page and verify its title
+        // Load the about: page and verify its title.
         String url = "about:";
         loadAndPaint(url);
 
-        Element urlBarTitle = mDriver.findElement(getActivity(), URL_BAR_TITLE_ID);
-        mAsserter.isnot(urlBarTitle, null, "Got the URL bar title");
-        assertMatches(urlBarTitle.getText(), "About (Fennec|Nightly|Aurora|Firefox|Firefox Beta)", "page title match");
+        ensureTitleMatches("About (Fennec|Nightly|Aurora|Firefox|Firefox Beta)");
 
-        // Open a new page to remove the about: page from the current tab
+        // Open a new page to remove the about: page from the current tab.
         url = getAbsoluteUrl("/robocop/robocop_blank_01.html");
         inputAndLoadUrl(url);
 
-        // Set up listeners to catch the page load we're about to do
+        // At this point the page title should have been set.
+        ensureTitleMatches("Browser Blank Page 01");
+
+        // Set up listeners to catch the page load we're about to do.
         Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
         Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
 
         selectSettingsItem("Mozilla", "About (Fennec|Nightly|Aurora|Firefox|Firefox Beta)");
 
         // Wait for the new tab and page to load
         tabEventExpecter.blockForEvent();
         contentEventExpecter.blockForEvent();
 
         tabEventExpecter.unregisterListener();
         contentEventExpecter.unregisterListener();
 
-        // Grab the title to make sure the about: page was loaded
-        urlBarTitle = mDriver.findElement(getActivity(), URL_BAR_TITLE_ID);
-        mAsserter.isnot(urlBarTitle, null, "Got the URL bar title");
-        assertMatches(urlBarTitle.getText(), "About (Fennec|Nightly|Aurora|Firefox|Firefox Beta)", "page title match");
+        // Grab the title to make sure the about: page was loaded.
+        ensureTitleMatches("About (Fennec|Nightly|Aurora|Firefox|Firefox Beta)");
     }
 }
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -348,28 +348,28 @@ var SelectionHandler = {
       icon: "drawable://cut",
       action: function(aElement) {
         let start = aElement.selectionStart;
         let end   = aElement.selectionEnd;
 
         SelectionHandler.copySelection();
         aElement.value = aElement.value.substring(0, start) + aElement.value.substring(end)
 
-        SelectionHandler._updateMenu();
+        // copySelection closes the selection. Show a caret where we just cut the text.
+        SelectionHandler.attachCaret(aElement);
       },
       selector: ClipboardHelper.cutContext,
     },
 
     COPY: {
       label: Strings.browser.GetStringFromName("contextmenu.copy"),
       id: "copy_action",
       icon: "drawable://copy",
       action: function() {
         SelectionHandler.copySelection();
-        SelectionHandler._updateMenu();
       },
       selector: ClipboardHelper.getCopyContext(false)
     },
 
     PASTE: {
       label: Strings.browser.GetStringFromName("contextmenu.paste"),
       id: "paste_action",
       icon: "drawable://paste",
@@ -382,17 +382,16 @@ var SelectionHandler = {
     },
 
     SHARE: {
       label: Strings.browser.GetStringFromName("contextmenu.share"),
       id: "share_action",
       icon: "drawable://ic_menu_share",
       action: function() {
         SelectionHandler.shareSelection();
-        SelectionHandler._closeSelection();
       },
       showAsAction: function(aElement) {
         return !(aElement instanceof HTMLInputElement && aElement.mozIsTextField(false))
       },
       selector: ClipboardHelper.shareContext,
     },
 
     SEARCH: {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -269,16 +269,17 @@ var BrowserApp = {
     dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
 
     this.deck = document.getElementById("browsers");
     BrowserEventHandler.init();
     ViewportHandler.init();
 
     Services.androidBridge.browserApp = this;
 
+    Services.obs.addObserver(this, "Locale:Changed", false);
     Services.obs.addObserver(this, "Tab:Load", false);
     Services.obs.addObserver(this, "Tab:Selected", false);
     Services.obs.addObserver(this, "Tab:Closed", false);
     Services.obs.addObserver(this, "Session:Back", false);
     Services.obs.addObserver(this, "Session:ShowHistory", false);
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
     Services.obs.addObserver(this, "Session:Stop", false);
@@ -405,16 +406,24 @@ var BrowserApp = {
 #expand    let ourmstone = "__MOZ_APP_VERSION__";
     if (ourmstone != savedmstone) {
       Services.prefs.setCharPref("browser.startup.homepage_override.mstone", ourmstone);
       return savedmstone ? "upgrade" : "new";
     }
     return "";
   },
 
+  /**
+   * Pass this a locale string, such as "fr" or "es_ES".
+   */
+  setLocale: function (locale) {
+    console.log("browser.js: requesting locale set: " + locale);
+    sendMessageToJava({ type: "Locale:Set", locale: locale });
+  },
+
   initContextMenu: function ba_initContextMenu() {
     // TODO: These should eventually move into more appropriate classes
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"),
       NativeWindow.contextmenus.linkOpenableNonPrivateContext,
       function(aTarget) {
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
         BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id });
 
@@ -1491,16 +1500,23 @@ var BrowserApp = {
         gViewportMargins = JSON.parse(aData);
         this.selectedTab.updateViewportSize(gScreenWidth);
         break;
 
       case "nsPref:changed":
         this.notifyPrefObservers(aData);
         break;
 
+      case "Locale:Changed":
+        // TODO: do we need to be more nuanced here -- e.g., checking for the
+        // OS locale -- or should it always be false on Fennec?
+        Services.prefs.setBoolPref("intl.locale.matchOS", false);
+        Services.prefs.setCharPref("general.useragent.locale", aData);
+        break;
+
       default:
         dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
         break;
 
     }
   },
 
   get defaultBrowserWidth() {
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -72,17 +72,16 @@ jmethodID GeckoAppShell::jPerformHapticF
 jmethodID GeckoAppShell::jPumpMessageLoop = 0;
 jmethodID GeckoAppShell::jRegisterSurfaceTextureFrameListener = 0;
 jmethodID GeckoAppShell::jRemovePluginView = 0;
 jmethodID GeckoAppShell::jScanMedia = 0;
 jmethodID GeckoAppShell::jScheduleRestart = 0;
 jmethodID GeckoAppShell::jSendMessageWrapper = 0;
 jmethodID GeckoAppShell::jSetFullScreen = 0;
 jmethodID GeckoAppShell::jSetKeepScreenOn = 0;
-jmethodID GeckoAppShell::jSetSelectedLocale = 0;
 jmethodID GeckoAppShell::jSetURITitle = 0;
 jmethodID GeckoAppShell::jShowAlertNotificationWrapper = 0;
 jmethodID GeckoAppShell::jShowFilePickerAsyncWrapper = 0;
 jmethodID GeckoAppShell::jShowFilePickerForExtensionsWrapper = 0;
 jmethodID GeckoAppShell::jShowFilePickerForMimeTypeWrapper = 0;
 jmethodID GeckoAppShell::jShowInputMethodPicker = 0;
 jmethodID GeckoAppShell::jUnlockProfile = 0;
 jmethodID GeckoAppShell::jUnlockScreenOrientation = 0;
@@ -153,17 +152,16 @@ void GeckoAppShell::InitStubs(JNIEnv *jE
     jPumpMessageLoop = getStaticMethod("pumpMessageLoop", "()Z");
     jRegisterSurfaceTextureFrameListener = getStaticMethod("registerSurfaceTextureFrameListener", "(Ljava/lang/Object;I)V");
     jRemovePluginView = getStaticMethod("removePluginView", "(Landroid/view/View;Z)V");
     jScanMedia = getStaticMethod("scanMedia", "(Ljava/lang/String;Ljava/lang/String;)V");
     jScheduleRestart = getStaticMethod("scheduleRestart", "()V");
     jSendMessageWrapper = getStaticMethod("sendMessage", "(Ljava/lang/String;Ljava/lang/String;I)V");
     jSetFullScreen = getStaticMethod("setFullScreen", "(Z)V");
     jSetKeepScreenOn = getStaticMethod("setKeepScreenOn", "(Z)V");
-    jSetSelectedLocale = getStaticMethod("setSelectedLocale", "(Ljava/lang/String;)V");
     jSetURITitle = getStaticMethod("setUriTitle", "(Ljava/lang/String;Ljava/lang/String;)V");
     jShowAlertNotificationWrapper = getStaticMethod("showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jShowFilePickerAsyncWrapper = getStaticMethod("showFilePickerAsync", "(Ljava/lang/String;J)V");
     jShowFilePickerForExtensionsWrapper = getStaticMethod("showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jShowFilePickerForMimeTypeWrapper = getStaticMethod("showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
     jShowInputMethodPicker = getStaticMethod("showInputMethodPicker", "()V");
     jUnlockProfile = getStaticMethod("unlockProfile", "()Z");
     jUnlockScreenOrientation = getStaticMethod("unlockScreenOrientation", "()V");
@@ -2050,45 +2048,16 @@ void GeckoAppShell::SetKeepScreenOn(bool
         env->ExceptionClear();
         env->PopLocalFrame(NULL);
         return;
     }
 
     env->PopLocalFrame(NULL);
 }
 
-void GeckoAppShell::SetSelectedLocale(const nsAString& a0) {
-    JNIEnv *env = AndroidBridge::GetJNIEnv();
-    if (!env) {
-        ALOG_BRIDGE("Aborted: No env - %s", __PRETTY_FUNCTION__);
-        return;
-    }
-
-    if (env->PushLocalFrame(1) != 0) {
-        ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
-        env->ExceptionDescribe();
-        env->ExceptionClear();
-        return;
-    }
-
-    jstring j0 = AndroidBridge::NewJavaString(env, a0);
-
-    env->CallStaticVoidMethod(mGeckoAppShellClass, jSetSelectedLocale, j0);
-
-    if (env->ExceptionCheck()) {
-        ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
-        env->ExceptionDescribe();
-        env->ExceptionClear();
-        env->PopLocalFrame(NULL);
-        return;
-    }
-
-    env->PopLocalFrame(NULL);
-}
-
 void GeckoAppShell::SetURITitle(const nsAString& a0, const nsAString& a1) {
     JNIEnv *env = AndroidBridge::GetJNIEnv();
     if (!env) {
         ALOG_BRIDGE("Aborted: No env - %s", __PRETTY_FUNCTION__);
         return;
     }
 
     if (env->PushLocalFrame(2) != 0) {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -79,17 +79,16 @@ public:
     static bool PumpMessageLoop();
     static void RegisterSurfaceTextureFrameListener(jobject a0, int32_t a1);
     static void RemovePluginView(jobject a0, bool a1);
     static void ScanMedia(const nsAString& a0, const nsAString& a1);
     static void ScheduleRestart();
     static void SendMessageWrapper(const nsAString& a0, const nsAString& a1, int32_t a2);
     static void SetFullScreen(bool a0);
     static void SetKeepScreenOn(bool a0);
-    static void SetSelectedLocale(const nsAString& a0);
     static void SetURITitle(const nsAString& a0, const nsAString& a1);
     static void ShowAlertNotificationWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3, const nsAString& a4);
     static void ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1);
     static jstring ShowFilePickerForExtensionsWrapper(const nsAString& a0);
     static jstring ShowFilePickerForMimeTypeWrapper(const nsAString& a0);
     static void ShowInputMethodPicker();
     static bool UnlockProfile();
     static void UnlockScreenOrientation();
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -158,105 +158,58 @@ nsAppShell::~nsAppShell()
 
 void
 nsAppShell::NotifyNativeEvent()
 {
     MutexAutoLock lock(mCondLock);
     mQueueCond.Notify();
 }
 
-#define PREFNAME_MATCH_OS  "intl.locale.matchOS"
-#define PREFNAME_UA_LOCALE "general.useragent.locale"
 #define PREFNAME_COALESCE_TOUCHES "dom.event.touch.coalescing.enabled"
 static const char* kObservedPrefs[] = {
-  PREFNAME_MATCH_OS,
-  PREFNAME_UA_LOCALE,
   PREFNAME_COALESCE_TOUCHES,
   nullptr
 };
 
 nsresult
 nsAppShell::Init()
 {
 #ifdef PR_LOGGING
     if (!gWidgetLog)
         gWidgetLog = PR_NewLogModule("Widget");
 #endif
 
     nsresult rv = nsBaseAppShell::Init();
-    AndroidBridge* bridge = AndroidBridge::Bridge();
-
     nsCOMPtr<nsIObserverService> obsServ =
         mozilla::services::GetObserverService();
     if (obsServ) {
         obsServ->AddObserver(this, "xpcom-shutdown", false);
     }
 
     if (sPowerManagerService)
         sPowerManagerService->AddWakeLockListener(sWakeLockListener);
 
-    if (!bridge)
-        return rv;
-
     Preferences::AddStrongObservers(this, kObservedPrefs);
-
-    bool match;
-    rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (match) {
-        GeckoAppShell::SetSelectedLocale(EmptyString());
-        return NS_OK;
-    }
-
-    nsAutoString locale;
-    rv = Preferences::GetLocalizedString(PREFNAME_UA_LOCALE, &locale);
-    if (NS_FAILED(rv)) {
-        rv = Preferences::GetString(PREFNAME_UA_LOCALE, &locale);
-    }
-
-    GeckoAppShell::SetSelectedLocale(locale);
     mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
     return rv;
 }
 
 NS_IMETHODIMP
 nsAppShell::Observe(nsISupports* aSubject,
                     const char* aTopic,
                     const PRUnichar* aData)
 {
     if (!strcmp(aTopic, "xpcom-shutdown")) {
         // We need to ensure no observers stick around after XPCOM shuts down
         // or we'll see crashes, as the app shell outlives XPConnect.
         mObserversHash.Clear();
         return nsBaseAppShell::Observe(aSubject, aTopic, aData);
-    } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) && aData && (
-                   nsDependentString(aData).Equals(
-                       NS_LITERAL_STRING(PREFNAME_UA_LOCALE)) ||
-                   nsDependentString(aData).Equals(
-                       NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES)) ||
-                   nsDependentString(aData).Equals(
-                       NS_LITERAL_STRING(PREFNAME_MATCH_OS)))) {
-        bool match;
-        nsresult rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match);
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        if (match) {
-            GeckoAppShell::SetSelectedLocale(EmptyString());
-            return NS_OK;
-        }
-
-        nsAutoString locale;
-        if (NS_FAILED(Preferences::GetLocalizedString(PREFNAME_UA_LOCALE,
-                                                      &locale))) {
-            locale = Preferences::GetString(PREFNAME_UA_LOCALE);
-        }
-
-        GeckoAppShell::SetSelectedLocale(locale);
-
+    } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) &&
+               aData &&
+               nsDependentString(aData).Equals(NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES))) {
         mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
         return NS_OK;
     }
     return NS_OK;
 }
 
 void
 nsAppShell::ScheduleNativeEventCallback()