Bug 914296: Add more intelligent caching to the Favicons system. r=rnewman
authorChris Kitching <ckitching@mozilla.com>
Fri, 11 Oct 2013 16:42:39 -0700
changeset 164389 c83b4d9555bc43618feb017e6769bff0978bd16e
parent 164388 27a7bb8d83271ea7a77fa223f8203eb1908f7088
child 164390 3ff6c5ec4dc57a9011605bc6fe50e4f51c6913ab
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs914296
milestone27.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
Bug 914296: Add more intelligent caching to the Favicons system. r=rnewman
mobile/android/base/BrowserApp.java
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
mobile/android/base/Tabs.java
mobile/android/base/db/BrowserDB.java
mobile/android/base/db/BrowserProvider.java
mobile/android/base/db/LocalBrowserDB.java
mobile/android/base/favicons/Favicons.java
mobile/android/base/favicons/LoadFaviconTask.java
mobile/android/base/favicons/OnFaviconLoadedListener.java
mobile/android/base/favicons/cache/FaviconCache.java
mobile/android/base/favicons/cache/FaviconCacheElement.java
mobile/android/base/favicons/cache/FaviconsForURL.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/HomeFragment.java
mobile/android/base/home/TopSitesGridItemView.java
mobile/android/base/home/TopSitesPage.java
mobile/android/base/home/TwoLinePageRow.java
mobile/android/base/resources/values/dimens.xml
mobile/android/base/widget/FaviconView.java
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -52,17 +52,16 @@ import android.nfc.NdefRecord;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcEvent;
 import android.os.Build;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.InputDevice;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
@@ -592,17 +591,17 @@ abstract public class BrowserApp extends
 
                 if (itemId == 0) {
                     new EditBookmarkDialog(BrowserApp.this).show(tab.getURL());
                 } else if (itemId == 1) {
                     String url = tab.getURL();
                     String title = tab.getDisplayTitle();
                     Bitmap favicon = tab.getFavicon();
                     if (url != null && title != null) {
-                        GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon, "");
+                        GeckoAppShell.createShortcut(title, url, url, favicon, "");
                     }
                 }
             }
         });
 
         final Prompt.PromptListItem[] items = new Prompt.PromptListItem[2];
         Resources res = getResources();
         items[0] = new Prompt.PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
@@ -705,21 +704,21 @@ abstract public class BrowserApp extends
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 final String url = tab.getURL();
                 final String title = tab.getDisplayTitle();
                 if (url == null || title == null) {
                     return true;
                 }
 
-                Favicons.loadFavicon(url, tab.getFaviconURL(), 0,
+                Favicons.getFaviconForSize(url, tab.getFaviconURL(), Integer.MAX_VALUE, LoadFaviconTask.FLAG_PERSIST,
                 new OnFaviconLoadedListener() {
                     @Override
-                    public void onFaviconLoaded(String url, Bitmap favicon) {
-                        GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon, "");
+                    public void onFaviconLoaded(String pageUrl, String faviconURL, Bitmap favicon) {
+                        GeckoAppShell.createShortcut(title, url, url, favicon, "");
                     }
                 });
             }
             return true;
         }
 
         return false;
     }
@@ -1275,17 +1274,17 @@ abstract public class BrowserApp extends
 
         final Tabs tabs = Tabs.getInstance();
         final int tabId = tabs.getTabIdForUrl(url, tabs.getSelectedTab().isPrivate());
         if (tabId < 0) {
             return false;
         }
 
         // If this tab is already selected, just hide the home pager.
-        if (tabs.isSelectedTab(tabs.getTab(tabId))) {
+        if (tabs.isSelectedTabId(tabId)) {
             hideHomePager();
         } else {
             tabs.selectTab(tabId);
         }
 
         hideBrowserSearch();
         mBrowserToolbar.cancelEdit();
 
@@ -1325,47 +1324,47 @@ abstract public class BrowserApp extends
     private void openReadingList() {
         Tabs.getInstance().loadUrl(ABOUT_HOME, Tabs.LOADURL_READING_LIST);
     }
 
     /* Favicon methods */
     private void loadFavicon(final Tab tab) {
         maybeCancelFaviconLoad(tab);
 
-        int flags = LoadFaviconTask.FLAG_SCALE | ( (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST);
-        int id = Favicons.loadFavicon(tab.getURL(), tab.getFaviconURL(), flags,
-                        new OnFaviconLoadedListener() {
-            @Override
-            public void onFaviconLoaded(String pageUrl, Bitmap favicon) {
-                // If we failed to load a favicon, we use the default favicon instead.
-                if (favicon == null) {
-                    favicon = Favicons.sDefaultFavicon;
-                }
+        final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
+
+        int flags = (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST;
+        int id = Favicons.getFaviconForSize(tab.getURL(), tab.getFaviconURL(), tabFaviconSize, flags,
+            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.
+                    if (favicon == null) {
+                        favicon = Favicons.sDefaultFavicon;
+                    }
 
-                // The tab might be pointing to another URL by the time the
-                // favicon is finally loaded, in which case we simply ignore it.
-                if (!tab.getURL().equals(pageUrl))
-                    return;
+                    // The tab might be pointing to another URL by the time the
+                    // favicon is finally loaded, in which case we simply ignore it.
+                    // See also: Bug 920331.
+                    if (!tab.getURL().equals(pageUrl)) {
+                        return;
+                    }
 
-                tab.updateFavicon(favicon);
-                tab.setFaviconLoadId(Favicons.NOT_LOADING);
-
-                Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.FAVICON);
-            }
-        });
+                    tab.updateFavicon(favicon);
+                    tab.setFaviconLoadId(Favicons.NOT_LOADING);
+                    Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.FAVICON);
+                }
+            });
 
         tab.setFaviconLoadId(id);
     }
 
     private void maybeCancelFaviconLoad(Tab tab) {
         int faviconLoadId = tab.getFaviconLoadId();
 
-        if (faviconLoadId == Favicons.NOT_LOADING)
-            return;
-
         // Cancel pending favicon load task
         Favicons.cancelFaviconLoad(faviconLoadId);
 
         // Reset favicon load state
         tab.setFaviconLoadId(Favicons.NOT_LOADING);
     }
 
     /**
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1187,17 +1187,21 @@ abstract public class GeckoApp
         ((GeckoApplication)getApplication()).initialize();
 
         sAppContext = this;
         GeckoAppShell.setContextGetter(this);
         GeckoAppShell.setGeckoInterface(this);
         ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
 
         Tabs.getInstance().attachToContext(this);
-        Favicons.attachToContext(this);
+        try {
+            Favicons.attachToContext(this);
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
+        }
 
         // When we detect a locale change, we need to restart Gecko, which
         // actually means restarting the entire application. This logic should
         // actually be handled elsewhere since GeckoApp may not be alive to
         // handle this event if "Don't keep activities" is enabled (filed as
         // bug 889082).
         if (((GeckoApplication)getApplication()).needsRestart()) {
             doRestart();
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -71,16 +71,19 @@ FENNEC_JAVA_FILES = \
   db/BrowserDB.java \
   db/LocalBrowserDB.java \
   db/DBUtils.java \
   DataReportingNotification.java \
   Distribution.java \
   DoorHanger.java \
   DoorHangerPopup.java \
   EditBookmarkDialog.java \
+  favicons/cache/FaviconCache.java \
+  favicons/cache/FaviconCacheElement.java \
+  favicons/cache/FaviconsForURL.java \
   favicons/Favicons.java \
   favicons/LoadFaviconTask.java \
   favicons/OnFaviconLoadedListener.java \
   FilePickerResultHandler.java \
   FilePickerResultHandlerSync.java \
   FindInPageBar.java \
   FlowLayout.java \
   FontSizePreference.java \
--- a/mobile/android/base/Tabs.java
+++ b/mobile/android/base/Tabs.java
@@ -2,27 +2,25 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONObject;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
-import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.graphics.Color;
 import android.net.Uri;
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.Log;
@@ -267,16 +265,21 @@ public class Tabs implements GeckoEventL
     public Tab getSelectedTab() {
         return mSelectedTab;
     }
 
     public boolean isSelectedTab(Tab tab) {
         return tab != null && tab == mSelectedTab;
     }
 
+    public boolean isSelectedTabId(int tabId) {
+        final Tab selected = mSelectedTab;
+        return selected != null && selected.getId() == tabId;
+    }
+
     public synchronized Tab getTab(int id) {
         if (mTabs.size() == 0)
             return null;
 
         if (!mTabs.containsKey(id))
            return null;
 
         return mTabs.get(id);
@@ -602,16 +605,25 @@ public class Tabs implements GeckoEventL
             if (TextUtils.equals(tabUrl, url) && isPrivate == tab.isPrivate()) {
                 return tab.getId();
             }
         }
 
         return -1;
     }
 
+    public int getTabIdForUrl(String url) {
+        return getTabIdForUrl(url, Tabs.getInstance().getSelectedTab().isPrivate());
+    }
+
+    public synchronized Tab getTabForUrl(String url) {
+        int tabId = getTabIdForUrl(url);
+        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);
     }
--- a/mobile/android/base/db/BrowserDB.java
+++ b/mobile/android/base/db/BrowserDB.java
@@ -83,20 +83,16 @@ public class BrowserDB {
         public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
 
         public void addReadingListItem(ContentResolver cr, String title, String uri);
 
         public void removeReadingListItemWithURL(ContentResolver cr, String uri);
 
         public Bitmap getFaviconForUrl(ContentResolver cr, String uri);
 
-        public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri);
-
-        public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls);
-
         public String getFaviconUrlForHistoryUrl(ContentResolver cr, String url);
 
         public void updateFaviconForUrl(ContentResolver cr, String pageUri, Bitmap favicon, String faviconUri);
 
         public void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail);
 
         public byte[] getThumbnailForUrl(ContentResolver cr, String uri);
 
@@ -237,26 +233,18 @@ public class BrowserDB {
     public static void addReadingListItem(ContentResolver cr, String title, String uri) {
         sDb.addReadingListItem(cr, title, uri);
     }
 
     public static void removeReadingListItemWithURL(ContentResolver cr, String uri) {
         sDb.removeReadingListItemWithURL(cr, uri);
     }
 
-    public static Bitmap getFaviconForUrl(ContentResolver cr, String uri) {
-        return sDb.getFaviconForUrl(cr, uri);
-    }
-
-    public static byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) {
-        return sDb.getFaviconBytesForUrl(cr, uri);
-    }
-
-    public static Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
-        return sDb.getFaviconsForUrls(cr, urls);
+    public static Bitmap getFaviconForFaviconUrl(ContentResolver cr, String faviconURL) {
+        return sDb.getFaviconForUrl(cr, faviconURL);
     }
 
     public static String getFaviconUrlForHistoryUrl(ContentResolver cr, String url) {
         return sDb.getFaviconUrlForHistoryUrl(cr, url);
     }
 
     public static void updateFaviconForUrl(ContentResolver cr, String pageUri, Bitmap favicon, String faviconUri) {
         sDb.updateFaviconForUrl(cr, pageUri, favicon, faviconUri);
--- a/mobile/android/base/db/BrowserProvider.java
+++ b/mobile/android/base/db/BrowserProvider.java
@@ -3010,16 +3010,21 @@ public class BrowserProvider extends Con
         stripEmptyByteArray(values, Favicons.DATA);
 
         // Extract the page URL from the ContentValues
         if (values.containsKey(Favicons.PAGE_URL)) {
             pageUrl = values.getAsString(Favicons.PAGE_URL);
             values.remove(Favicons.PAGE_URL);
         }
 
+        // If no URL is provided, insert using the default one.
+        if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
+            values.put(Favicons.URL, org.mozilla.gecko.favicons.Favicons.guessDefaultFaviconURL(pageUrl));
+        }
+
         long now = System.currentTimeMillis();
         values.put(Favicons.DATE_CREATED, now);
         values.put(Favicons.DATE_MODIFIED, now);
         faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
 
         if (pageUrl != null) {
             updateFaviconIdsForUrl(db, pageUrl, faviconId);
         }
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -691,51 +691,52 @@ public class LocalBrowserDB implements B
         values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
 
         cr.update(mBookmarksUriWithProfile,
                   values,
                   Bookmarks._ID + " = ?",
                   new String[] { String.valueOf(id) });
     }
 
+    /**
+     * Get the favicon from the database, if any, associated with the given favicon URL. (That is,
+     * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
+     * @param cr The ContentResolver to use.
+     * @param faviconURL The URL of the favicon to fetch from the database.
+     * @return The decoded Bitmap from the database, if any. null if none is stored.
+     */
     @Override
