Merge m-c to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 27 Nov 2013 12:41:30 +0100
changeset 157814 61bf4acd28efa9aa7d71e3735ae1a77ea84faba1
parent 157813 0e9832497387d0fbb9dbbcd89ac58fb11bb0ed40 (current diff)
parent 157732 6ecf0c4dfcbe6b04640713afa29eba044b6c0475 (diff)
child 157815 2f8abdba302fa6fa69f43e48b262d77e25b3ebc7
push id25726
push usercbook@mozilla.com
push dateThu, 28 Nov 2013 10:47:25 +0000
treeherdermozilla-central@cdca43b7657d [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 m-c to mozilla-inbound
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "70f416f866858cb2068bffa31118fc4b15b482c9", 
+    "revision": "9ba62e3061abd5521ffbbee5386a0654f972f73b", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/base/content/browser-charsetmenu.inc
+++ b/browser/base/content/browser-charsetmenu.inc
@@ -1,41 +1,62 @@
 # 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/.
 
 #filter substitution
 
 #expand <menu id="__ID_PREFIX__charsetMenu"
     label="&charsetMenu.label;"
+#ifndef OMIT_ACCESSKEYS
     accesskey="&charsetMenu.accesskey;"
+#endif
     oncommand="MultiplexHandler(event)"
+#ifdef OMIT_ACCESSKEYS
     onpopupshowing="CharsetMenu.build(event);"
+#else
+    onpopupshowing="CharsetMenu.build(event, true);"
+#endif
     onpopupshown="UpdateMenus(event);">
   <menupopup>
     <menu label="&charsetMenuAutodet.label;"
-          accesskey="&charsetMenuAutodet.accesskey;">
+#ifndef OMIT_ACCESSKEYS
+          accesskey="&charsetMenuAutodet.accesskey;"
+#endif
+        >
       <menupopup>
         <menuitem type="radio"
                   name="detectorGroup"
-                  id="chardet.off"
+#expand           id="__ID_PREFIX__chardet.off"
                   label="&charsetMenuAutodet.off.label;"
-                  accesskey="&charsetMenuAutodet.off.accesskey;"/>
+#ifndef OMIT_ACCESSKEYS
+                  accesskey="&charsetMenuAutodet.off.accesskey;"
+#endif
+                  />
         <menuitem type="radio"
                   name="detectorGroup"
-                  id="chardet.ja_parallel_state_machine"
+#expand           id="__ID_PREFIX__chardet.ja_parallel_state_machine"
                   label="&charsetMenuAutodet.ja.label;"
-                  accesskey="&charsetMenuAutodet.ja.accesskey;"/>
+#ifndef OMIT_ACCESSKEYS
+                  accesskey="&charsetMenuAutodet.ja.accesskey;"
+#endif
+                  />
         <menuitem type="radio"
                   name="detectorGroup"
-                  id="chardet.ruprob"
+#expand           id="__ID_PREFIX__chardet.ruprob"
                   label="&charsetMenuAutodet.ru.label;"
-                  accesskey="&charsetMenuAutodet.ru.accesskey;"/>
+#ifndef OMIT_ACCESSKEYS
+                  accesskey="&charsetMenuAutodet.ru.accesskey;"
+#endif
+                  />
         <menuitem type="radio"
                   name="detectorGroup"
-                  id="chardet.ukprob"
+#expand           id="__ID_PREFIX__chardet.ukprob"
                   label="&charsetMenuAutodet.uk.label;"
-                  accesskey="&charsetMenuAutodet.uk.accesskey;"/>
+#ifndef OMIT_ACCESSKEYS
+                  accesskey="&charsetMenuAutodet.uk.accesskey;"
+#endif
+                  />
       </menupopup>
     </menu>
     <menuseparator/>
   </menupopup>
 </menu>
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -373,16 +373,17 @@
 
 <!-- This is a peculiar binding. It is here to deal with overlayed/inserted add-on content,
       and immediately direct such content elsewhere. -->
   <binding id="addonbar-delegating">
     <implementation>
       <constructor><![CDATA[
           // Reading these immediately so nobody messes with them anymore:
           this._delegatingToolbar = this.getAttribute("toolbar-delegate");
+          this._wasCollapsed = this.getAttribute("collapsed");
           // Leaving those in here to unbreak some code:
           if (document.readyState == "complete") {
             this._init();
           } else {
             // Need to wait until XUL overlays are loaded. See bug 554279.
             let self = this;
             document.addEventListener("readystatechange", function onReadyStateChange() {
               if (document.readyState != "complete")
@@ -404,18 +405,27 @@
                 // Hold on to the palette but remove it from the document.
                 toolbox.palette = node;
                 toolbox.removeChild(node);
               }
             }
           }
 
           // pass the current set of children for comparison with placements:
-          let children = [node.id for (node of this.childNodes)
-                          if (node.getAttribute("skipintoolbarset") != "true" && node.id)];
+          let children = [];
+          for (node of this.childNodes) {
+            if (node.getAttribute("skipintoolbarset") != "true" && node.id) {
+              // Force everything to be removable so that buildArea can chuck stuff
+              // out if the user has customized things / we've been here before:
+              if (!this._whiteListed.has(node.id)) {
+                node.setAttribute("removable", "true");
+              }
+              children.push(node);
+            }
+          }
           CustomizableUI.registerToolbarNode(this, children);
           this.evictNodes();
           // We can't easily use |this| or strong bindings for the observer fn here
           // because that creates leaky circular references when the node goes away,
           // and XBL destructors are unreliable.
           let mutationObserver = new MutationObserver(function(mutations) {
             if (!mutations.length) {
               return;
@@ -450,30 +460,38 @@
         <parameter name="aNode"/>
         <body>
         <![CDATA[
           if (this._whiteListed.has(aNode.id) || CustomizableUI.isSpecialWidget(aNode.id)) {
             return;
           }
           const kItemMaxWidth = 100;
           let oldParent = aNode.parentNode;
-
-          try {
-            aNode.setAttribute("removable", "true");
+          aNode.setAttribute("removable", "true");
 
-            let nodeWidth = aNode.getBoundingClientRect().width;
-            if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
-              throw new Error(aNode.id + " is too big (" + nodeWidth +
-                              "px wide), moving to the palette");
+          let movedOut = false;
+          if (!this._wasCollapsed) {
+            try {
+              let nodeWidth = aNode.getBoundingClientRect().width;
+              if (nodeWidth == 0 || nodeWidth > kItemMaxWidth) {
+                throw new Error(aNode.id + " is too big (" + nodeWidth +
+                                "px wide), moving to the palette");
+              }
+              CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
+              movedOut = true;
+            } catch (ex) {
+              // This will throw if the node is too big, or can't be moved there for
+              // some reason. Report this:
+              Cu.reportError(ex);
             }
-            CustomizableUI.addWidgetToArea(aNode.id, this._delegatingToolbar);
-          } catch (ex) {
-            Cu.reportError(ex);
-            // This will throw if the node is too big, or can't be moved there for
-            // some reason. Try to remove it anyway:
+          }
+
+          /* We won't have moved the widget if either the add-on bar was collapsed,
+           * or if it was too wide to be inserted into the navbar. */
+          if (!movedOut) {
             try {
               CustomizableUI.removeWidgetFromArea(aNode.id);
             } catch (ex) {
               Cu.reportError(ex);
               aNode.remove();
             }
           }
 
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -233,19 +233,29 @@ Editor.prototype = {
       cm = win.CodeMirror(win.document.body, this.config);
       cm.getWrapperElement().addEventListener("contextmenu", (ev) => {
         ev.preventDefault();
         this.showContextMenu(el.ownerDocument, ev.screenX, ev.screenY);
       }, false);
 
       cm.on("focus", () => this.emit("focus"));
       cm.on("change", () => this.emit("change"));
-      cm.on("gutterClick", (cm, line) => this.emit("gutterClick", line));
       cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
 
+      cm.on("gutterClick", (cm, line, gutter, ev) => {
+        let head = { line: line, ch: 0 };
+        let tail = { line: line, ch: this.getText(line).length };
+
+        // Shift-click on a gutter selects the whole line.
+        if (ev.shiftKey)
+          return void cm.setSelection(head, tail);
+
+        this.emit("gutterClick", line);
+      });
+
       win.CodeMirror.defineExtension("l10n", (name) => {
         return L10N.GetStringFromName(name);
       });
 
       cm.getInputField().controllers.insertControllerAt(0, controller(this));
 
       this.container = env;
       editors.set(this, cm);
--- a/browser/devtools/sourceeditor/test/browser_editor_cursor.js
+++ b/browser/devtools/sourceeditor/test/browser_editor_cursor.js
@@ -26,11 +26,18 @@ function test() {
     is(ed.getSelection(), "Hello", "setSelection");
 
     ed.extendSelection({ start: 0, length: 5 });
     is(ed.getSelection(), ".\nHow", "extendSelection");
 
     ed.dropSelection();
     is(ed.getSelection(), "", "dropSelection");
 
+    // Check that shift-click on a gutter selects the whole line (bug 919707)
+    let iframe = win.document.querySelector("iframe");
+    let gutter = iframe.contentWindow.document.querySelector(".CodeMirror-gutters");
+
+    EventUtils.sendMouseEvent({ type: "mousedown", shiftKey: true }, gutter, iframe.contentWindow);
+    is(ed.getSelection(), "Hello.", "shift-click");
+
     teardown(ed, win);
   });
 }
\ No newline at end of file
--- a/browser/modules/CharsetMenu.jsm
+++ b/browser/modules/CharsetMenu.jsm
@@ -77,17 +77,17 @@ const kEncodings = new Set([
 
 // Always at the start of the menu, in this order, followed by a separator.
 const kPinned = [
   "UTF-8",
   "windows-1252"
 ];
 
 this.CharsetMenu = Object.freeze({
-  build: function BuildCharsetMenu(event) {
+  build: function BuildCharsetMenu(event, showAccessKeys) {
     let parent = event.target;
     if (parent.lastChild.localName != "menuseparator") {
       // Detector menu or charset menu already built
       return;
     }
     let doc = parent.ownerDocument;
 
     function createItem(encoding) {
@@ -95,21 +95,23 @@ this.CharsetMenu = Object.freeze({
       menuItem.setAttribute("type", "radio");
       menuItem.setAttribute("name", "charsetGroup");
       try {
         menuItem.setAttribute("label", gBundle.GetStringFromName(encoding));
       } catch (e) {
         // Localization error but put *something* in the menu to recover.
         menuItem.setAttribute("label", encoding);
       }
-      try {
-        menuItem.setAttribute("accesskey",
-                              gBundle.GetStringFromName(encoding + ".key"));
-      } catch (e) {
-        // Some items intentionally don't have an accesskey
+      if (showAccessKeys) {
+        try {
+          menuItem.setAttribute("accesskey",
+                                gBundle.GetStringFromName(encoding + ".key"));
+        } catch (e) {
+          // Some items intentionally don't have an accesskey
+        }
       }
       menuItem.setAttribute("id", "charset." + encoding);
       return menuItem;
     }
 
     // Clone the set in order to be able to remove the pinned encodings from
     // the cloned set.
     let encodings = new Set(kEncodings);
--- a/mobile/android/base/AboutPages.java
+++ b/mobile/android/base/AboutPages.java
@@ -34,10 +34,43 @@ public class AboutPages {
     }
 
     public static final boolean isAboutReader(final String url) {
         if (url == null) {
             return false;
         }
         return url.startsWith(READER);
     }
+
+    private static final String[] DEFAULT_ICON_PAGES = new String[] {
+        HOME,
+
+        ADDONS,
+        CONFIG,
+        DOWNLOADS,
+        FIREFOX,
+        HEALTHREPORT,
+        UPDATER
+    };
+
+    /**
+     * Callers must not modify the returned array.
+     */
+    public static String[] getDefaultIconPages() {
+        return DEFAULT_ICON_PAGES;
+    }
+
+    public static boolean isDefaultIconPage(final String url) {
+        if (url == null ||
+            !url.startsWith("about:")) {
+            return false;
+        }
+
+        // TODO: it'd be quicker to not compare the "about:" part every time.
+        for (int i = 0; i < DEFAULT_ICON_PAGES.length; ++i) {
+            if (DEFAULT_ICON_PAGES[i].equals(url)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
 
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -192,20 +192,34 @@ abstract public class BrowserApp extends
         if (mSiteIdentityPopup == null)
             mSiteIdentityPopup = new SiteIdentityPopup(this);
 
         return mSiteIdentityPopup;
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
+        if (tab == null) {
+            // Only RESTORED is allowed a null tab: it's the only event that
+            // isn't tied to a specific tab.
+            if (msg != Tabs.TabEvents.RESTORED) {
+                throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
+            }
+            return;
+        }
+
+        Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
         switch(msg) {
+            // We don't get a LOCATION_CHANGE event for the first about:home
+            // load, because the previous and current URIs are the
+            // same. That means it's OK to trigger a new favicon load
+            // at this point.
             case LOCATION_CHANGE:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
-                    maybeCancelFaviconLoad(tab);
+                    loadFavicon(tab);
                 }
                 // fall through
             case SELECTED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     updateHomePagerForTab(tab);
 
                     if (mSiteIdentityPopup != null)
                         mSiteIdentityPopup.dismiss();
@@ -236,19 +250,16 @@ abstract public class BrowserApp extends
                 break;
             case LOAD_ERROR:
             case STOP:
             case MENU_UPDATED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
                 }
                 break;
-            case PAGE_SHOW:
-                loadFavicon(tab);
-                break;
             case LINK_FAVICON:
                 // If tab is not loading and the favicon is updated, we
                 // want to load the image straight away. If tab is still
                 // loading, we only load the favicon once the page's content
                 // is fully loaded.
                 if (tab.getState() != Tab.STATE_LOADING) {
                     loadFavicon(tab);
                 }
@@ -1224,17 +1235,17 @@ abstract public class BrowserApp extends
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     @Override
     public void addTab() {
-        Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
+        super.loadHomePage(Tabs.LOADURL_NEW_TAB);
     }
 
     @Override
     public void addPrivateTab() {
         Tabs.getInstance().loadUrl(AboutPages.PRIVATEBROWSING, Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE);
     }
 
     @Override
@@ -1396,17 +1407,17 @@ abstract public class BrowserApp extends
         mBrowserToolbar.cancelEdit();
     }
 
     private boolean isHomePagerVisible() {
         return (mHomePager != null && mHomePager.isVisible());
     }
 
     private void openReadingList() {
-        Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_READING_LIST);
+        super.loadHomePage(Tabs.LOADURL_READING_LIST);
     }
 
     /* Favicon stuff. */
     private static OnFaviconLoadedListener sFaviconLoadedListener = new OnFaviconLoadedListener() {
         @Override
         public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
             // If we failed to load a favicon, we use the default favicon instead.
             Tabs.getInstance()
@@ -1423,20 +1434,23 @@ abstract public class BrowserApp extends
         int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
         int id = Favicons.getFaviconForSize(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags, sFaviconLoadedListener);
         tab.setFaviconLoadId(id);
     }
 
     private void maybeCancelFaviconLoad(Tab tab) {
         int faviconLoadId = tab.getFaviconLoadId();
 
-        // Cancel pending favicon load task
+        if (Favicons.NOT_LOADING == faviconLoadId) {
+            return;
+        }
+
+        // Cancel load task and reset favicon load state if it wasn't already
+        // in NOT_LOADING state.
         Favicons.cancelFaviconLoad(faviconLoadId);
-
-        // Reset favicon load state
         tab.setFaviconLoadId(Favicons.NOT_LOADING);
     }
 
     /**
      * Enters editing mode with the current tab's URL. There might be no
      * tabs loaded by the time the user enters editing mode e.g. just after
      * the app starts. In this case, we simply fallback to an empty URL.
      */
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1349,26 +1349,34 @@ abstract public class GeckoApp
      * (about:home) be loaded.
      *
      * @param url External URL to load, or null to load the default URL
      */
     protected void loadStartupTab(String url) {
         if (url == null) {
             if (!mShouldRestore) {
                 // Show about:home if we aren't restoring previous session and
-                // there's no external URL
-                Tab tab = Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
+                // there's no external URL.
+                loadHomePage(Tabs.LOADURL_NEW_TAB);
             }
         } else {
             // If given an external URL, load it
             int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
             Tabs.getInstance().loadUrl(url, flags);
         }
     }
 
+    protected Tab loadHomePage() {
+        return loadHomePage(Tabs.LOADURL_NONE);
+    }
+
+    protected Tab loadHomePage(int flags) {
+        return Tabs.getInstance().loadUrl(AboutPages.HOME, flags);
+    }
+
     private void initialize() {
         mInitialized = true;
 
         if (Build.VERSION.SDK_INT >= 11) {
             // Create the panel and inflate the custom menu.
             onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
         }
         invalidateOptionsMenu();
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -622,29 +622,41 @@ public class Tab {
                 mHistoryIndex = -1;
         }
     }
 
     void handleLocationChange(JSONObject message) throws JSONException {
         final String uri = message.getString("uri");
         final String oldUrl = getURL();
         mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri);
+
+        if (TextUtils.equals(oldUrl, uri)) {
+            Log.d(LOGTAG, "Ignoring location change event: URIs are the same.");
+            return;
+        }
+
         updateURL(uri);
         updateUserSearch(message.getString("userSearch"));
 
         mBaseDomain = message.optString("baseDomain");
         if (message.getBoolean("sameDocument")) {
             // We can get a location change event for the same document with an anchor tag
             // Notify listeners so that buttons like back or forward will update themselves
             Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
             return;
         }
 
         setContentType(message.getString("contentType"));
+
+        // We can unconditionally clear the favicon here: we already
+        // short-circuited for both cases in which this was a (pseudo-)
+        // spurious location change, so we're definitely loading a new page.
+        // The same applies to all of the other fields we're wiping out.
         clearFavicon();
+
         setHasFeeds(false);
         updateTitle(null);
         updateIdentityData(null);
         setReaderEnabled(false);
         setZoomConstraints(new ZoomConstraints(true));
         setHasTouchListeners(false);
         setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
         setErrorType(ErrorType.NONE);
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -377,16 +377,17 @@ public class Tabs implements GeckoEventL
     public static Tabs getInstance() {
        return Tabs.TabsInstanceHolder.INSTANCE;
     }
 
     // GeckoEventListener implementation
 
     @Override
     public void handleMessage(String event, JSONObject message) {
+        Log.d(LOGTAG, "handleMessage: " + event);
         try {
             if (event.equals("Session:RestoreEnd")) {
                 notifyListeners(null, TabEvents.RESTORED);
                 return;
             }
 
             // All other events handled below should contain a tabID property
             int id = message.getInt("tabID");
@@ -557,16 +558,21 @@ public class Tabs implements GeckoEventL
     }
 
     public void notifyListeners(Tab tab, TabEvents msg) {
         notifyListeners(tab, msg, "");
     }
 
     // Throws if not initialized.
     public void notifyListeners(final Tab tab, final TabEvents msg, final Object data) {
+        if (tab == null &&
+            msg != TabEvents.RESTORED) {
+            throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
+        }
+
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 onTabChanged(tab, msg, data);
 
                 synchronized (mTabsChangedListeners) {
                     if (mTabsChangedListeners.isEmpty()) {
                         return;
@@ -655,18 +661,18 @@ public class Tabs implements GeckoEventL
         return getTab(tabId);
     }
 
     /**
      * Loads a tab with the given URL in the currently selected tab.
      *
      * @param url URL of page to load, or search term used if searchEngine is given
      */
-    public void loadUrl(String url) {
-        loadUrl(url, LOADURL_NONE);
+    public Tab loadUrl(String url) {
+        return loadUrl(url, LOADURL_NONE);
     }
 
     /**
      * Loads a tab with the given URL.
      *
      * @param url   URL of page to load, or search term used if searchEngine is given
      * @param flags flags used to load tab
      *
@@ -726,24 +732,45 @@ public class Tabs implements GeckoEventL
                 added.setDesktopMode(desktopMode);
             }
         } catch (Exception e) {
             Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
         }
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
 
-        if ((added != null) && !delayLoad && !background) {
+        if (added == null) {
+            return null;
+        }
+
+        if (!delayLoad && !background) {
             selectTab(added.getId());
         }
 
+        // TODO: surely we could just fetch *any* cached icon?
+        if (AboutPages.isDefaultIconPage(url)) {
+            Log.d(LOGTAG, "Setting about: tab favicon inline.");
+            added.updateFavicon(getAboutPageFavicon(url));
+        }
+
         return added;
     }
 
     /**
+     * These favicons are only used for the URL bar, so
+     * we fetch with that size.
+     *
+     * This method completes on the calling thread.
+     */
+    private Bitmap getAboutPageFavicon(final String url) {
+        int faviconSize = Math.round(mAppContext.getResources().getDimension(R.dimen.browser_toolbar_favicon_size));
+        return Favicons.getCachedFaviconForSize(url, faviconSize);
+    }
+
+    /**
      * Open the url as a new tab, and mark the selected tab as its "parent".
      *
      * If the url is already open in a tab, the existing tab is selected.
      * Use this for tabs opened by the browser chrome, so users can press the
      * "Back" button to return to the previous tab.
      *
      * @param url URL of page to load
      */
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -1,42 +1,50 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.favicons;
 
 import org.mozilla.gecko.AboutPages;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.favicons.cache.FaviconCache;
 import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.util.GeckoJarReader;
+import org.mozilla.gecko.util.NonEvictingLruCache;
 import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.NonEvictingLruCache;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
 public class Favicons {
     private static final String LOGTAG = "GeckoFavicons";
 
+    // A magic URL representing the app's own favicon, used for about: pages.
+    private static final String BUILT_IN_FAVICON_URL = "about:favicon";
+
     // Size of the favicon bitmap cache, in bytes (Counting payload only).
     public static final int FAVICON_CACHE_SIZE_BYTES = 512 * 1024;
 
     // Number of URL mappings from page URL to Favicon URL to cache in memory.
     public static final int NUM_PAGE_URL_MAPPINGS_TO_STORE = 128;
 
     public static final int NOT_LOADING  = 0;
     public static final int LOADED       = 1;
@@ -93,33 +101,52 @@ public class Favicons {
             public void run() {
                 listener.onFaviconLoaded(pageUrl, faviconURL, image);
             }
         });
         return NOT_LOADING;
     }
 
     /**
+     * Only returns a non-null Bitmap if the entire path is cached -- the
+     * page URL to favicon URL, and the favicon URL to in-memory bitmaps.
+     *
+     * Returns null otherwise.
+     */
+    public static Bitmap getCachedFaviconForSize(final String pageURL, int targetSize) {
+        final String faviconURL = sPageURLMappings.get(pageURL);
+        if (faviconURL == null) {
+            return null;
+        }
+        return getSizedFaviconFromCache(faviconURL, targetSize);
+    }
+
+    /**
      * Get a Favicon as close as possible to the target dimensions for the URL provided.
      * If a result is instantly available from the cache, it is returned and the listener is invoked.
      * Otherwise, the result is drawn from the database or network and the listener invoked when the
      * result becomes available.
      *
      * @param pageURL Page URL for which a Favicon is desired.
      * @param faviconURL URL of the Favicon to be downloaded, if known. If none provided, an educated
      *                    guess is made by the system.
      * @param targetSize Target size of the returned Favicon
      * @param listener Listener to call with the result of the load operation, if the result is not
      *                  immediately available.
      * @return The id of the asynchronous task created, NOT_LOADING if none is created, or
      *         LOADED if the value could be dispatched on the current thread.
      */
     public static int getFaviconForSize(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) {
+        // Do we know the favicon URL for this page already?
+        String cacheURL = faviconURL;
+        if (cacheURL == null) {
+            cacheURL = sPageURLMappings.get(pageURL);
+        }
+
         // If there's no favicon URL given, try and hit the cache with the default one.
-        String cacheURL = faviconURL;
         if (cacheURL == null)  {
             cacheURL = guessDefaultFaviconURL(pageURL);
         }
 
         // If it's something we can't even figure out a default URL for, just give up.
         if (cacheURL == null) {
             return dispatchResult(pageURL, null, sDefaultFavicon, listener);
         }
@@ -257,18 +284,18 @@ public class Favicons {
 
         return taskId;
     }
 
     public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
         sFaviconsCache.putSingleFavicon(pageUrl, image);
     }
 
-    public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images) {
-        sFaviconsCache.putFavicons(pageUrl, images);
+    public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images, boolean permanently) {
+        sFaviconsCache.putFavicons(pageUrl, images, permanently);
     }
 
     public static void clearMemCache() {
         sFaviconsCache.evictAll();
         sPageURLMappings.evictAll();
     }
 
     public static void putFaviconInFailedCache(String faviconURL) {
@@ -323,26 +350,59 @@ public class Favicons {
     /**
      * Called by GeckoApp on startup to pass this class a reference to the GeckoApp object used as
      * the application's Context.
      * Consider replacing with references to a staticly held reference to the GeckoApp object.
      *
      * @param context A reference to the GeckoApp instance.
      */
     public static void attachToContext(Context context) throws Exception {
+        final Resources res = context.getResources();
         sContext = context;
 
         // Decode the default Favicon ready for use.
-        sDefaultFavicon = BitmapFactory.decodeResource(context.getResources(), R.drawable.favicon);
+        sDefaultFavicon = BitmapFactory.decodeResource(res, R.drawable.favicon);
         if (sDefaultFavicon == null) {
             throw new Exception("Null default favicon was returned from the resources system!");
         }
 
-        sDefaultFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
-        sFaviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size));
+        sDefaultFaviconSize = res.getDimensionPixelSize(R.dimen.favicon_bg);
+        sFaviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, res.getDimensionPixelSize(R.dimen.favicon_largest_interesting_size));
+
+        // Initialize page mappings for each of our special pages.
+        for (String url : AboutPages.getDefaultIconPages()) {
+            sPageURLMappings.putWithoutEviction(url, BUILT_IN_FAVICON_URL);
+        }
+
+        // Load and cache the built-in favicon in each of its sizes.
+        // TODO: don't open the zip twice!
+        ArrayList<Bitmap> toInsert = new ArrayList<Bitmap>(2);
+        toInsert.add(loadBrandingBitmap(context, "favicon64.png"));
+        toInsert.add(loadBrandingBitmap(context, "favicon32.png"));
+        putFaviconsInMemCache(BUILT_IN_FAVICON_URL, toInsert.iterator(), true);
+    }
+
+    /**
+     * Compute a string like:
+     * "jar:jar:file:///data/app/org.mozilla.firefox-1.apk!/assets/omni.ja!/chrome/chrome/content/branding/favicon64.png"
+     */
+    private static String getBrandingBitmapPath(Context context, String name) {
+        final String apkPath = context.getPackageResourcePath();
+        return "jar:jar:" + new File(apkPath).toURI() + "!/" +
+               AppConstants.OMNIJAR_NAME + "!/" +
+               "chrome/chrome/content/branding/" + name;
+    }
+
+    private static Bitmap loadBrandingBitmap(Context context, String name) {
+        Bitmap b = GeckoJarReader.getBitmap(context.getResources(),
+                                            getBrandingBitmapPath(context, name));
+        if (b == null) {
+            throw new IllegalStateException("Bitmap " + name + " missing from JAR!");
+        }
+        return b;
     }
 
     /**
      * Helper method to get the default Favicon URL for a given pageURL. Generally: somewhere.com/favicon.ico
      *
      * @param pageURL Page URL for which a default Favicon URL is requested
      * @return The default Favicon URL.
      */
--- a/mobile/android/base/favicons/cache/FaviconCache.java
+++ b/mobile/android/base/favicons/cache/FaviconCache.java
@@ -18,16 +18,19 @@ import java.util.concurrent.atomic.Atomi
  * Implements a Least-Recently-Used cache for Favicons, keyed by Favicon URL.
  *
  * When a favicon at a particular URL is decoded, it will yield one or more bitmaps.
  * While in memory, these bitmaps are stored in a list, sorted in ascending order of size, in a
  * FaviconsForURL object.
  * The collection of FaviconsForURL objects currently in the cache is stored in mBackingMap, keyed
  * by favicon URL.
  *
+ * A second map exists for permanent cache entries -- ones that are never expired. These entries
+ * are assumed to be disjoint from those in the normal cache, and this map is checked first.
+ *
  * FaviconsForURL provides a method for obtaining the smallest icon larger than a given size - the
  * most appropriate icon for a particular size.
  * It also distinguishes between "primary" favicons (Ones that have merely been extracted from a
  * file downloaded from the website) and "secondary" favicons (Ones that have been computed locally
  * as resized versions of primary favicons.).
  *
  * FaviconsForURL is also responsible for storing URL-specific, as opposed to favicon-specific,
  * information. For the purposes of this cache, the simplifying assumption that the dominant colour
@@ -101,28 +104,31 @@ public class FaviconCache {
     public static final long FAILURE_RETRY_MILLISECONDS = 1000 * 60 * 20;
 
     // Map relating Favicon URLs with objects representing decoded favicons.
     // Since favicons may be container formats holding multiple icons, the underlying type holds a
     // sorted list of bitmap payloads in ascending order of size. The underlying type may be queried
     // for the least larger payload currently present.
     private final ConcurrentHashMap<String, FaviconsForURL> mBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
 
+    // And the same, but never evicted.
+    private final ConcurrentHashMap<String, FaviconsForURL> mPermanentBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
+
     // A linked list used to implement a queue, defining the LRU properties of the cache. Elements
     // contained within the various FaviconsForURL objects are held here, the least recently used
     // of which at the end of the list. When space needs to be reclaimed, the appropriate bitmap is
     // culled.
     private final LinkedList<FaviconCacheElement> mOrdering = new LinkedList<FaviconCacheElement>();
 
     // The above structures, if used correctly, enable this cache to exhibit LRU semantics across all
     // favicon payloads in the system, as well as enabling the dynamic selection from the cache of
     // the primary bitmap most suited to the requested size (in cases where multiple primary bitmaps
     // are provided by the underlying file format).
 
-    // Current size, in bytes, of the bitmap data present in the cache.
+    // Current size, in bytes, of the bitmap data present in the LRU cache.
     private final AtomicInteger mCurrentSize = new AtomicInteger(0);
 
     // The maximum quantity, in bytes, of bitmap data which may be stored in the cache.
     private final int mMaxSizeBytes;
 
     // Tracks the number of ongoing read operations. Enables the first one in to lock writers out and
     // the last one out to let them in.
     private final AtomicInteger mOngoingReads = new AtomicInteger(0);
@@ -211,16 +217,18 @@ public class FaviconCache {
 
         startRead();
 
         boolean isExpired = false;
         boolean isAborting = false;
 
         try {
             // If we don't have it in the cache, it certainly isn't a known failure.
+            // Non-evictable favicons are never failed, so we don't need to
+            // check mPermanentBackingMap.
             if (!mBackingMap.containsKey(faviconURL)) {
                 return false;
             }
 
             FaviconsForURL container = mBackingMap.get(faviconURL);
 
             // If the has failed flag is not set, it's certainly not a known failure.
             if (!container.mHasFailed) {
@@ -299,28 +307,34 @@ public class FaviconCache {
         if (faviconURL == null) {
             Log.e(LOGTAG, "You passed a null faviconURL to getFaviconForDimensions. Don't.");
             return null;
         }
 
         boolean doingWrites = false;
         boolean shouldComputeColour = false;
         boolean isAborting = false;
+        boolean wasPermanent = false;
+        FaviconsForURL container;
         final Bitmap newBitmap;
-        final FaviconsForURL container;
 
         startRead();
 
         try {
-            if (!mBackingMap.containsKey(faviconURL)) {
-                return null;
+            container = mPermanentBackingMap.get(faviconURL);
+            if (container == null) {
+                container = mBackingMap.get(faviconURL);
+                if (container == null) {
+                    // We don't have it!
+                    return null;
+                }
+            } else {
+                wasPermanent = true;
             }
 
-            container = mBackingMap.get(faviconURL);
-
             FaviconCacheElement cacheElement;
 
             int cacheElementIndex = container.getNextHighestIndex(targetSize);
 
             // cacheElementIndex now holds either the index of the next least largest bitmap from
             // targetSize, or -1 if targetSize > all bitmaps.
             if (cacheElementIndex != -1) {
                 // If cacheElementIndex is not the sentinel value, then it is a valid index into mFavicons.
@@ -407,19 +421,20 @@ public class FaviconCache {
             }
 
             // While the image might not actually BE that size, we set the size field to the target
             // because this is the best image you can get for a request of that size using the Favicon
             // information provided by this website.
             // This way, subsequent requests hit straight away.
             FaviconCacheElement newElement = container.addSecondary(newBitmap, targetSize);
 
-            setMostRecentlyUsed(newElement);
-
-            mCurrentSize.addAndGet(newElement.sizeOf());
+            if (!wasPermanent) {
+                setMostRecentlyUsed(newElement);
+                mCurrentSize.addAndGet(newElement.sizeOf());
+            }
         } finally {
             finishWrite();
         }
 
         return newBitmap;
     }
 
     /**
@@ -427,24 +442,28 @@ public class FaviconCache {
      *
      * @param key The URL of the Favicon for which a dominant colour is desired.
      * @return The cached dominant colour, or null if none is cached.
      */
     public int getDominantColor(String key) {
         startRead();
 
         try {
-            if (!mBackingMap.containsKey(key)) {
+            FaviconsForURL element = mPermanentBackingMap.get(key);
+            if (element == null) {
+                element = mBackingMap.get(key);
+            }
+
+            if (element == null) {
                 Log.w(LOGTAG, "Cannot compute dominant color of non-cached favicon. Cache fullness " +
                               mCurrentSize.get() + '/' + mMaxSizeBytes);
                 finishRead();
                 return 0xFFFFFF;
             }
 
-            FaviconsForURL element = mBackingMap.get(key);
 
             return element.ensureDominantColor();
         } finally {
             finishRead();
         }
     }
 
     /**
@@ -538,18 +557,19 @@ public class FaviconCache {
         cullIfRequired();
     }
 
     /**
      * Set the collection of primary favicons for the given URL to the provided collection of bitmaps.
      *
      * @param faviconURL The URL from which the favicons originate.
      * @param favicons A List of favicons decoded from this URL.
+     * @param permanently If true, the added favicons are never subject to eviction.
      */
-    public void putFavicons(String faviconURL, Iterator<Bitmap> favicons) {
+    public void putFavicons(String faviconURL, Iterator<Bitmap> favicons, boolean permanently) {
         // We don't know how many icons we'll have - let's just take a guess.
         FaviconsForURL toInsert = new FaviconsForURL(5 * NUM_FAVICON_SIZES);
         int sizeGained = 0;
 
         while (favicons.hasNext()) {
             Bitmap favicon = produceCacheableBitmap(favicons.next());
             if (favicon == null) {
                 continue;
@@ -562,38 +582,44 @@ public class FaviconCache {
         startRead();
 
         boolean abortingRead = false;
 
         // Not using setMostRecentlyUsed, because the elements are known to be new. This can be done
         // without taking the write lock, via the magic of the reordering semaphore.
         mReorderingSemaphore.acquireUninterruptibly();
         try {
-            for (FaviconCacheElement newElement : toInsert.mFavicons) {
-                mOrdering.offer(newElement);
+            if (!permanently) {
+                for (FaviconCacheElement newElement : toInsert.mFavicons) {
+                    mOrdering.offer(newElement);
+                }
             }
         } catch (Exception e) {
             abortingRead = true;
             mReorderingSemaphore.release();
             finishRead();
 
             Log.e(LOGTAG, "Favicon cache exception!", e);
             return;
         } finally {
             if (!abortingRead) {
                 mReorderingSemaphore.release();
                 upgradeReadToWrite();
             }
         }
 
         try {
-            mCurrentSize.addAndGet(sizeGained);
+            if (permanently) {
+                mPermanentBackingMap.put(faviconURL, toInsert);
+            } else {
+                mCurrentSize.addAndGet(sizeGained);
 
-            // Update the value in the LruCache...
-            recordRemoved(mBackingMap.put(faviconURL, toInsert));
+                // Update the value in the LruCache...
+                recordRemoved(mBackingMap.put(faviconURL, toInsert));
+            }
         } finally {
             finishWrite();
         }
 
         cullIfRequired();
     }
 
     /**
@@ -626,17 +652,19 @@ public class FaviconCache {
     }
 
     /**
      * Purge all elements from the FaviconCache. Handy if you want to reclaim some memory.
      */
     public void evictAll() {
         startWrite();
 
+        // Note that we neither clear, nor track the size of, the permanent map.
         try {
             mCurrentSize.set(0);
             mBackingMap.clear();
             mOrdering.clear();
+
         } finally {
             finishWrite();
         }
     }
 }
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -110,18 +110,22 @@ public class BrowserToolbar extends Geck
     private ImageView mUrlBarRightEdge;
     private GeckoTextView mTitle;
     private int mTitlePadding;
     private boolean mSiteSecurityVisible;
     private boolean mSwitchingTabs;
     private ShapedButton mTabs;
     private ImageButton mBack;
     private ImageButton mForward;
+    private ImageButton mStop;
+
+    // To de-bounce sets.
+    private Bitmap mLastFavicon;
     private ImageButton mFavicon;
-    private ImageButton mStop;
+
     private ImageButton mSiteSecurity;
     private PageActionLayout mPageActionLayout;
     private Animation mProgressSpinner;
     private TabCounter mTabsCounter;
     private GeckoImageButton mMenu;
     private GeckoImageView mMenuIcon;
     private LinearLayout mActionItemBar;
     private MenuPopup mMenuPopup;
@@ -423,17 +427,17 @@ public class BrowserToolbar extends Geck
                 SiteIdentityPopup siteIdentityPopup = mActivity.getSiteIdentityPopup();
                 siteIdentityPopup.updateIdentity(identityData);
                 siteIdentityPopup.show();
             }
         };
 
         mFavicon.setOnClickListener(faviconListener);
         mSiteSecurity.setOnClickListener(faviconListener);
-        
+
         mStop.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 Tab tab = Tabs.getInstance().getSelectedTab();
                 if (tab != null)
                     tab.doStop();
                 setProgressVisibility(false);
             }
@@ -527,16 +531,17 @@ public class BrowserToolbar extends Geck
                     mActivity.refreshToolbarHeight();
                 }
             });
         }
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
+        Log.d(LOGTAG, "onTabChanged: " + msg);
         final Tabs tabs = Tabs.getInstance();
 
         // These conditions are split into three phases:
         // * Always do first
         // * Handling specific to the selected tab
         // * Always do afterwards.
 
         switch (msg) {
@@ -578,17 +583,22 @@ public class BrowserToolbar extends Geck
 
                 case SELECTED:
                 case LOAD_ERROR:
                     updateTitle();
                     // Fall through.
                 case LOCATION_CHANGE:
                     // A successful location change will cause Tab to notify
                     // us of a title change, so we don't update the title here.
-                    refresh();
+                    // And there's no point in refreshing the UI
+                    // if the page is the same.
+                    final String oldURL = (String) data;
+                    if (!TextUtils.equals(oldURL, tab.getURL())) {
+                        refresh();
+                    }
                     break;
 
                 case CLOSED:
                 case ADDED:
                     updateBackButton(canDoBack(tab));
                     updateForwardButton(canDoForward(tab));
                     break;
 
@@ -736,33 +746,37 @@ public class BrowserToolbar extends Geck
 
         // Update A11y information
         mTabs.setContentDescription((count > 1) ?
                                     mActivity.getString(R.string.num_tabs, count) :
                                     mActivity.getString(R.string.one_tab));
     }
 
     public void setProgressVisibility(boolean visible) {
+        Log.d(LOGTAG, "setProgressVisibility: " + visible);
         // The "Throbber start" and "Throbber stop" log messages in this method
         // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
         // See discussion in Bug 804457. Bug 805124 tracks paring these down.
         if (visible) {
             mFavicon.setImageResource(R.drawable.progress_spinner);
-            //To stop the glitch caused by mutiple start() calls.
+            mLastFavicon = null;
+
+            // To stop the glitch caused by multiple start() calls.
             if (!mSpinnerVisible) {
                 setPageActionVisibility(true);
                 mFavicon.setAnimation(mProgressSpinner);
                 mProgressSpinner.start();
                 mSpinnerVisible = true;
             }
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
         } else {
             Tab selectedTab = Tabs.getInstance().getSelectedTab();
-            if (selectedTab != null)
+            if (selectedTab != null) {
                 setFavicon(selectedTab.getFavicon());
+            }
 
             if (mSpinnerVisible) {
                 setPageActionVisibility(false);
                 mFavicon.setAnimation(null);
                 mProgressSpinner.cancel();
                 mSpinnerVisible = false;
             }
             Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
@@ -863,25 +877,25 @@ public class BrowserToolbar extends Geck
         mUrlEditLayout.onEditSuggestion(suggestion);
     }
 
     public void setTitle(CharSequence title) {
         mTitle.setText(title);
         setContentDescription(title != null ? title : mTitle.getHint());
     }
 
-    // Sets the toolbar title according to the selected tab, obeying the mShowUrl prference.
+    // Sets the toolbar title according to the selected tab, obeying the mShowUrl preference.
     private void updateTitle() {
-        Tab tab = Tabs.getInstance().getSelectedTab();
+        final Tab tab = Tabs.getInstance().getSelectedTab();
         // Keep the title unchanged if there's no selected tab, or if the tab is entering reader mode.
         if (tab == null || tab.isEnteringReaderMode()) {
             return;
         }
 
-        String url = tab.getURL();
+        final String url = tab.getURL();
 
         if (!isEditing()) {
             mUrlEditLayout.setText(url);
         }
 
         // Setting a null title will ensure we just see the "Enter Search or Address" placeholder text.
         if (AboutPages.isTitlelessAboutPage(url)) {
             setTitle(null);
@@ -918,27 +932,36 @@ public class BrowserToolbar extends Geck
                 title = builder;
             }
         }
 
         setTitle(title);
     }
 
     private void setFavicon(Bitmap image) {
-        if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING)
+        Log.d(LOGTAG, "setFavicon(" + image + ")");
+        if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING) {
             return;
+        }
+
+        if (image == mLastFavicon) {
+            Log.d(LOGTAG, "Ignoring favicon set: new favicon is identical to previous favicon.");
+            return;
+        }
+
+        mLastFavicon = image;     // Cache the original so we can debounce without scaling.
 
         if (image != null) {
             image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
             mFavicon.setImageBitmap(image);
         } else {
             mFavicon.setImageDrawable(null);
         }
     }
-    
+
     private void setSecurityMode(String mode) {
         int imageLevel = SiteIdentityPopup.getSecurityImageLevel(mode);
         mSiteSecurity.setImageLevel(imageLevel);
         mShowSiteSecurity = (imageLevel != SiteIdentityPopup.LEVEL_UKNOWN);
 
         setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
     }
 
@@ -1566,16 +1589,17 @@ public class BrowserToolbar extends Geck
     }
 
     protected void unregisterEventListener(String event) {
         GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
+        Log.d(LOGTAG, "handleMessage: " + event);
         if (event.equals("Reader:Click")) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 tab.toggleReaderMode();
             }
         } else if (event.equals("Reader:LongClick")) {
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
--- a/mobile/android/chrome/content/aboutHome.xhtml
+++ b/mobile/android/chrome/content/aboutHome.xhtml
@@ -11,13 +11,12 @@
 
 <!-- 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/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <title>&abouthome.title;</title>
-  <link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
 </head>
 <body>
 </body>
 </html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4383,21 +4383,26 @@ var BrowserEventHandler = {
           try {
             let data = JSON.parse(aData);
             let [x, y] = [data.x, data.y];
             if (ElementTouchHelper.isElementClickable(element)) {
               [x, y] = this._moveClickPoint(element, x, y);
               element = ElementTouchHelper.anyElementFromPoint(x, y);
             }
 
+            // Was the element already focused before it was clicked?
+            let isFocused = (element == BrowserApp.getFocusedInput(BrowserApp.selectedBrowser, true));
+
             this._sendMouseEvent("mousemove", element, x, y);
             this._sendMouseEvent("mousedown", element, x, y);
             this._sendMouseEvent("mouseup",   element, x, y);
 
-            SelectionHandler.attachCaret(element);
+            // If the element was previously focused, show the caret attached to it.
+            if (isFocused)
+              SelectionHandler.attachCaret(element);
 
             // scrollToFocusedInput does its own checks to find out if an element should be zoomed into
             BrowserApp.scrollToFocusedInput(BrowserApp.selectedBrowser);
           } catch(e) {
             Cu.reportError(e);
           }
         }
         this._cancelTapHighlight();
--- a/mobile/android/themes/core/content.css
+++ b/mobile/android/themes/core/content.css
@@ -295,19 +295,19 @@ select[disabled] > button {
 /* -moz-touch-enabled? media elements */
 :-moz-any(video, audio) > xul|videocontrols {
   -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#touchControls");
 }
 
 *:-moz-any-link:active,
 *[role=button]:active,
 button:not([disabled]):active,
-input:not([disabled]):active,
+input:not(:focus):not([disabled]):active,
 select:not([disabled]):active,
-textarea:not([disabled]):active,
+textarea:not(:focus):not([disabled]):active,
 option:active,
 label:active,
 xul|menulist:active {
   background-color: @color_background_highlight_overlay@;
 }
 
 input[type=number]::-moz-number-spin-box {
   display: none;