merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 27 Nov 2013 11:01:25 +0100
changeset 157732 6ecf0c4dfcbe6b04640713afa29eba044b6c0475
parent 157720 aeaac4c8fbedd084564ccbf8ab5c9c25cff9c5b4 (current diff)
parent 157731 9f446888b2d2318bde2761089bfc4a01a1c811c6 (diff)
child 157764 6d9e485ede928e818504b5354bc5d838239c8a72
child 157814 61bf4acd28efa9aa7d71e3735ae1a77ea84faba1
push id25721
push usercbook@mozilla.com
push dateWed, 27 Nov 2013 10:02:03 +0000
treeherdermozilla-central@6ecf0c4dfcbe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
6ecf0c4dfcbe / 28.0a1 / 20131127030201 / files
nightly linux64
6ecf0c4dfcbe / 28.0a1 / 20131127030201 / files
nightly mac
6ecf0c4dfcbe / 28.0a1 / 20131127030201 / files
nightly win32
6ecf0c4dfcbe / 28.0a1 / 20131127030201 / files
nightly win64
6ecf0c4dfcbe / 28.0a1 / 20131127030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-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;