-    public Bitmap getFaviconForUrl(ContentResolver cr, String uri) {
-        final byte[] b = getFaviconBytesForUrl(cr, uri);
-        if (b == null) {
-            return null;
-        }
-
-        return BitmapUtils.decodeByteArray(b);
-    }
-
-    @Override
-    public byte[] getFaviconBytesForUrl(ContentResolver cr, String uri) {
+    public Bitmap getFaviconForUrl(ContentResolver cr, String faviconURL) {
         Cursor c = null;
         byte[] b = null;
 
         try {
-            c = cr.query(mCombinedUriWithProfile,
-                         new String[] { Combined.FAVICON },
-                         Combined.URL + " = ?",
-                         new String[] { uri },
+            c = cr.query(mFaviconsUriWithProfile,
+                         new String[] { Favicons.DATA },
+                         Favicons.URL + " = ?",
+                         new String[] { faviconURL },
                          null);
 
             if (!c.moveToFirst()) {
                 return null;
             }
 
-            final int faviconIndex = c.getColumnIndexOrThrow(Combined.FAVICON);
+            final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA);
             b = c.getBlob(faviconIndex);
         } finally {
             if (c != null) {
                 c.close();
             }
         }
 
-        return b;
+        if (b == null) {
+            return null;
+        }
+
+        return BitmapUtils.decodeByteArray(b);
     }
 
     @Override
     public String getFaviconUrlForHistoryUrl(ContentResolver cr, String uri) {
         Cursor c = null;
 
         try {
             c = cr.query(mHistoryUriWithProfile,
@@ -750,38 +751,16 @@ public class LocalBrowserDB implements B
             if (c != null)
                 c.close();
         }
 
         return null;
     }
 
     @Override
-    public Cursor getFaviconsForUrls(ContentResolver cr, List<String> urls) {
-        StringBuilder selection = new StringBuilder();
-        selection.append(Favicons.URL + " IN (");
-
-        for (int i = 0; i < urls.size(); i++) {
-            final String url = urls.get(i);
-
-            if (i > 0)
-                selection.append(", ");
-
-            DatabaseUtils.appendEscapedSQLString(selection, url);
-        }
-
-        selection.append(")");
-
-        return cr.query(mCombinedUriWithProfile,
-                        new String[] { Combined.URL, Combined.FAVICON },
-                        selection.toString(),
-                        null, null);
-    }
-
-    @Override
     public void updateFaviconForUrl(ContentResolver cr, String pageUri,
             Bitmap favicon, String faviconUri) {
         ContentValues values = new ContentValues();
         values.put(Favicons.URL, faviconUri);
         values.put(Favicons.PAGE_URL, pageUri);
 
         byte[] data = null;
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
--- a/mobile/android/base/favicons/Favicons.java
+++ b/mobile/android/base/favicons/Favicons.java
@@ -1,150 +1,279 @@
 /* -*- 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 android.graphics.BitmapFactory;
+import android.text.TextUtils;
 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.ThreadUtils;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.support.v4.util.LruCache;
 import android.util.Log;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 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";
 
+    // 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 PAGE_URL_MAPPINGS_TO_STORE = 128;
+
     public static final int NOT_LOADING = 0;
-    public static final int FAILED_EXPIRY_NEVER = -1;
     public static final int FLAG_PERSIST = 1;
     public static final int FLAG_SCALE = 2;
 
-    private static int sFaviconSmallSize = -1;
-    private static int sFaviconLargeSize = -1;
-
     protected static Context sContext;
 
-    private static final Map<Integer, LoadFaviconTask> sLoadTasks = Collections.synchronizedMap(new HashMap<Integer, LoadFaviconTask>());
-    private static final LruCache<String, Bitmap> sFaviconCache = new LruCache<String, Bitmap>(1024 * 1024) {
-        @Override
-        protected int sizeOf(String url, Bitmap image) {
-            return image.getRowBytes() * image.getHeight();
-        }
-    };
-
+    // The default Favicon to show if no other can be found.
     public static Bitmap sDefaultFavicon;
 
-    // A cache of the Favicon which have recently failed to download - prevents us from repeatedly
-    // trying to download a Favicon when doing so is currently impossible.
-    private static final LruCache<String, Long> sFailedCache = new LruCache<String, Long>(64);
+    // The density-adjusted default Favicon dimensions.
+    public static int sDefaultFaviconSize;
+
+    private static final Map<Integer, LoadFaviconTask> sLoadTasks = Collections.synchronizedMap(new HashMap<Integer, LoadFaviconTask>());
+
+    // Cache to hold mappings between page URLs and Favicon URLs. Used to avoid going to the DB when
+    // doing so is not necessary.
+    private static final LruCache<String, String> sPageURLMappings = new LruCache<String, String>(PAGE_URL_MAPPINGS_TO_STORE);
 
-    // A cache holding the dominant colours of favicons - used by FaviconView to fill the extra space
-    // around a Favicon when it is asked to render a Favicon small than the view.
-    private static final LruCache<String, Integer> sColorCache = new LruCache<String, Integer>(256);
-    static void dispatchResult(final String pageUrl, final Bitmap image,
+    public static String getFaviconURLForPageURLFromCache(String pageURL) {
+        return sPageURLMappings.get(pageURL);
+    }
+
+    /**
+     * Insert the given pageUrl->faviconUrl mapping into the memory cache of such mappings.
+     * Useful for short-circuiting local database access.
+     */
+    public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) {
+        sPageURLMappings.put(pageURL, faviconURL);
+    }
+
+    private static FaviconCache sFaviconsCache;
+    static void dispatchResult(final String pageUrl, final String faviconURL, final Bitmap image,
             final OnFaviconLoadedListener listener) {
-        if (pageUrl != null && image != null)
-            putFaviconInMemCache(pageUrl, image);
-
         // We want to always run the listener on UI thread
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
-                if (listener != null)
-                    listener.onFaviconLoaded(pageUrl, image);
+                if (listener != null) {
+                    listener.onFaviconLoaded(pageUrl, faviconURL, image);
+                }
             }
         });
     }
 
-    public static String getFaviconUrlForPageUrl(String pageUrl) {
-        return BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageUrl);
-    }
+    /**
+     * 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.
+     */
+    public static int getFaviconForSize(String pageURL, String faviconURL, int targetSize, int flags, OnFaviconLoadedListener listener) {
+        // 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);
+        }
 
-    public static int loadFavicon(String pageUrl, String faviconUrl, int flags,
-            OnFaviconLoadedListener listener) {
+        // If it's something we can't even figure out a default URL for, just give up.
+        if (cacheURL == null) {
+            dispatchResult(pageURL, null, sDefaultFavicon, listener);
+            return NOT_LOADING;
+        }
 
-        // Handle the case where page url is empty
-        if (pageUrl == null || pageUrl.length() == 0) {
-            dispatchResult(null, null, listener);
-            return -1;
+        Bitmap cachedIcon = getSizedFaviconFromCache(cacheURL, targetSize);
+        if (cachedIcon != null) {
+            dispatchResult(pageURL, cacheURL, cachedIcon, listener);
+            return NOT_LOADING;
+        }
+
+        // Check if favicon has failed.
+        if (sFaviconsCache.isFailedFavicon(cacheURL)) {
+            dispatchResult(pageURL, cacheURL, sDefaultFavicon, listener);
+            return NOT_LOADING;
         }
 
-        // Check if favicon has failed
-        if (isFailedFavicon(pageUrl)) {
-            dispatchResult(pageUrl, null, listener);
-            return -1;
+        // Failing that, try and get one from the database or internet.
+        return loadUncachedFavicon(pageURL, faviconURL, flags, targetSize, listener);
+    }
+
+    /**
+     * Returns the cached Favicon closest to the target size if any exists or is coercible. Returns
+     * null otherwise. Does not query the database or network for the Favicon is the result is not
+     * immediately available.
+     *
+     * @param faviconURL URL of the Favicon to query for.
+     * @param targetSize The desired size of the returned Favicon.
+     * @return The cached Favicon, rescaled to be as close as possible to the target size, if any exists.
+     *         null if no applicable Favicon exists in the cache.
+     */
+    public static Bitmap getSizedFaviconFromCache(String faviconURL, int targetSize) {
+        return sFaviconsCache.getFaviconForDimensions(faviconURL, targetSize);
+    }
+
+    /**
+     * Attempts to find a Favicon for the provided page URL from either the mem cache or the database.
+     * Does not need an explicit favicon URL, since, as we are accessing the database anyway, we
+     * can query the history DB for the Favicon URL.
+     * Handy for easing the transition from caching with page URLs to caching with Favicon URLs.
+     *
+     * A null result is passed to the listener if no value is locally available. The Favicon is not
+     * added to the failure cache.
+     *
+     * @param pageURL Page URL for which a Favicon is wanted.
+     * @param targetSize Target size of the desired Favicon to pass to the cache query
+     * @param callback Callback to fire with the result.
+     * @return The job ID of the spawned async task, if any.
+     */
+    public static int getSizedFaviconForPageFromLocal(final String pageURL, final int targetSize, final OnFaviconLoadedListener callback) {
+        // Firstly, try extremely hard to cheat.
+        // Have we cached this favicon URL? If we did, we can consult the memcache right away.
+        String targetURL = sPageURLMappings.get(pageURL);
+        if (targetURL != null) {
+            // Check if favicon has failed.
+            if (sFaviconsCache.isFailedFavicon(targetURL)) {
+                dispatchResult(pageURL, targetURL, null, callback);
+                return NOT_LOADING;
+            }
+
+            // Do we have a Favicon in the cache for this favicon URL?
+            Bitmap result = getSizedFaviconFromCache(targetURL, targetSize);
+            if (result != null) {
+                // Victory - immediate response!
+                dispatchResult(pageURL, targetURL, result, callback);
+                return NOT_LOADING;
+            }
         }
 
-        // Check if favicon is mem cached
-        Bitmap image = getFaviconFromMemCache(pageUrl);
-        if (image != null) {
-            dispatchResult(pageUrl, image, listener);
-            return -1;
+        // No joy using in-memory resources. Go to background thread and ask the database.
+        LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageURL, targetURL, 0, callback, targetSize, true);
+        int taskId = task.getId();
+        sLoadTasks.put(taskId, task);
+        task.execute();
+        return taskId;
+    }
+
+    public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) {
+        return getSizedFaviconForPageFromLocal(pageURL, sDefaultFaviconSize, callback);
+    }
+    /**
+     * Helper method to determine the URL of the Favicon image for a given page URL by querying the
+     * history database. Should only be called from the background thread - does database access.
+     *
+     * @param pageURL The URL of a webpage with a Favicon.
+     * @return The URL of the Favicon used by that webpage, according to either the History database
+     *         or a somewhat educated guess.
+     */
+    public static String getFaviconUrlForPageUrl(String pageURL) {
+        // Attempt to determine the Favicon URL from the Tabs datastructure. Can dodge having to use
+        // the database sometimes by doing this.
+        String targetURL;
+        Tab theTab = Tabs.getInstance().getTabForUrl(pageURL);
+        if (theTab != null) {
+            targetURL = theTab.getFaviconURL();
+            if (targetURL != null) {
+                return targetURL;
+            }
         }
 
-        LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener);
+        targetURL = BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageURL);
+        if (targetURL == null) {
+            // Nothing in the history database. Fall back to the default URL and hope for the best.
+            targetURL = guessDefaultFaviconURL(pageURL);
+        }
+        return targetURL;
+    }
+
+    /**
+     * Helper function to create an async job to load a Favicon which does not exist in the memcache.
+     * Contains logic to prevent the repeated loading of Favicons which have previously failed.
+     * There is no support for recovery from transient failures.
+     *
+     * @param pageUrl URL of the page for which to load a Favicon. If null, no job is created.
+     * @param faviconUrl The URL of the Favicon to load. If null, an attempt to infer the value from
+     *                   the history database will be made, and ultimately an attempt to guess will
+     *                   be made.
+     * @param flags Flags to be used by the LoadFaviconTask while loading. Currently only one flag
+     *              is supported, LoadFaviconTask.FLAG_PERSIST.
+     *              If FLAG_PERSIST is set and the Favicon is ultimately loaded from the internet,
+     *              the downloaded Favicon is subsequently stored in the local database.
+     *              If FLAG_PERSIST is unset, the downloaded Favicon is stored only in the memcache.
+     *              FLAG_PERSIST has no effect on loads which come from the database.
+     * @param listener The OnFaviconLoadedListener to invoke with the result of this Favicon load.
+     * @return The id of the LoadFaviconTask handling this job.
+     */
+    private static int loadUncachedFavicon(String pageUrl, String faviconUrl, int flags, int targetSize, OnFaviconLoadedListener listener) {
+        // Handle the case where we have no page url.
+        if (TextUtils.isEmpty(pageUrl)) {
+            dispatchResult(null, null, null, listener);
+            return NOT_LOADING;
+        }
+
+        LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener, targetSize, false);
 
         int taskId = task.getId();
         sLoadTasks.put(taskId, task);
 
         task.execute();
 
         return taskId;
     }
 
-    public static Bitmap getFaviconFromMemCache(String pageUrl) {
-        // If for some reason the key is null, simply return null
-        // and avoid an exception on the mem cache (see bug 813546)
-        if (pageUrl == null) {
-            return null;
-        }
-
-        return sFaviconCache.get(pageUrl);
+    public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
+        sFaviconsCache.putSingleFavicon(pageUrl, image);
     }
 
-    public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
-        sFaviconCache.put(pageUrl, image);
+    public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images) {
+        sFaviconsCache.putFavicons(pageUrl, images);
     }
 
     public static void clearMemCache() {
-        sFaviconCache.evictAll();
+        sFaviconsCache.evictAll();
+        sPageURLMappings.evictAll();
     }
 
-    public static boolean isFailedFavicon(String pageUrl) {
-        Long fetchTime = sFailedCache.get(pageUrl);
-        if (fetchTime == null)
-            return false;
-        // We don't have any other rules yet.
-        return true;
-    }
-
-    public static void putFaviconInFailedCache(String pageUrl, long fetchTime) {
-        sFailedCache.put(pageUrl, fetchTime);
-    }
-
-    public static void clearFailedCache() {
-        sFailedCache.evictAll();
+    public static void putFaviconInFailedCache(String faviconURL) {
+        sFaviconsCache.putFailed(faviconURL);
     }
 
     public static boolean cancelFaviconLoad(int taskId) {
-        Log.d(LOGTAG, "Requesting cancelation of favicon load (" + taskId + ")");
+        if (taskId == NOT_LOADING) {
+            return false;
+        }
 
-        boolean cancelled = false;
+        boolean cancelled;
         synchronized (sLoadTasks) {
             if (!sLoadTasks.containsKey(taskId))
                 return false;
 
             Log.d(LOGTAG, "Cancelling favicon load (" + taskId + ")");
 
             LoadFaviconTask task = sLoadTasks.get(taskId);
             cancelled = task.cancel(false);
@@ -158,55 +287,97 @@ public class Favicons {
         // Cancel any pending tasks
         synchronized (sLoadTasks) {
             Set<Integer> taskIds = sLoadTasks.keySet();
             Iterator<Integer> iter = taskIds.iterator();
             while (iter.hasNext()) {
                 int taskId = iter.next();
                 cancelFaviconLoad(taskId);
             }
+            sLoadTasks.clear();
         }
 
         LoadFaviconTask.closeHTTPClient();
     }
 
-    public static boolean isLargeFavicon(Bitmap image) {
-        return image.getWidth() > sFaviconSmallSize || image.getHeight() > sFaviconSmallSize;
-    }
-
-    public static Bitmap scaleImage(Bitmap image) {
-        // If the icon is larger than 16px, scale it to sFaviconLargeSize.
-        // Otherwise, scale it to sFaviconSmallSize.
-        if (isLargeFavicon(image)) {
-            image = Bitmap.createScaledBitmap(image, sFaviconLargeSize, sFaviconLargeSize, false);
-        } else {
-            image = Bitmap.createScaledBitmap(image, sFaviconSmallSize, sFaviconSmallSize, false);
-        }
-        return image;
+    /**
+     * Get the dominant colour of the Favicon at the URL given, if any exists in the cache.
+     *
+     * @param url The URL of the Favicon, to be used as the cache key for the colour value.
+     * @return The dominant colour of the provided Favicon.
+     */
+    public static int getFaviconColor(String url) {
+        return sFaviconsCache.getDominantColor(url);
     }
 
-    public static int getFaviconColor(Bitmap image, String key) {
-        Integer color = sColorCache.get(key);
-        if (color != null) {
-            return color;
+    /**
+     * 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 {
+        sContext = context;
+
+        // Decode the default Favicon ready for use.
+        sDefaultFavicon = BitmapFactory.decodeResource(context.getResources(), R.drawable.favicon);
+        if (sDefaultFavicon == null) {
+            throw new Exception("Null default favicon was returned from the resources system!");
         }
 
-        color = BitmapUtils.getDominantColor(image);
-        sColorCache.put(key, color);
-        return color;
+        sDefaultFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
+        sFaviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size));
     }
 
-    public static void attachToContext(Context context) {
-        sContext = context;
-        if (sFaviconSmallSize < 0) {
-            sFaviconSmallSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_small));
-        }
-        if (sFaviconLargeSize < 0) {
-            sFaviconLargeSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_large));
+    /**
+     * 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.
+     */
+    public static String guessDefaultFaviconURL(String pageURL) {
+        // Special-casing for about: pages. The favicon for about:pages which don't provide a link tag
+        // is bundled in the database, keyed only by page URL, hence the need to return the page URL
+        // here. If the database ever migrates to stop being silly in this way, this can plausibly
+        // be removed.
+        if (pageURL.startsWith("about:") || pageURL.startsWith("jar:")) {
+            return pageURL;
         }
 
-        // Load the default favicon.
-        sDefaultFavicon = BitmapUtils.decodeResource(sContext, R.drawable.favicon);
+        try {
+            // Fall back to trying "someScheme:someDomain.someExtension/favicon.ico".
+            URI u = new URI(pageURL);
+            return new URI(u.getScheme(),
+                           u.getAuthority(),
+                           "/favicon.ico", null,
+                           null).toString();
+        } catch (URISyntaxException e) {
+            return null;
+        }
     }
+
     public static void removeLoadTask(long taskId) {
         sLoadTasks.remove(taskId);
     }
+
+    /**
+     * Method to wrap FaviconCache.isFailedFavicon for use by LoadFaviconTask.
+     *
+     * @param faviconURL Favicon URL to check for failure.
+     */
+    static boolean isFailedFavicon(String faviconURL) {
+        return sFaviconsCache.isFailedFavicon(faviconURL);
+    }
+
+    /**
+     * Sidestep the cache and get, from either the database or the internet, the largest available
+     * Favicon for the given page URL. Useful for creating homescreen shortcuts without being limited
+     * by possibly low-resolution values in the cache.
+     * Deduces the favicon URL from the history database and, ultimately, guesses.
+     *
+     * @param url Page URL to get a large favicon image fro.
+     * @param onFaviconLoadedListener Listener to call back with the result.
+     */
+    public static void getLargestFaviconForPage(String url, OnFaviconLoadedListener onFaviconLoadedListener) {
+        loadUncachedFavicon(url, null, 0, -1, onFaviconLoadedListener);
+    }
 }
--- a/mobile/android/base/favicons/LoadFaviconTask.java
+++ b/mobile/android/base/favicons/LoadFaviconTask.java
@@ -4,130 +4,190 @@
 
 package org.mozilla.gecko.favicons;
 
 
 import android.content.ContentResolver;
 import android.graphics.Bitmap;
 import android.net.http.AndroidHttpClient;
 import android.os.Handler;
+import android.text.TextUtils;
 import android.util.Log;
+import org.apache.http.Header;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.entity.BufferedHttpEntity;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import static org.mozilla.gecko.favicons.Favicons.sContext;
 
+import java.io.IOException;
 import java.io.InputStream;
-import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Class representing the asynchronous task to load a Favicon which is not currently in the in-memory
  * cache.
  * The implementation initially tries to get the Favicon from the database. Upon failure, the icon
  * is loaded from the internet.
  */
 public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
     private static final String LOGTAG = "LoadFaviconTask";
 
+    // Access to this map needs to be synchronized prevent multiple jobs loading the same favicon
+    // from executing concurrently.
+    private static final HashMap<String, LoadFaviconTask> loadsInFlight = new HashMap<String, LoadFaviconTask>();
+
     public static final int FLAG_PERSIST = 1;
     public static final int FLAG_SCALE = 2;
+    private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
 
     private static AtomicInteger mNextFaviconLoadId = new AtomicInteger(0);
     private int mId;
     private String mPageUrl;
     private String mFaviconUrl;
     private OnFaviconLoadedListener mListener;
     private int mFlags;
 
+    private final boolean mOnlyFromLocal;
+
+    // Assuming square favicons, judging by width only is acceptable.
+    protected int mTargetWidth;
+    private final AtomicReference<LoadFaviconTask> mChainee = new AtomicReference<LoadFaviconTask>(null);
+    private boolean mIsChaining;
+
     static AndroidHttpClient sHttpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
 
     public LoadFaviconTask(Handler backgroundThreadHandler,
-                           String aPageUrl, String aFaviconUrl, int aFlags,
-                           OnFaviconLoadedListener aListener) {
+                           String pageUrl, String faviconUrl, int flags,
+                           OnFaviconLoadedListener listener) {
+        this(backgroundThreadHandler, pageUrl, faviconUrl, flags, listener, -1, false);
+    }
+    public LoadFaviconTask(Handler backgroundThreadHandler,
+                           String pageUrl, String faviconUrl, int flags,
+                           OnFaviconLoadedListener aListener, int targetSize, boolean fromLocal) {
         super(backgroundThreadHandler);
 
         mId = mNextFaviconLoadId.incrementAndGet();
 
-        mPageUrl = aPageUrl;
-        mFaviconUrl = aFaviconUrl;
+        mPageUrl = pageUrl;
+        mFaviconUrl = faviconUrl;
         mListener = aListener;
-        mFlags = aFlags;
+        mFlags = flags;
+        mTargetWidth = targetSize;
+        mOnlyFromLocal = fromLocal;
     }
 
     // Runs in background thread
     private Bitmap loadFaviconFromDb() {
         ContentResolver resolver = sContext.getContentResolver();
-        return BrowserDB.getFaviconForUrl(resolver, mPageUrl);
+        return BrowserDB.getFaviconForFaviconUrl(resolver, mFaviconUrl);
     }
 
     // Runs in background thread
     private void saveFaviconToDb(final Bitmap favicon) {
         if ((mFlags & FLAG_PERSIST) == 0) {
             return;
         }
 
         ContentResolver resolver = sContext.getContentResolver();
         BrowserDB.updateFaviconForUrl(resolver, mPageUrl, favicon, mFaviconUrl);
     }
 
+    /**
+     * Helper method for trying the download request to grab a Favicon.
+     * @param faviconURI URL of Favicon to try and download
+     * @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
+     */
+    private HttpResponse tryDownload(URI faviconURI) throws URISyntaxException, IOException {
+        HashSet<String> visitedLinkSet = new HashSet<String>();
+        visitedLinkSet.add(faviconURI.toString());
+        return tryDownloadRecurse(faviconURI, visitedLinkSet);
+    }
+    private HttpResponse tryDownloadRecurse(URI faviconURI, HashSet<String> visited) throws URISyntaxException, IOException {
+        if (visited.size() == MAX_REDIRECTS_TO_FOLLOW) {
+            return null;
+        }
+
+        HttpGet request = new HttpGet(faviconURI);
+        HttpResponse response = sHttpClient.execute(request);
+        if (response == null) {
+            return null;
+        }
+
+        if (response.getStatusLine() != null) {
+
+            // Was the response a failure?
+            int status = response.getStatusLine().getStatusCode();
+
+            // Handle HTTP status codes requesting a redirect.
+            if (status >= 300 && status < 400) {
+                Header header = response.getFirstHeader("Location");
+
+                // Handle mad webservers.
+                if (header == null) {
+                    return null;
+                }
+
+                String newURI = header.getValue();
+                if (newURI == null || newURI.equals(faviconURI.toString())) {
+                    return null;
+                }
+
+                if (visited.contains(newURI)) {
+                    // Already been redirected here - abort.
+                    return null;
+                }
+
+                visited.add(newURI);
+                return tryDownloadRecurse(new URI(newURI), visited);
+            }
+
+            if (status >= 400) {
+                return null;
+            }
+        }
+        return response;
+    }
+
     // Runs in background thread
-    private Bitmap downloadFavicon(URL targetFaviconURL) {
+    private Bitmap downloadFavicon(URI targetFaviconURI) {
         if (mFaviconUrl.startsWith("jar:jar:")) {
             return GeckoJarReader.getBitmap(sContext.getResources(), mFaviconUrl);
         }
 
-        URI uri;
-        try {
-            uri = targetFaviconURL.toURI();
-        } catch (URISyntaxException e) {
-            Log.d(LOGTAG, "Could not get URI for favicon");
+        // only get favicons for HTTP/HTTPS
+        String scheme = targetFaviconURI.getScheme();
+        if (!"http".equals(scheme) && !"https".equals(scheme)) {
             return null;
         }
 
-        // only get favicons for HTTP/HTTPS
-        String scheme = uri.getScheme();
-        if (!"http".equals(scheme) && !"https".equals(scheme))
-            return null;
-
         // skia decoder sometimes returns null; workaround is to use BufferedHttpEntity
         // http://groups.google.com/group/android-developers/browse_thread/thread/171b8bf35dbbed96/c3ec5f45436ceec8?lnk=raot
         Bitmap image = null;
         try {
-            HttpGet request = new HttpGet(targetFaviconURL.toURI());
-            HttpResponse response = sHttpClient.execute(request);
-            if (response == null)
+            // Try the URL we were given.
+            HttpResponse response = tryDownload(targetFaviconURI);
+            if (response == null) {
                 return null;
-            if (response.getStatusLine() != null) {
-                // Was the response a failure?
-                int status = response.getStatusLine().getStatusCode();
-                if (status >= 400) {
-                    Favicons.putFaviconInFailedCache(mPageUrl, Favicons.FAILED_EXPIRY_NEVER);
-                    return null;
-                }
             }
 
             HttpEntity entity = response.getEntity();
-            if (entity == null)
+            if (entity == null) {
                 return null;
-            if (entity.getContentType() != null) {
-                // Is the content type valid? Might be a captive portal.
-                String contentType = entity.getContentType().getValue();
-                if (contentType.indexOf("image") == -1)
-                    return null;
             }
 
             BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
             InputStream contentStream = null;
             try {
                 contentStream = bufferedEntity.getContent();
                 image = BitmapUtils.decodeStream(contentStream);
                 contentStream.close();
@@ -140,79 +200,201 @@ public class LoadFaviconTask extends UiA
             Log.e(LOGTAG, "Error reading favicon", e);
         }
 
         return image;
     }
 
     @Override
     protected Bitmap doInBackground(Void... unused) {
-        Bitmap image;
+        if (isCancelled()) {
+            return null;
+        }
 
-        if (isCancelled())
-            return null;
+        String storedFaviconUrl;
+        boolean isUsingDefaultURL = false;
 
-        URL faviconURLToDownload;
+        // Handle the case of malformed favicon URL.
+        // If favicon is empty, fall back to the stored one.
+        if (TextUtils.isEmpty(mFaviconUrl)) {
+            // Try to get the favicon URL from the memory cache.
+            storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(mPageUrl);
 
-        // Handle the case of malformed favicon URL
-        try {
-            // If favicon is empty, fallback to default favicon URI
-            if (mFaviconUrl == null || mFaviconUrl.length() == 0) {
-                // Handle the case of malformed URL
-                URL targetPageURL = new URL(mPageUrl);
+            // If that failed, try to get the URL from the database.
+            if (storedFaviconUrl == null) {
+                storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
+                if (storedFaviconUrl != null) {
+                    // If that succeeded, cache the URL loaded from the database in memory.
+                    Favicons.putFaviconURLForPageURLInCache(mPageUrl, storedFaviconUrl);
+                }
+            }
 
-                faviconURLToDownload = new URL(targetPageURL.getProtocol(), targetPageURL.getAuthority(), "/favicon.ico");
-                mFaviconUrl = faviconURLToDownload.toString();
+            // If we found a faviconURL - use it.
+            if (storedFaviconUrl != null) {
+                mFaviconUrl = storedFaviconUrl;
             } else {
-                faviconURLToDownload = new URL(mFaviconUrl);
+                // If we don't have a stored one, fall back to the default.
+                mFaviconUrl = Favicons.guessDefaultFaviconURL(mPageUrl);
+                isUsingDefaultURL = true;
             }
-        } catch (MalformedURLException e) {
-            Log.d(LOGTAG, "The provided favicon URL is not valid");
+        }
+
+        // Check if favicon has failed - if so, give up. We need this check because, sometimes, we
+        // didn't know the real Favicon URL until we asked the database.
+        if (Favicons.isFailedFavicon(mFaviconUrl)) {
+            return null;
+        }
+
+        if (isCancelled()) {
             return null;
         }
 
-        if (isCancelled())
-            return null;
+        Bitmap image;
+        // Determine if there is already an ongoing task to fetch the Favicon we desire.
+        // If there is, just join the queue and wait for it to finish. If not, we carry on.
+        synchronized(loadsInFlight) {
+            // Another load of the current Favicon is already underway
+            LoadFaviconTask existingTask = loadsInFlight.get(mFaviconUrl);
+            if (existingTask != null && !existingTask.isCancelled()) {
+                existingTask.chainTasks(this);
+                mIsChaining = true;
+            }
 
-        String storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
-        if (storedFaviconUrl != null && storedFaviconUrl.equals(mFaviconUrl)) {
-            image = loadFaviconFromDb();
-            if (image != null && image.getWidth() > 0 && image.getHeight() > 0)
-                return ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
+            // Replace the element in the map with the end of the chain, so a subsequent redundant
+            // request created during the lifetime of this one will also get the result sent down -
+            // without the need to chaining calls to chainTasks.
+            loadsInFlight.put(mFaviconUrl, this);
+        }
+
+        if (mIsChaining) {
+            // If we're chaining, abort.
+            return null;
         }
 
-        if (isCancelled())
+        if (isCancelled()) {
+            return null;
+        }
+
+        image = loadFaviconFromDb();
+        if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
+            return image;
+        }
+
+        if (mOnlyFromLocal || isCancelled()) {
             return null;
+        }
 
-        image = downloadFavicon(faviconURLToDownload);
+        try {
+            image = downloadFavicon(new URI(mFaviconUrl));
+        } catch (URISyntaxException e) {
+            Log.e(LOGTAG, "The provided favicon URL is not valid");
+            return null;
+        }
+
+        // If we're not already trying the default URL, try it now.
+        if (image == null && !isUsingDefaultURL) {
+            try {
+                image = downloadFavicon(new URI(Favicons.guessDefaultFaviconURL(mPageUrl)));
+            } catch (URISyntaxException e){
+                // Not interesting. It was an educated guess, anyway.
+            }
+        }
 
         if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
             saveFaviconToDb(image);
-            image = ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
         } else {
-            image = null;
+            Favicons.putFaviconInFailedCache(mFaviconUrl);
         }
 
         return image;
     }
 
     @Override
-    protected void onPostExecute(final Bitmap image) {
+    protected void onPostExecute(Bitmap image) {
+        if (mIsChaining) {
+            return;
+        }
+
+        // Put what we got in the memcache.
+        Favicons.putFaviconInMemCache(mFaviconUrl, image);
+
+        // Process the result - scale for the listener, chain if required.
+        processResult(image);
+    }
+
+    private void processResult(Bitmap image) {
         Favicons.removeLoadTask(mId);
-        Favicons.dispatchResult(mPageUrl, image, mListener);
+
+        Bitmap scaled = image;
+
+        // Notify listeners, scaling if required.
+        if (mTargetWidth != -1 && image != null &&  image.getWidth() != mTargetWidth) {
+            scaled = Favicons.getSizedFaviconFromCache(mFaviconUrl, mTargetWidth);
+        }
+
+        Favicons.dispatchResult(mPageUrl, mFaviconUrl, scaled, mListener);
+
+        // Take a snapshot of the chainee reference.
+        final LoadFaviconTask chainee = mChainee.get();
+
+        if (chainee != null) {
+            // Propagate the result along the chain.
+            // Note that we don't pass the scaled image -- the chainee might not want
+            // the same size that this task's listener does.
+            chainee.processResult(image);
+            mChainee.set(null);
+            return;
+        }
+
+        // If we find we had no chainee set, enter the monitor (Which is required to update the
+        // mChainee reference) and check again. If we're still lacking a chainee, remove the
+        // reference from loadsInFlight. Deals with chain growth racing with this call.
+        synchronized(loadsInFlight) {
+            if (mChainee.get() == null) {
+                loadsInFlight.remove(mFaviconUrl);
+                return;
+            }
+        }
+
+        // Another element was added to the chain while we weren't looking...
+        mChainee.get().processResult(image);
     }
 
     @Override
     protected void onCancelled() {
         Favicons.removeLoadTask(mId);
 
+        synchronized(loadsInFlight) {
+            if (mChainee.get() == null) {
+                loadsInFlight.remove(mFaviconUrl);
+            }
+        }
+
         // Note that we don't call the listener callback if the
         // favicon load is cancelled.
     }
 
+    /**
+     * When the result of this job is ready, also notify the chainee of the result.
+     * Used for aggregating concurrent requests for the same Favicon into a single actual request.
+     * (Don't want to download a hundred instances of Google's Favicon at once, for example).
+     *
+     * @param aChainee LoadFaviconTask
+     */
+    private void chainTasks(LoadFaviconTask aChainee) {
+        // Atomically update mChainee if it's null.
+        if (mChainee.compareAndSet(null, aChainee)) {
+            return;
+        }
+
+        // chainResults is only called within a synchronized block on loadsInFlight - so the value
+        // can't have changed since the CAS above.
+        mChainee.get().chainTasks(aChainee);
+    }
+
     int getId() {
         return mId;
     }
 
     static void closeHTTPClient() {
         // This work must be done on a background thread because it shuts down
         // the connection pool, which typically involves closing a connection --
         // which counts as network activity.
--- a/mobile/android/base/favicons/OnFaviconLoadedListener.java
+++ b/mobile/android/base/favicons/OnFaviconLoadedListener.java
@@ -5,10 +5,10 @@
 package org.mozilla.gecko.favicons;
 
 import android.graphics.Bitmap;
 
 /**
  * Interface to be implemented by objects wishing to listen for favicon load completion events.
  */
 public interface OnFaviconLoadedListener {
-    void onFaviconLoaded(String url, Bitmap favicon);
+    void onFaviconLoaded(String url, String faviconURL, Bitmap favicon);
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/favicons/cache/FaviconCache.java
@@ -0,0 +1,636 @@
+/* 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.cache;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import org.mozilla.gecko.favicons.Favicons;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 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.
+ *
+ * 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
+ * for all favicons served from a particular favicon URL shall be the same is made. (To violate this
+ * would mandate serving an ICO or similar file with multiple radically different images in it - an
+ * ill-advised and extremely uncommon use-case, for sure.)
+ * The dominant colour information is updated as the element is being added to the cache - typically
+ * on the background thread.
+ * Also present here are the download timestamp and isFailed flag. Upon failure, the flag is set.
+ * A constant exists in this file to specify the maximum time permitted between failures before
+ * a retry is again permitted.
+ *
+ * TODO: Expiry of Favicons from the favicon database cache is not implemented. (Bug 914296)
+ *
+ * A typical request to the cache will consist of a Favicon URL and a target size. The FaviconsForURL
+ * object for that URL will be obtained, queried for a favicon matching exactly the needed size, and
+ * if successful, the result is returned.
+ * If unsuccessful, the object is asked to return the smallest available primary favicon larger than
+ * the target size. If this step works, the result is downscaled to create a new secondary favicon,
+ * which is then stored (So subsequent requests will succeed at the first step) and returned.
+ * If that step fails, the object finally walks backwards through its sequence of favicons until it
+ * finds the largest primary favicon smaller than the target. This is then upscaled by a maximum of
+ * 2x towards the target size, and the result cached and returned as above.
+ *
+ * The bitmaps themselves are encapsulated inside FaviconCacheElement objects. These objects contain,
+ * as well as the bitmap, a pointer to the encapsulating FaviconsForURL object (Used by the LRU
+ * culler), the size of the encapsulated image, a flag indicating if this is a primary favicon, and
+ * a flag indicating if the entry is invalid.
+ * All FaviconCacheElement objects are tracked in the mOrdering LinkedList. This is used to record
+ * LRU information about FaviconCacheElements. In particular, the most recently used FaviconCacheElement
+ * will be at the start of the list, the least recently used at the end of the list.
+ *
+ * When the cache runs out of space, it removes FaviconCacheElements starting from the end of the list
+ * until a sufficient amount of space has been freed.
+ * When a secondary favicon is removed in this way, it is simply deleted from its parent FaviconsForURLs
+ * object's list of available favicons.
+ * The backpointer field on the FaviconCacheElement is used to remove the element from the encapsulating
+ * FaviconsForURL object, when this is required.
+ * When a primary favicon is removed, its invalid flag is set to true and its bitmap payload is set
+ * to null (So it is available for freeing by the garbage collector). This reduces the memory footprint
+ * of the icon to essentially zero, but keeps track of which primary favicons exist for this favicon
+ * URL.
+ * If a subsequent request comes in for that favicon URL, it is then known that a primary of those
+ * dimensions is available, just that it is not in the cache. The system is then able to load the
+ * primary back into the cache from the database (Where the entirety of the initially encapsulating
+ * container-formatted image file is stored).
+ * If this were not done, then when processing requests after the culling of primary favicons it would
+ * be impossible to distinguish between the nonexistence of a primary and the nonexistence of a primary
+ * in the cache without querying the database.
+ *
+ * The implementation is safe to use from multiple threads and, while is it not entirely strongly
+ * consistent all of the time, you almost certainly don't care.
+ * The thread-safety implementation used is approximately MRSW with semaphores. An extra semaphore
+ * is used to grant mutual exclusion over reordering operations from reader threads (Who thus gain
+ * a quasi-writer status to do such limited mutation as is necessary).
+ *
+ * Reads which race with writes are liable to not see the ongoing write. The cache may return a
+ * stale or now-removed value to the caller. Returned values are never invalid, even in the face
+ * of concurrent reading and culling.
+ */
+public class FaviconCache {
+    private static final String LOGTAG = "FaviconCache";
+
+    // The number of spaces to allocate for favicons in each node.
+    private static final int NUM_FAVICON_SIZES = 4;
+
+    // Dimensions of the largest favicon to store in the cache. Everything is downscaled to this.
+    public final int mMaxCachedWidth;
+
+    // Retry failed favicons after 20 minutes.
+    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>();
+
+    // 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.
+    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);
+
+    // Used to ensure transaction fairness - each txn acquires and releases this as the first operation.
+    // The effect is an orderly, inexpensive ordering enforced on txns to prevent writer starvation.
+    private final Semaphore mTurnSemaphore = new Semaphore(1);
+
+    // A deviation from the usual MRSW solution - this semaphore is used to guard modification to the
+    // ordering map. This allows for read transactions to update the most-recently-used value without
+    // needing to take out the write lock.
+    private final Semaphore mReorderingSemaphore = new Semaphore(1);
+
+    // The semaphore one must acquire in order to perform a write.
+    private final Semaphore mWriteLock = new Semaphore(1);
+
+    /**
+     * Called by txns performing only reads as they start. Prevents writer starvation with a turn
+     * semaphore and locks writers out if this is the first concurrent reader txn starting up.
+     */
+    private void startRead() {
+        mTurnSemaphore.acquireUninterruptibly();
+        mTurnSemaphore.release();
+
+        if (mOngoingReads.incrementAndGet() == 1) {
+            // First one in. Wait for writers to finish and lock them out.
+            mWriteLock.acquireUninterruptibly();
+        }
+    }
+
+    /**
+     * An alternative to startWrite to be used when in a read transaction and wanting to upgrade it
+     * to a write transaction. Such a transaction should be terminated with finishWrite.
+     */
+    private void upgradeReadToWrite() {
+        mTurnSemaphore.acquireUninterruptibly();
+        if (mOngoingReads.decrementAndGet() == 0) {
+            mWriteLock.release();
+        }
+        mWriteLock.acquireUninterruptibly();
+    }
+
+    /**
+     * Called by transactions performing only reads as they finish. Ensures that if this is the last
+     * concluding read transaction then then writers are subsequently allowed in.
+     */
+    private void finishRead() {
+        if (mOngoingReads.decrementAndGet() == 0) {
+            mWriteLock.release();
+        }
+    }
+
+    /**
+     * Called by writer transactions upon start. Ensures fairness and then obtains the write lock.
+     * Upon return, no other txns will be executing concurrently.
+     */
+    private void startWrite() {
+        mTurnSemaphore.acquireUninterruptibly();
+        mWriteLock.acquireUninterruptibly();
+    }
+
+    /**
+     * Called by a concluding write transaction - unlocks the structure.
+     */
+    private void finishWrite() {
+        mTurnSemaphore.release();
+        mWriteLock.release();
+    }
+
+    public FaviconCache(int maxSize, int maxWidthToCache) {
+        mMaxSizeBytes = maxSize;
+        mMaxCachedWidth = maxWidthToCache;
+    }
+
+    /**
+     * Determine if the provided favicon URL is marked as a failure (Has failed to load before -
+     * such icons get blacklisted for a time to prevent us endlessly retrying.)
+     *
+     * @param faviconURL Favicon URL to check if failed in memcache.
+     * @return true if this favicon is blacklisted, false otherwise.
+     */
+    public boolean isFailedFavicon(String faviconURL) {
+        startRead();
+
+        boolean isExpired = false;
+        boolean isAborting = false;
+
+        try {
+            // If we don't have it in the cache, it certainly isn't a known failure.
+            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) {
+                return false;
+            }
+
+            final long failureTimestamp = container.mDownloadTimestamp;
+
+            // Calculate elapsed time since the failing download.
+            final long failureDiff = System.currentTimeMillis() - failureTimestamp;
+
+            // If long enough has passed, mark it as no longer a failure.
+            if (failureDiff > FAILURE_RETRY_MILLISECONDS) {
+                isExpired = true;
+            } else {
+                return true;
+            }
+        } catch (Exception unhandled) {
+            // Handle any exception thrown and return the locks to a sensible state.
+            finishRead();
+
+            // Flag to prevent finally from doubly-unlocking.
+            isAborting = true;
+            Log.e(LOGTAG, "FaviconCache exception!", unhandled);
+            return false;
+        }  finally {
+            if (!isAborting) {
+                if (isExpired) {
+                    // No longer expired.
+                    upgradeReadToWrite();
+                } else {
+                    finishRead();
+                }
+            }
+        }
+
+        try {
+            recordRemoved(mBackingMap.get(faviconURL));
+            mBackingMap.remove(faviconURL);
+            return false;
+        } finally {
+            finishWrite();
+        }
+    }
+
+    /**
+     * Mark the indicated page URL as a failed Favicon until the provided time.
+     *
+     * @param faviconURL Page URL for which a Favicon load has failed.
+     */
+    public void putFailed(String faviconURL) {
+        startWrite();
+
+        if (mBackingMap.containsKey(faviconURL)) {
+            recordRemoved(mBackingMap.get(faviconURL));
+        }
+
+        FaviconsForURL container = new FaviconsForURL(0, true);
+        mBackingMap.put(faviconURL, container);
+
+        finishWrite();
+    }
+
+    /**
+     * Fetch a Favicon for the given URL as close as possible to the size provided.
+     * If an icon of the given size is already in the cache, it is returned.
+     * If an icon of the given size is not in the cache but a larger unscaled image does exist in
+     * the cache, we downscale the larger image to the target size and cache the result.
+     * If there is no image of the required size, null is returned.
+     *
+     * @param faviconURL The URL for which a Favicon is desired. Must not be null.
+     * @param targetSize The size of the desired favicon.
+     * @return A favicon of the requested size for the requested URL, or null if none cached.
+     */
+    public Bitmap getFaviconForDimensions(String faviconURL, int targetSize) {
+        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;
+        final Bitmap newBitmap;
+        final FaviconsForURL container;
+
+        startRead();
+
+        try {
+            if (!mBackingMap.containsKey(faviconURL)) {
+                return null;
+            }
+
+            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.
+                cacheElement = container.mFavicons.get(cacheElementIndex);
+
+                if (cacheElement.mInvalidated) {
+                    return null;
+                }
+
+                // If we found exactly what we wanted - we're done.
+                if (cacheElement.mImageSize == targetSize) {
+                    setMostRecentlyUsed(cacheElement);
+                    return cacheElement.mFaviconPayload;
+                }
+            } else {
+                // We requested an image larger than all primaries. Set the element to start the search
+                // from to the element beyond the end of the array, so the search runs backwards.
+                cacheElementIndex = container.mFavicons.size();
+            }
+
+            // We did not find exactly what we wanted, but now have set cacheElementIndex to the index
+            // where what we want should live in the list. We now request the next least larger primary
+            // from the cache. We will downscale this to our target size.
+
+            // If there is no such primary, we'll upscale the next least smaller one instead.
+            cacheElement = container.getNextPrimary(cacheElementIndex);
+
+
+            if (cacheElement == null) {
+                // The primary has been invalidated! Fail! Need to get it back from the database.
+                return null;
+            }
+
+            // Having got this far, we'll be needing to write the new secondary to the cache, which
+            // involves us falling through to the next try block. This flag lets us do this (Other
+            // paths prior to this end in returns.)
+            doingWrites = true;
+
+            // Scaling logic...
+            Bitmap largestElementBitmap = cacheElement.mFaviconPayload;
+            int largestSize = cacheElement.mImageSize;
+
+            if (largestSize >= targetSize) {
+                // The largest we have is larger than the target - downsize to target.
+                newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, targetSize, targetSize, true);
+            } else {
+                // Our largest primary is smaller than the desired size. Upscale by a maximum of 2x.
+                // largestSize now reflects the maximum size we can upscale to.
+                largestSize *= 2;
+
+                if (largestSize >= targetSize) {
+                    // Perfect! We can upscale by less than 2x and reach the needed size. Do it.
+                    newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, targetSize, targetSize, true);
+                } else {
+                    // We don't have enough information to make the target size look nonterrible. Best effort:
+                    newBitmap = Bitmap.createScaledBitmap(largestElementBitmap, largestSize, largestSize, true);
+
+                    shouldComputeColour = true;
+                }
+            }
+        } catch (Exception unhandled) {
+            isAborting = true;
+
+            // Handle any exception thrown and return the locks to a sensible state.
+            finishRead();
+
+            // Flag to prevent finally from doubly-unlocking.
+            Log.e(LOGTAG, "FaviconCache exception!", unhandled);
+            return null;
+        } finally {
+            if (!isAborting) {
+                if (doingWrites) {
+                    upgradeReadToWrite();
+                } else {
+                    finishRead();
+                }
+            }
+        }
+
+        try {
+            if (shouldComputeColour) {
+                // And since we failed, we'll need the dominant colour.
+                container.ensureDominantColor();
+            }
+
+            // 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());
+        } finally {
+            finishWrite();
+        }
+
+        return newBitmap;
+    }
+
+    /**
+     * Query the cache for the dominant colour stored for the Favicon URL provided, if any.
+     *
+     * @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)) {
+                Log.w(LOGTAG, "Cannot compute dominant color of non-cached favicon " + key);
+                finishRead();
+                return 0xFFFFFF;
+            }
+
+            FaviconsForURL element = mBackingMap.get(key);
+
+            return element.ensureDominantColor();
+        } finally {
+            finishRead();
+        }
+    }
+
+    /**
+     * Remove all payloads stored in the given container from the LRU cache. Must be called while
+     * holding the write lock.
+     *
+     * @param wasRemoved The container to purge from the cache.
+     */
+    private void recordRemoved(FaviconsForURL wasRemoved) {
+        // If there was an existing value, strip it from the insertion-order cache.
+        if (wasRemoved == null) {
+            return;
+        }
+
+        int sizeRemoved = 0;
+
+        for (FaviconCacheElement e : wasRemoved.mFavicons) {
+            sizeRemoved += e.sizeOf();
+            mOrdering.remove(e);
+        }
+
+        mCurrentSize.addAndGet(-sizeRemoved);
+    }
+
+    private Bitmap produceCacheableBitmap(Bitmap favicon) {
+        // Never cache the default Favicon, or the null Favicon.
+        if (favicon == Favicons.sDefaultFavicon || favicon == null) {
+            return null;
+        }
+
+        // Some sites serve up insanely huge Favicons (Seen 512x512 ones...)
+        // While we want to cache nice big icons, we apply a limit based on screen density for the
+        // sake of space.
+        if (favicon.getWidth() > mMaxCachedWidth) {
+            return Bitmap.createScaledBitmap(favicon, mMaxCachedWidth, mMaxCachedWidth, true);
+        }
+        return favicon;
+    }
+
+    /**
+     * Set an existing element as the most recently used element. May be called from either type of
+     * transaction.
+     *
+     * @param element The element that is to become the most recently used one.
+     */
+    private void setMostRecentlyUsed(FaviconCacheElement element) {
+        mReorderingSemaphore.acquireUninterruptibly();
+        mOrdering.remove(element);
+        mOrdering.offer(element);
+        mReorderingSemaphore.release();
+    }
+
+    /**
+     * Add the provided bitmap to the cache as the only available primary for this URL.
+     * Should never be called with scaled Favicons. The input is assumed to be an unscaled Favicon.
+     *
+     * @param faviconURL The URL of the Favicon being stored.
+     * @param aFavicon The Favicon to store.
+     */
+    public void putSingleFavicon(String faviconURL, Bitmap aFavicon) {
+        Bitmap favicon = produceCacheableBitmap(aFavicon);
+        if (favicon == null) {
+            return;
+        }
+
+        // Create a fresh container for the favicons associated with this URL. Allocate extra slots
+        // in the underlying ArrayList in case multiple secondary favicons are later created.
+        // Currently set to the number of favicon sizes used in the UI, plus 1, at time of writing.
+        // Ought to be  tuned as things change for maximal performance.
+        FaviconsForURL toInsert = new FaviconsForURL(NUM_FAVICON_SIZES);
+
+        // Create the cache element for the single element we are inserting, and configure it.
+        FaviconCacheElement newElement = toInsert.addPrimary(favicon);
+
+        startWrite();
+        try {
+            // Set the new element as the most recently used one.
+            setMostRecentlyUsed(newElement);
+
+            mCurrentSize.addAndGet(newElement.sizeOf());
+
+            // Update the value in the LruCache...
+            FaviconsForURL wasRemoved;
+            wasRemoved = mBackingMap.put(faviconURL, toInsert);
+
+            recordRemoved(wasRemoved);
+        } finally {
+            finishWrite();
+        }
+
+        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.
+     */
+    public void putFavicons(String faviconURL, Iterator<Bitmap> favicons) {
+        // 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;
+            }
+
+            FaviconCacheElement newElement = toInsert.addPrimary(favicon);
+            sizeGained += newElement.sizeOf();
+        }
+
+        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);
+            }
+        } 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);
+
+            // Update the value in the LruCache...
+            recordRemoved(mBackingMap.put(faviconURL, toInsert));
+        } finally {
+            finishWrite();
+        }
+
+        cullIfRequired();
+    }
+
+    /**
+     * If cache too large, drop stuff from the cache to get the size back into the acceptable range.
+     * Otherwise, do nothing.
+     */
+    private void cullIfRequired() {
+        Log.d(LOGTAG, "Favicon cache fullness: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
+
+        if (mCurrentSize.get() <= mMaxSizeBytes) {
+            return;
+        }
+
+        startWrite();
+        try {
+            while (mCurrentSize.get() > mMaxSizeBytes) {
+                // Cull the least recently used element.
+
+                FaviconCacheElement victim;
+                victim = mOrdering.poll();
+
+                mCurrentSize.addAndGet(-victim.sizeOf());
+                victim.onEvictedFromCache();
+
+                Log.d(LOGTAG, "After cull: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
+            }
+        } finally {
+            finishWrite();
+        }
+    }
+
+    /**
+     * Purge all elements from the FaviconCache. Handy if you want to reclaim some memory.
+     */
+    public void evictAll() {
+        startWrite();
+
+        try {
+            mBackingMap.clear();
+            mOrdering.clear();
+        } finally {
+            finishWrite();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/favicons/cache/FaviconCacheElement.java
@@ -0,0 +1,115 @@
+/* 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.cache;
+
+import android.graphics.Bitmap;
+
+/**
+ * Objects stored in the Favicon cache - allow for the bitmap to be tagged to indicate if it has
+ * been scaled. Unscaled bitmaps are not included in the scaled-bitmap cache's size calculation.
+ */
+public class FaviconCacheElement implements Comparable<FaviconCacheElement> {
+    // Was this Favicon computed via scaling another primary Favicon, or is this a primary Favicon?
+    final boolean mIsPrimary;
+
+    // The Favicon bitmap.
+    Bitmap mFaviconPayload;
+
+    // If set, mFaviconPayload is absent. Since the underlying ICO may contain multiple primary
+    // payloads, primary payloads are never truly deleted from the cache, but instead have their
+    // payload deleted and this flag set on their FaviconCacheElement. That way, the cache always
+    // has a record of the existence of a primary payload, even if it is no longer in the cache.
+    // This means that when a request comes in that will be best served using a primary that is in
+    // the database but no longer cached, we know that it exists and can go get it (Useful when ICO
+    // support is added).
+    volatile boolean mInvalidated;
+
+    final int mImageSize;
+
+    // Used for LRU pruning.
+    final FaviconsForURL mBackpointer;
+
+    public FaviconCacheElement(Bitmap payload, boolean isPrimary, int imageSize, FaviconsForURL backpointer) {
+        mFaviconPayload = payload;
+        mIsPrimary = isPrimary;
+        mImageSize = imageSize;
+        mBackpointer = backpointer;
+    }
+
+    public FaviconCacheElement(Bitmap payload, boolean isPrimary, FaviconsForURL backpointer) {
+        mFaviconPayload = payload;
+        mIsPrimary = isPrimary;
+        mBackpointer = backpointer;
+
+        if (payload != null) {
+            mImageSize = payload.getWidth();
+        } else {
+            mImageSize = 0;
+        }
+    }
+
+    public int sizeOf() {
+        if (mInvalidated) {
+            return 0;
+        }
+        return mFaviconPayload.getRowBytes() * mFaviconPayload.getHeight();
+    }
+
+    /**
+     * Establish an ordering on FaviconCacheElements based on size and validity. An element is
+     * considered "greater than" another if it is valid and the other is not, or if it contains a
+     * larger payload.
+     *
+     * @param another The FaviconCacheElement to compare to this one.
+     * @return -1 if this element is less than the given one, 1 if the other one is larger than this
+     *         and 0 if both are of equal value.
+     */
+    @Override
+    public int compareTo(FaviconCacheElement another) {
+        if (mInvalidated && !another.mInvalidated) {
+            return -1;
+        }
+
+        if (!mInvalidated && another.mInvalidated) {
+            return 1;
+        }
+
+        if (mInvalidated) {
+            return 0;
+        }
+
+        final int w1 = mImageSize;
+        final int w2 = another.mImageSize;
+        if (w1 > w2) {
+            return 1;
+        } else if (w2 > w1) {
+            return -1;
+        }
+        return 0;
+    }
+
+    /**
+     * Called when this element is evicted from the cache.
+     *
+     * If primary, drop the payload and set invalid. If secondary, just unlink from parent node.
+     */
+    public void onEvictedFromCache() {
+        if (mIsPrimary) {
+            // So we keep a record of which primaries exist in the database for this URL, we
+            // don't actually delete the entry for primaries. Instead, we delete their payload
+            // and flag them as invalid. This way, we can later figure out that what a request
+            // really want is one of the primaries that have been dropped from the cache, and we
+            // can go get it.
+            mInvalidated = true;
+            mFaviconPayload = null;
+        } else {
+            // Secondaries don't matter - just delete them.
+            if (mBackpointer == null) {
+                return;
+            }
+            mBackpointer.mFavicons.remove(this);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/favicons/cache/FaviconsForURL.java
@@ -0,0 +1,146 @@
+/* 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.cache;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+import org.mozilla.gecko.gfx.BitmapUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class FaviconsForURL {
+    private static final String LOGTAG = "FaviconForURL";
+
+    private volatile int mDominantColor = -1;
+
+    final long mDownloadTimestamp;
+    final ArrayList<FaviconCacheElement> mFavicons;
+
+    public final boolean mHasFailed;
+
+    public FaviconsForURL(int size) {
+        this(size, false);
+    }
+
+    public FaviconsForURL(int size, boolean hasFailed) {
+        mHasFailed = hasFailed;
+        mDownloadTimestamp = System.currentTimeMillis();
+        mFavicons = new ArrayList<FaviconCacheElement>(size);
+    }
+
+    public FaviconCacheElement addSecondary(Bitmap favicon, int imageSize) {
+        return addInternal(favicon, false, imageSize);
+    }
+
+    public FaviconCacheElement addPrimary(Bitmap favicon) {
+        return addInternal(favicon, true, favicon.getWidth());
+    }
+
+    private FaviconCacheElement addInternal(Bitmap favicon, boolean isPrimary, int imageSize) {
+        FaviconCacheElement c = new FaviconCacheElement(favicon, isPrimary, imageSize, this);
+
+        int index = Collections.binarySearch(mFavicons, c);
+        if (index < 0) {
+            index = 0;
+        }
+        mFavicons.add(index, c);
+
+        return c;
+    }
+
+    /**
+     * Get the index of the smallest image in this collection larger than or equal to
+     * the given target size.
+     *
+     * @param targetSize Minimum size for the desired result.
+     * @return The index of the smallest image larger than the target size, or -1 if none exists.
+     */
+    public int getNextHighestIndex(int targetSize) {
+        // Create a dummy object to hold the target value for comparable.
+        FaviconCacheElement dummy = new FaviconCacheElement(null, false, targetSize, null);
+
+        int index = Collections.binarySearch(mFavicons, dummy);
+
+        // The search routine returns the index of an element equal to dummy, if present.
+        // Otherwise, it returns -x - 1, where x is the index in the ArrayList where dummy would be
+        // inserted if the list were to remain sorted.
+        if (index < 0) {
+            index++;
+            index = -index;
+        }
+
+        // index is now 'x', as described above.
+
+        // The routine will return mFavicons.size() as the index iff dummy is larger than all elements
+        // present (So the "index at which it should be inserted" is the index after the end.
+        // In this case, we set the sentinel value -1 to indicate that we just requested something
+        // larger than all primaries.
+        if (index == mFavicons.size()) {
+            index = -1;
+        }
+
+        return index;
+    }
+
+    /**
+     * Get the next valid primary icon from this collection, starting at the given index.
+     * If the appropriate icon is found, but is invalid, we return null - the proper response is to
+     * reacquire the primary from the database.
+     * If no icon is found, the search is repeated going backwards from the start index to find any
+     * primary at all (The input index may be a secondary which is larger than the actual available
+     * primary.)
+     *
+     * @param fromIndex The index into mFavicons from which to start the search.
+     * @return The FaviconCacheElement of the next valid primary from the given index. If none exists,
+     *         then returns the previous valid primary. If none exists, returns null (Insanity.).
+     */
+    public FaviconCacheElement getNextPrimary(final int fromIndex) {
+        final int numIcons = mFavicons.size();
+
+        int searchIndex = fromIndex;
+        while (searchIndex < numIcons) {
+            FaviconCacheElement element = mFavicons.get(searchIndex);
+
+            if (element.mIsPrimary) {
+                if (element.mInvalidated) {
+                    // TODO: Replace with `return null` when ICO decoder is introduced.
+                    break;
+                }
+                return element;
+            }
+            searchIndex++;
+        }
+
+        // No larger primary available. Let's look for smaller ones...
+        searchIndex = fromIndex - 1;
+        while (searchIndex >= 0) {
+            FaviconCacheElement element = mFavicons.get(searchIndex);
+
+            if (element.mIsPrimary) {
+                if (element.mInvalidated) {
+                    return null;
+                }
+                return element;
+            }
+            searchIndex--;
+        }
+
+        Log.e(LOGTAG, "No primaries found in Favicon cache structure. This is madness!");
+
+        return null;
+    }
+
+    /**
+     * Ensure the dominant colour field is populated for this favicon.
+     */
+    public int ensureDominantColor() {
+        if (mDominantColor == -1) {
+            mDominantColor = BitmapUtils.getDominantColor(getNextPrimary(0).mFaviconPayload);
+        }
+
+        return mDominantColor;
+    }
+}
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -411,17 +411,17 @@ public class GeckoLayerClient implements
             mDisplayPort = DisplayPortCalculator.calculate(getViewportMetrics(), null);
         }
         return mDisplayPort;
     }
 
     /* This is invoked by JNI on the gecko thread */
     DisplayPortMetrics getDisplayPort(boolean pageSizeUpdate, boolean isBrowserContentDisplayed, int tabId, ImmutableViewportMetrics metrics) {
         Tabs tabs = Tabs.getInstance();
-        if (tabs.isSelectedTab(tabs.getTab(tabId)) && isBrowserContentDisplayed) {
+        if (isBrowserContentDisplayed && tabs.isSelectedTabId(tabId)) {
             // for foreground tabs, send the viewport update unless the document
             // displayed is different from the content document. In that case, just
             // calculate the display port.
             return handleViewportMessage(metrics, pageSizeUpdate ? ViewportMessageType.PAGE_SIZE : ViewportMessageType.UPDATE);
         } else {
             // for background tabs, request a new display port calculation, so that
             // when we do switch to that tab, we have the correct display port and
             // don't need to draw twice (once to allow the first-paint viewport to
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -1,38 +1,28 @@
 /* -*- 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.home;
 
-import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.URLColumns;
-import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
-import android.graphics.Bitmap;
 import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
 public class BookmarksPage extends HomeFragment {
--- a/mobile/android/base/home/HomeFragment.java
+++ b/mobile/android/base/home/HomeFragment.java
@@ -11,17 +11,16 @@ import org.mozilla.gecko.favicons.OnFavi
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.ReaderModeUtils;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -109,17 +108,17 @@ abstract class HomeFragment extends Frag
         // menu item selection handling, it's better to avoid menu id collisions
         // between the activity and its fragments.
 
         ContextMenuInfo menuInfo = item.getMenuInfo();
         if (menuInfo == null || !(menuInfo instanceof HomeContextMenuInfo)) {
             return false;
         }
 
-        HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
+        final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
         final Context context = getActivity().getApplicationContext();
 
         final int itemId = item.getItemId();
         if (itemId == R.id.home_share) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't share because URL is null");
             } else {
                 GeckoAppShell.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
@@ -128,17 +127,24 @@ abstract class HomeFragment extends Frag
         }
 
         if (itemId == R.id.home_add_to_launcher) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't add to home screen because URL is null");
                 return false;
             }
 
-            new AddToLauncherTask(info.url, info.getDisplayTitle()).execute();
+            // Fetch the largest cacheable icon size.
+            Favicons.getLargestFaviconForPage(info.url, new OnFaviconLoadedListener() {
+                @Override
+                public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
+                    GeckoAppShell.createShortcut(info.getDisplayTitle(), info.url, favicon, "");
+                }
+            });
+
             return true;
         }
 
         if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) {
             if (info.url == null) {
                 Log.e(LOGTAG, "Can't open in new tab because URL is null");
                 return false;
             }
@@ -214,45 +220,16 @@ abstract class HomeFragment extends Frag
         }
 
         if (!mIsLoaded) {
             load();
             mIsLoaded = true;
         }
     }
 
-    private static class AddToLauncherTask extends UiAsyncTask<Void, Void, String> {
-        private final String mUrl;
-        private final String mTitle;
-
-        public AddToLauncherTask(String url, String title) {
-            super(ThreadUtils.getBackgroundHandler());
-
-            mUrl = url;
-            mTitle = title;
-        }
-
-        @Override
-        public String doInBackground(Void... params) {
-            return Favicons.getFaviconUrlForPageUrl(mUrl);
-        }
-
-        @Override
-        public void onPostExecute(String faviconUrl) {
-            OnFaviconLoadedListener listener = new OnFaviconLoadedListener() {
-                @Override
-                public void onFaviconLoaded(String url, Bitmap favicon) {
-                    GeckoAppShell.createShortcut(mTitle, mUrl, favicon, "");
-                }
-            };
-
-            Favicons.loadFavicon(mUrl, faviconUrl, 0, listener);
-        }
-    }
-
     private static class RemoveBookmarkTask extends UiAsyncTask<Void, Void, Void> {
         private final Context mContext;
         private final int mId;
         private final String mUrl;
         private final boolean mInReadingList;
 
         public RemoveBookmarkTask(Context context, int id, String url, boolean inReadingList) {
             super(ThreadUtils.getBackgroundHandler());
--- a/mobile/android/base/home/TopSitesGridItemView.java
+++ b/mobile/android/base/home/TopSitesGridItemView.java
@@ -32,22 +32,26 @@ public class TopSitesGridItemView extend
 
     // Child views.
     private final TextView mTitleView;
     private final ImageView mThumbnailView;
 
     // Data backing this view.
     private String mTitle;
     private String mUrl;
+    private String mFaviconURL;
+
+    private Bitmap mThumbnail;
 
     // Pinned state.
     private boolean mIsPinned = false;
 
     // Empty state.
     private boolean mIsEmpty = true;
+    private int mLoadId = Favicons.NOT_LOADING;
 
     public TopSitesGridItemView(Context context) {
         this(context, null);
     }
 
     public TopSitesGridItemView(Context context, AttributeSet attrs) {
         this(context, attrs, R.attr.topSitesGridItemViewStyle);
     }
@@ -145,37 +149,50 @@ public class TopSitesGridItemView extend
      * @param thumbnail The bitmap to show as thumbnail.
      */
     public void displayThumbnail(Bitmap thumbnail) {
         if (thumbnail == null) {
             // Show a favicon based view instead.
             displayThumbnail(R.drawable.favicon);
             return;
         }
+        mThumbnail = thumbnail;
+        Favicons.cancelFaviconLoad(mLoadId);
 
         mThumbnailView.setScaleType(ScaleType.CENTER_CROP);
         mThumbnailView.setImageBitmap(thumbnail);
         mThumbnailView.setBackgroundDrawable(null);
     }
 
     /**
      * Display the thumbnail from a favicon.
      *
      * @param favicon The favicon to show as thumbnail.
      */
-    public void displayFavicon(Bitmap favicon) {
+    public void displayFavicon(Bitmap favicon, String faviconURL) {
+        if (mThumbnail != null) {
+            return;
+        }
+
         if (favicon == null) {
             // Should show default favicon.
             displayThumbnail(R.drawable.favicon);
             return;
         }
 
+        if (faviconURL != null) {
+            mFaviconURL = faviconURL;
+        }
+
         mThumbnailView.setScaleType(ScaleType.CENTER);
         mThumbnailView.setImageBitmap(favicon);
-        mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(favicon, mUrl));
+
+        if (mFaviconURL != null) {
+            mThumbnailView.setBackgroundColor(Favicons.getFaviconColor(mFaviconURL));
+        }
     }
 
     /**
      * Update the title shown by this view. If both title and url
      * are empty, mark the state as STATE_EMPTY and show a default text.
      */
     private void updateTitleView() {
         String title = getTitle();
@@ -185,9 +202,14 @@ public class TopSitesGridItemView extend
         } else {
             mTitleView.setText(R.string.home_top_sites_add);
             mIsEmpty = true;
         }
 
         // Refresh for state change.
         refreshDrawableState();
     }
+
+    public void setLoadId(int aLoadId) {
+        Favicons.cancelFaviconLoad(mLoadId);
+        mLoadId = aLoadId;
+    }
 }
--- a/mobile/android/base/home/TopSitesPage.java
+++ b/mobile/android/base/home/TopSitesPage.java
@@ -10,16 +10,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.Property;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.db.BrowserDB.TopSitesCursorWrapper;
+import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.home.HomeListView.HomeContextMenuInfo;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
 import org.mozilla.gecko.home.TopSitesGridView.OnPinSiteListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
 import org.mozilla.gecko.util.ThreadUtils;
 
@@ -27,17 +28,16 @@ import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.AsyncTaskLoader;
 import android.support.v4.content.Loader;
 import android.support.v4.widget.CursorAdapter;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -107,32 +107,16 @@ public class TopSitesPage extends HomeFr
     private OnUrlOpenListener mUrlOpenListener;
 
     // Max number of entries shown in the grid from the cursor.
     private int mMaxGridEntries;
 
     // Time in ms until the Gecko thread is reset to normal priority.
     private static final long PRIORITY_RESET_TIMEOUT = 10000;
 
-    /**
-     *  Class to hold the bitmap of cached thumbnails/favicons.
-     */
-    public static class Thumbnail {
-        // Thumbnail or favicon.
-        private final boolean isThumbnail;
-
-        // Bitmap of thumbnail/favicon.
-        private final Bitmap bitmap;
-
-        public Thumbnail(Bitmap bitmap, boolean isThumbnail) {
-            this.bitmap = bitmap;
-            this.isThumbnail = isThumbnail;
-        }
-    }
-
     public static TopSitesPage newInstance() {
         return new TopSitesPage();
     }
 
     public TopSitesPage() {
         mUrlOpenListener = null;
     }
 
@@ -526,17 +510,17 @@ public class TopSitesPage extends HomeFr
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return LayoutInflater.from(context).inflate(R.layout.bookmark_item_row, parent, false);
         }
     }
 
     public class TopSitesGridAdapter extends CursorAdapter {
         // Cache to store the thumbnails.
-        private Map<String, Thumbnail> mThumbnails;
+        private Map<String, Bitmap> mThumbnails;
 
         public TopSitesGridAdapter(Context context, Cursor cursor) {
             super(context, cursor);
         }
 
         @Override
         public int getCount() {
             return Math.min(mMaxGridEntries, super.getCount());
@@ -549,17 +533,17 @@ public class TopSitesPage extends HomeFr
             return;
         }
 
         /**
          * Update the thumbnails returned by the db.
          *
          * @param thumbnails A map of urls and their thumbnail bitmaps.
          */
-        public void updateThumbnails(Map<String, Thumbnail> thumbnails) {
+        public void updateThumbnails(Map<String, Bitmap> thumbnails) {
             mThumbnails = thumbnails;
             notifyDataSetChanged();
         }
 
         @Override
         public void bindView(View bindView, Context context, Cursor cursor) {
             String url = "";
             String title = "";
@@ -567,33 +551,37 @@ public class TopSitesPage extends HomeFr
 
             // Cursor is already moved to required position.
             if (!cursor.isAfterLast()) {
                 url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
                 title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
                 pinned = ((TopSitesCursorWrapper) cursor).isPinned();
             }
 
-            TopSitesGridItemView view = (TopSitesGridItemView) bindView;
+            final TopSitesGridItemView view = (TopSitesGridItemView) bindView;
             view.setTitle(title);
             view.setUrl(url);
             view.setPinned(pinned);
 
             // If there is no url, then show "add bookmark".
             if (TextUtils.isEmpty(url)) {
                 view.displayThumbnail(R.drawable.top_site_add);
             } else {
-                // Show the thumbnail.
-                Thumbnail thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
-                if (thumbnail == null) {
-                    view.displayThumbnail(null);
-                } else if (thumbnail.isThumbnail) {
-                    view.displayThumbnail(thumbnail.bitmap);
+                // Show the thumbnail, if any.
+                Bitmap thumbnail = (mThumbnails != null ? mThumbnails.get(url) : null);
+                if (thumbnail != null) {
+                    view.displayThumbnail(thumbnail);
                 } else {
-                    view.displayFavicon(thumbnail.bitmap);
+                    // If we have no thumbnail, attempt to show a Favicon instead.
+                    view.setLoadId(Favicons.getSizedFaviconForPageFromLocal(url, new OnFaviconLoadedListener() {
+                        @Override
+                        public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
+                            view.displayFavicon(favicon, faviconURL);
+                        }
+                    }));
                 }
             }
         }
 
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return new TopSitesGridItemView(context);
         }
@@ -660,40 +648,40 @@ public class TopSitesPage extends HomeFr
                 mGridAdapter.swapCursor(null);
             }
         }
     }
 
     /**
      * An AsyncTaskLoader to load the thumbnails from a cursor.
      */
-    private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
-        private Map<String, Thumbnail> mThumbnails;
+    private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Bitmap>> {
+        private Map<String, Bitmap> mThumbnails;
         private ArrayList<String> mUrls;
 
         public ThumbnailsLoader(Context context, ArrayList<String> urls) {
             super(context);
             mUrls = urls;
         }
 
         @Override
-        public Map<String, Thumbnail> loadInBackground() {
+        public Map<String, Bitmap> loadInBackground() {
             if (mUrls == null || mUrls.size() == 0) {
                 return null;
             }
 
             // Query the DB for thumbnails.
             final ContentResolver cr = getContext().getContentResolver();
             final Cursor cursor = BrowserDB.getThumbnailsForUrls(cr, mUrls);
 
             if (cursor == null) {
                 return null;
             }
 
-            final Map<String, Thumbnail> thumbnails = new HashMap<String, Thumbnail>();
+            final Map<String, Bitmap> thumbnails = new HashMap<String, Bitmap>();
 
             try {
                 final int urlIndex = cursor.getColumnIndexOrThrow(Thumbnails.URL);
                 final int dataIndex = cursor.getColumnIndexOrThrow(Thumbnails.DATA);
 
                 while (cursor.moveToNext()) {
                     String url = cursor.getString(urlIndex);
 
@@ -708,39 +696,27 @@ public class TopSitesPage extends HomeFr
                     // Our thumbnails are never null, so if we get a null decoded
                     // bitmap, it's because we hit an OOM or some other disaster.
                     // Give up immediately rather than hammering on.
                     if (bitmap == null) {
                         Log.w(LOGTAG, "Aborting thumbnail load; decode failed.");
                         break;
                     }
 
-                    thumbnails.put(url, new Thumbnail(bitmap, true));
+                    thumbnails.put(url, bitmap);
                 }
             } finally {
                 cursor.close();
             }
 
-            // Query the DB for favicons for the urls without thumbnails.
-            for (String url : mUrls) {
-                if (!thumbnails.containsKey(url)) {
-                    final Bitmap bitmap = BrowserDB.getFaviconForUrl(cr, url);
-                    if (bitmap != null) {
-                        // Favicons.scaleImage can return several different size favicons,
-                        // but will at least prevent this from being too large.
-                        thumbnails.put(url, new Thumbnail(Favicons.scaleImage(bitmap), false));
-                    }
-                }
-            }
-
             return thumbnails;
         }
 
         @Override
-        public void deliverResult(Map<String, Thumbnail> thumbnails) {
+        public void deliverResult(Map<String, Bitmap> thumbnails) {
             if (isReset()) {
                 mThumbnails = null;
                 return;
             }
 
             mThumbnails = thumbnails;
 
             if (isStarted()) {
@@ -760,17 +736,17 @@ public class TopSitesPage extends HomeFr
         }
 
         @Override
         protected void onStopLoading() {
             cancelLoad();
         }
 
         @Override
-        public void onCanceled(Map<String, Thumbnail> thumbnails) {
+        public void onCanceled(Map<String, Bitmap> thumbnails) {
             mThumbnails = null;
         }
 
         @Override
         protected void onReset() {
             super.onReset();
 
             // Ensure the loader is stopped.
@@ -778,33 +754,33 @@ public class TopSitesPage extends HomeFr
 
             mThumbnails = null;
         }
     }
 
     /**
      * Loader callbacks for the thumbnails on TopSitesGridView.
      */
-    private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Thumbnail>> {
+    private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, Bitmap>> {
         @Override
-        public Loader<Map<String, Thumbnail>> onCreateLoader(int id, Bundle args) {
+        public Loader<Map<String, Bitmap>> onCreateLoader(int id, Bundle args) {
             return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
         }
 
         @Override
-        public void onLoadFinished(Loader<Map<String, Thumbnail>> loader, Map<String, Thumbnail> thumbnails) {
+        public void onLoadFinished(Loader<Map<String, Bitmap>> loader, Map<String, Bitmap> thumbnails) {
             if (mGridAdapter != null) {
                 mGridAdapter.updateThumbnails(thumbnails);
             }
 
             // Once thumbnails have finished loading, the UI is ready. Reset
             // Gecko to normal priority.
             ThreadUtils.resetGeckoPriority();
         }
 
         @Override
-        public void onLoaderReset(Loader<Map<String, Thumbnail>> loader) {
+        public void onLoaderReset(Loader<Map<String, Bitmap>> loader) {
             if (mGridAdapter != null) {
                 mGridAdapter.updateThumbnails(null);
             }
         }
     }
 }
--- a/mobile/android/base/home/TwoLinePageRow.java
+++ b/mobile/android/base/home/TwoLinePageRow.java
@@ -1,33 +1,29 @@
 /* -*- 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.home;
 
+import android.util.Log;
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
-import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.FaviconView;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.os.AsyncTask;
-import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class TwoLinePageRow extends LinearLayout
@@ -36,22 +32,29 @@ public class TwoLinePageRow extends Line
 
     private final TextView mTitle;
     private final TextView mUrl;
     private final FaviconView mFavicon;
 
     private int mUrlIconId;
     private int mBookmarkIconId;
     private boolean mShowIcons;
+    private int mLoadFaviconJobId = Favicons.NOT_LOADING;
+
+    // Listener for handling Favicon loads.
+    private final OnFaviconLoadedListener mFaviconListener = new OnFaviconLoadedListener() {
+        @Override
+        public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
+            setFaviconWithUrl(favicon, faviconURL);
+        }
+    };
 
     // The URL for the page corresponding to this view.
     private String mPageUrl;
 
-    private LoadFaviconTask mLoadFaviconTask;
-
     public TwoLinePageRow(Context context) {
         this(context, null);
     }
 
     public TwoLinePageRow(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         setGravity(Gravity.CENTER_VERTICAL);
@@ -76,18 +79,16 @@ public class TwoLinePageRow extends Line
         // Delay removing the listener to avoid modifying mTabsChangedListeners
         // while notifyListeners is iterating through the array.
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
             }
         });
-
-        cancelLoadFaviconTask();
     }
 
     @Override
     public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
         switch(msg) {
             case ADDED:
             case CLOSED:
             case LOCATION_CHANGE:
@@ -113,17 +114,21 @@ public class TwoLinePageRow extends Line
             return;
         }
 
         mUrlIconId = urlIconId;
         mUrl.setCompoundDrawablesWithIntrinsicBounds(mUrlIconId, 0, mBookmarkIconId, 0);
     }
 
     private void setFaviconWithUrl(Bitmap favicon, String url) {
-        mFavicon.updateImage(favicon, url);
+        if (favicon == null) {
+            mFavicon.showDefaultFavicon();
+        } else {
+            mFavicon.updateImage(favicon, url);
+        }
     }
 
     private void setBookmarkIcon(int bookmarkIconId) {
         if (mBookmarkIconId == bookmarkIconId) {
             return;
         }
 
         mBookmarkIconId = bookmarkIconId;
@@ -135,26 +140,16 @@ public class TwoLinePageRow extends Line
      * tab changes or is closed.
      */
     private void updateDisplayedUrl(String url) {
         mPageUrl = url;
         updateDisplayedUrl();
     }
 
     /**
-     * Cancels any pending favicon loading task associated with this view.
-     */
-    private void cancelLoadFaviconTask() {
-        if (mLoadFaviconTask != null) {
-            mLoadFaviconTask.cancel(true);
-            mLoadFaviconTask = null;
-        }
-    }
-
-    /**
      * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
      * Only looks for tabs that are either private or non-private, depending on the current 
      * selected tab.
      */
     private void updateDisplayedUrl() {
         boolean isPrivate = Tabs.getInstance().getSelectedTab().isPrivate();
         int tabId = Tabs.getInstance().getTabIdForUrl(mPageUrl, isPrivate);
         if (!mShowIcons || tabId < 0) {
@@ -176,111 +171,51 @@ public class TwoLinePageRow extends Line
         }
 
         int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE);
         final String title = cursor.getString(titleIndex);
 
         int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
         final String url = cursor.getString(urlIndex);
 
+        if (mShowIcons) {
+            final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
+            if (bookmarkIdIndex != -1) {
+                final long bookmarkId = cursor.getLong(bookmarkIdIndex);
+                final int displayIndex = cursor.getColumnIndex(Combined.DISPLAY);
+
+                final int display;
+                if (displayIndex != -1) {
+                    display = cursor.getInt(displayIndex);
+                } else {
+                    display = Combined.DISPLAY_NORMAL;
+                }
+
+                // The bookmark id will be 0 (null in database) when the url
+                // is not a bookmark.
+                if (bookmarkId == 0) {
+                    setBookmarkIcon(NO_ICON);
+                } else if (display == Combined.DISPLAY_READER) {
+                    setBookmarkIcon(R.drawable.ic_url_bar_reader);
+                } else {
+                    setBookmarkIcon(R.drawable.ic_url_bar_star);
+                }
+            } else {
+                setBookmarkIcon(NO_ICON);
+            }
+        }
+
+        // No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
+        if (url.equals(mPageUrl)) {
+            return;
+        }
+
         // Use the URL instead of an empty title for consistency with the normal URL
         // bar view - this is the equivalent of getDisplayTitle() in Tab.java
         setTitle(TextUtils.isEmpty(title) ? url : title);
 
-        // No need to do extra work if the URL associated with this view
-        // hasn't changed.
-        if (TextUtils.equals(mPageUrl, url)) {
-            return;
-        }
+        // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
+        mFavicon.clearImage();
+        mLoadFaviconJobId = Favicons.getSizedFaviconForPageFromLocal(url, mFaviconListener);
 
         updateDisplayedUrl(url);
-        cancelLoadFaviconTask();
-
-        // First, try to find the favicon in the memory cache. If it's not
-        // cached yet, try to load it from the database, off main thread.
-        final Bitmap favicon = Favicons.getFaviconFromMemCache(url);
-        if (favicon != null) {
-            setFaviconWithUrl(favicon, url);
-        } else {
-            // Show blank image until the new favicon finishes loading
-            mFavicon.clearImage();
-
-            mLoadFaviconTask = new LoadFaviconTask(TwoLinePageRow.this, url);
-
-            // Try to use a thread pool instead of serial execution of tasks
-            // to add more throughput to the favicon loading routines.
-            if (Build.VERSION.SDK_INT >= 11) {
-                mLoadFaviconTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-            } else {
-                mLoadFaviconTask.execute();
-            }
-        }
-
-        // Don't show bookmark/reading list icon, if not needed.
-        if (!mShowIcons) {
-            return;
-        }
-
-        final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
-        if (bookmarkIdIndex != -1) {
-            final long bookmarkId = cursor.getLong(bookmarkIdIndex);
-            final int displayIndex = cursor.getColumnIndex(Combined.DISPLAY);
-
-            final int display;
-            if (displayIndex != -1) {
-                display = cursor.getInt(displayIndex);
-            } else {
-                display = Combined.DISPLAY_NORMAL;
-            }
-
-            // The bookmark id will be 0 (null in database) when the url
-            // is not a bookmark.
-            if (bookmarkId == 0) {
-                setBookmarkIcon(NO_ICON);
-            } else if (display == Combined.DISPLAY_READER) {
-                setBookmarkIcon(R.drawable.ic_url_bar_reader);
-            } else {
-                setBookmarkIcon(R.drawable.ic_url_bar_star);
-            }
-        } else {
-            setBookmarkIcon(NO_ICON);
-        }
-    }
-
-    void onFaviconLoaded(Bitmap favicon, String url) {
-        if (TextUtils.equals(mPageUrl, url)) {
-            setFaviconWithUrl(favicon, url);
-        }
-
-        mLoadFaviconTask = null;
-    }
-
-    private static class LoadFaviconTask extends AsyncTask<Void, Void, Bitmap> {
-        private final TwoLinePageRow mRow;
-        private final String mUrl;
-
-        public LoadFaviconTask(TwoLinePageRow row, String url) {
-            mRow = row;
-            mUrl = url;
-        }
-
-        @Override
-        public Bitmap doInBackground(Void... params) {
-            Bitmap favicon = Favicons.getFaviconFromMemCache(mUrl);
-            if (favicon == null) {
-                final ContentResolver cr = mRow.getContext().getContentResolver();
-
-                final Bitmap faviconFromDb = BrowserDB.getFaviconForUrl(cr, mUrl);
-                if (faviconFromDb != null) {
-                    favicon = Favicons.scaleImage(faviconFromDb);
-                    Favicons.putFaviconInMemCache(mUrl, favicon);
-                }
-            }
-
-            return favicon;
-        }
-
-        @Override
-        public void onPostExecute(Bitmap favicon) {
-            mRow.onFaviconLoaded(favicon, mUrl);
-        }
     }
 }
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -14,16 +14,23 @@
     <dimen name="browser_toolbar_lock_width">20dp</dimen>
     <dimen name="browser_toolbar_favicon_size">25.33dip</dimen>
 
     <!-- Dimensions used by Favicons and FaviconView -->
     <dimen name="favicon_size_small">16dp</dimen>
     <dimen name="favicon_size_large">32dp</dimen>
     <dimen name="favicon_bg">32dp</dimen>
     <dimen name="favicon_bg_radius">1dp</dimen>
+    <!-- Set the upper limit on the size of favicon that will be processed. Favicons larger than
+         this will be downscaled to this value. If you need to use larger Favicons (Due to a UI
+         redesign sometime after this is written) you should increase this value to the largest
+         commonly-used size of favicon and, performance permitting, fetch the remainder from the
+         database. The largest available size is always stored in the database, regardless of this
+         value.-->
+    <dimen name="favicon_largest_interesting_size">32dp</dimen>
 
     <!-- Page Row height -->
     <dimen name="page_row_height">64dp</dimen>
 
     <!-- Search Engine Row height -->
     <dimen name="search_row_height">48dp</dimen>
 
     <!-- Max width of arrow popups on tablets -->
--- a/mobile/android/base/widget/FaviconView.java
+++ b/mobile/android/base/widget/FaviconView.java
@@ -101,17 +101,21 @@ public class FaviconView extends ImageVi
         }
     }
 
     /**
      * Helper method to display background of the dominant colour of the favicon to pad the remaining
      * space.
      */
     private void showBackground() {
-        int color = Favicons.getFaviconColor(mIconBitmap, mIconKey);
+        int color = Favicons.getFaviconColor(mIconKey);
+        if (color == -1) {
+            hideBackground();
+            return;
+        }
         color = Color.argb(70, Color.red(color), Color.green(color), Color.blue(color));
         final Drawable drawable = getResources().getDrawable(R.drawable.favicon_bg);
         drawable.setColorFilter(color, Mode.SRC_ATOP);
         setBackgroundDrawable(drawable);
     }
 
     /**
      * Method to hide the background. The view will now have a transparent background.
@@ -147,17 +151,17 @@ public class FaviconView extends ImageVi
         mIconBitmap = bitmap;
         mIconKey = key;
         mScalingExpected = allowScaling;
 
         // Possibly update the display.
         formatImage();
     }
 
-    private void showDefaultFavicon() {
+    public void showDefaultFavicon() {
         setImageResource(R.drawable.favicon);
         hideBackground();
     }
 
     private void showNoImage() {
         setImageBitmap(null);
         hideBackground();
     }