Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 13 Oct 2013 10:29:45 -0700
changeset 164421 20ffeada8ecab8a70699718cbc5c7d370cd7f5bf
parent 164420 33563ee5a85975f7f8590d832110f66a4ee81245 (current diff)
parent 164419 211337f7fb83f9549096d7ecbc95649103605bbb (diff)
child 164422 c968b5768f41bab21ea99bf4dc5313ad3febd9c8
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)
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
Merge m-c to m-i
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -294,16 +294,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 #ifdef MOZ_USE_NATIVE_UCONV
 @BINPATH@/components/ucnative.xpt
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -680,16 +680,21 @@ SourcesView.prototype = Heritage.extend(
    * The select listener for the sources container.
    */
   _onSourceSelect: function({ detail: sourceItem }) {
     if (!sourceItem) {
       return;
     }
     // The container is not empty and an actual item was selected.
     DebuggerView.setEditorLocation(sourceItem.value);
+
+    // Set window title.
+    let script = sourceItem.value.split(" -> ").pop();
+    document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
+
     this.maybeShowBlackBoxMessage();
   },
 
   /**
    * Show or hide the black box message vs. source editor depending on if the
    * selected source is black boxed or not.
    */
   maybeShowBlackBoxMessage: function() {
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -63,16 +63,17 @@ let DebuggerView = {
     this.ChromeGlobals.initialize();
     this.StackFrames.initialize();
     this.Sources.initialize();
     this.WatchExpressions.initialize();
     this.EventListeners.initialize();
     this.GlobalSearch.initialize();
     this._initializeVariablesView();
     this._initializeEditor(deferred.resolve);
+    document.title = L10N.getStr("DebuggerWindowTitle");
 
     return deferred.promise;
   },
 
   /**
    * Destroys the debugger view.
    *
    * @return object
--- a/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js
@@ -14,16 +14,19 @@ function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
 
+    ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel1),
+      "Title with first source is correct.");
+
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
       .then(testSourcesDisplay)
       .then(testSwitchPaused1)
       .then(testSwitchPaused2)
       .then(testSwitchRunning)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
@@ -59,16 +62,19 @@ function testSourcesDisplay() {
   is(gSources.selectedValue, EXAMPLE_URL + gLabel2,
     "The selected value is the sources pane is incorrect.");
 
   is(gEditor.getText().search(/firstCall/), -1,
     "The first source is not displayed.");
   is(gEditor.getText().search(/debugger/), 172,
     "The second source is displayed.");
 
+  ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel2),
+    "Title with second source is correct.");
+
   ok(isCaretPos(gPanel, 6),
     "Editor caret location is correct.");
 
   // The editor's debug location takes a tick to update.
   executeSoon(() => {
     is(gEditor.getDebugLocation(), 5,
       "Editor debugger location is correct.");
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -300,16 +300,17 @@
 @BINPATH@/components/services-crypto-component.xpt
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/browser/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -10,16 +10,24 @@
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
 # LOCALIZATION NOTE (ToolboxDebugger.label):
 # This string is displayed in the title of the tab when the debugger is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxDebugger.label=Debugger
 
+# LOCALIZATION NOTE (DebuggerWindowTitle):
+# The title displayed for the debugger window.
+DebuggerWindowTitle=Browser Debugger
+
+# LOCALIZATION NOTE (DebuggerWindowScriptTitle):
+# The title displayed for the debugger window when a script is selected.
+DebuggerWindowScriptTitle=Browser Debugger - %S
+
 # LOCALIZATION NOTE (ToolboxDebugger.tooltip):
 # This string is displayed in the tooltip of the tab when the debugger is
 # displayed inside the developer tools window..
 ToolboxDebugger.tooltip=JavaScript Debugger
 
 # LOCALIZATION NOTE (debuggerMenu.commandkey, debuggerMenu.accesskey)
 # Used for the menuitem in the tool menu
 debuggerMenu.commandkey=S
--- a/content/base/test/test_bug338583.html
+++ b/content/base/test/test_bug338583.html
@@ -84,19 +84,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       e.target.hits['fn_event_listener_message']++;
   }
 
   function fn_other_event_name(e) {
     if (e.currentTarget == e.target && e.target.hits != null)
       e.target.hits['fn_other_event_name']++;
   }
 
-  var domBranch;
-  var oldPrefVal;
-
   var gEventSourceObj1 = null, gEventSourceObj1_e, gEventSourceObj1_f;
   var gEventSourceObj2 = null;
   var gEventSourceObj3_a = null, gEventSourceObj3_b = null,
       gEventSourceObj3_c = null, gEventSourceObj3_d = null,
       gEventSourceObj3_e = null, gEventSourceObj3_f = null,
       gEventSourceObj3_g = null, gEventSourceObj3_h = null;
   var gEventSourceObj4_a = null, gEventSourceObj4_b = null;
   var gEventSourceObj5_a = null, gEventSourceObj5_b = null,
@@ -245,16 +242,22 @@ https://bugzilla.mozilla.org/show_bug.cg
     setTimeout(function() {
       ok(gEventSourceObj3_a.hits['fn_onmessage'] == 0, "Test 3.a failed");
       gEventSourceObj3_a.close();
       setTestHasFinished(test_id);
     }, parseInt(1500*stress_factor));
   }
 
   function doTest3_b(test_id) {
+    // currently no support yet for local files for b2g/Android mochitest, see bug 838726
+    if (navigator.appVersion.indexOf("Android") != -1 || SpecialPowers.Services.appinfo.name == "B2G") {
+      setTestHasFinished(test_id);
+      return;
+    }
+
     var xhr = new XMLHttpRequest;
     xhr.open("GET", "/dynamic/getMyDirectory.sjs", false);
     xhr.send();
     var basePath = xhr.responseText;
 
     gEventSourceObj3_b = new EventSource("file://" + basePath + "eventsource.resource");
 
     gEventSourceObj3_b.onmessage = fn_onmessage;
@@ -457,17 +460,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       gEventSourceObj5_b.close();
       setTestHasFinished(test_id);
     }, parseInt(3000*stress_factor));
   }
 
   function doTest5_c(test_id)
   {
     // credentials using the auth cache and cookies
-    var xhr = SpecialPowers.createSystemXHR();
+    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
     xhr.withCredentials = true;
     // also, test mixed mode UI
     xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.c");
 
       gEventSourceObj5_c = new EventSource("https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
@@ -486,17 +489,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         gEventSourceObj5_c.close();
         doTest5_d(test_id);
       }, parseInt(3000*stress_factor));
     };
   }
 
   function doTest5_d(test_id)
   {
-    var xhr = SpecialPowers.createSystemXHR();
+    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.d");
   
       gEventSourceObj5_d = new EventSource("https://example.com/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc");
       ok(!gEventSourceObj5_d.withCredentials, "Wrong withCredentials in test 5.d");
@@ -514,17 +517,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         setTestHasFinished(test_id);
       }, parseInt(3000*stress_factor));
     };
   }
 
   function doTest5_e(test_id)
   {
     // credentials using the auth cache and cookies
-    var xhr = SpecialPowers.createSystemXHR();
+    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_xhr", true, "user 1", "password 1");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.e");
 
       gEventSourceObj5_e = new EventSource("http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user1_evtsrc",
                                            { get withCredentials() { return true; } } );
@@ -542,17 +545,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         gEventSourceObj5_e.close();
         doTest5_f(test_id);
       }, parseInt(5000*stress_factor));
     };
   }
 
   function doTest5_f(test_id)
   {
-    var xhr = SpecialPowers.createSystemXHR();
+    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
     xhr.withCredentials = true;
     xhr.open("GET", "http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_xhr", true, "user 2", "password 2");
     xhr.send();
     xhr.onloadend = function() {
       ok(xhr.status == 200, "Failed to set credentials in test 5.f");
 
       gEventSourceObj5_f = new EventSource("http://example.org/tests/content/base/test/file_restrictedEventSource.sjs?test=user2_evtsrc",
                                            { });
@@ -609,32 +612,29 @@ https://bugzilla.mozilla.org/show_bug.cg
     
     setTimeout(function() {
       gEventSourceObj7.close();
       
       ok(gEventSourceObj7.msg_received[0] == "" &&
          gEventSourceObj7.msg_received[1] == "delayed1" &&
          gEventSourceObj7.msg_received[2] == "delayed2", "Test 7 failed");
 
-      SpecialPowers.setBoolPref("dom.server-events.enabled", oldPrefVal);
       document.getElementById('waitSpan').innerHTML = '';
       setTestHasFinished(test_id);
     }, parseInt(8000*stress_factor));
   }
 
   function doTest()
   {
     // Allow all cookies, then run the actual test
-    SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, doTestCallback);
+    SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0], ["dom.server-events.enabled", true]]}, function() { SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], doTestCallback);});
   }
-  
+
   function doTestCallback()
   {
-    oldPrefVal = SpecialPowers.getBoolPref("dom.server-events.enabled");
-    SpecialPowers.setBoolPref("dom.server-events.enabled", true);
 
     // we get a good stress_factor by testing 10 setTimeouts and some float
     // arithmetic taking my machine as stress_factor==1 (time=589)
 
     var begin_time = (new Date()).getTime();
 
     var f = function() {
       for (var j=0; j<f.i; ++j)
--- a/content/base/test/test_bug426308.html
+++ b/content/base/test/test_bug426308.html
@@ -16,18 +16,27 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 426308 **/
 
 const SJS_URL = "http://example.org:80/tests/content/base/test/bug426308-redirect.sjs";
 
-var req = SpecialPowers.createSystemXHR();
-req.open("GET", SJS_URL + "?" + window.location.href, false);
-req.send(null);
+function startTest() {
+  var req = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  req.open("GET", SJS_URL + "?" + window.location.href, false);
+  req.send(null);
+
+  is(req.status, 200, "Redirect did not happen");
 
-is(req.status, 200, "Redirect did not happen");
+  SimpleTest.finish();
+}
 
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
 </script>
 </pre>
 </body>
 </html>
--- a/content/base/test/test_bug431701.html
+++ b/content/base/test/test_bug431701.html
@@ -46,17 +46,17 @@ function frameDoc(id) {
 
 function createDoc() {
   return document.implementation.createDocument('', 'html', null);
 }
 
 function xhrDoc(idx) {
   return function() {
     // Defy same-origin restrictions!
-    var xhr = SpecialPowers.createSystemXHR();
+    var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
     xhr.open("GET", docSources[idx], false);
     xhr.send();
     return xhr.responseXML;
   };
 }
 
 // Each row has the document getter function, then the characterSet,
 // inputEncoding expected for that document.
@@ -82,16 +82,20 @@ function doTest(idx) {
 
   // Have to be careful here to catch null vs ""
   is(doc.characterSet, expectedCharacterSet, "Test " + idx + " characterSet");
   is(doc.inputEncoding, expectedInputEncoding,
      "Test " + idx + " inputEncoding");
 }
 
 addLoadEvent(function() {
+   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
+
+function startTest() {
   // sanity check
   isnot("", null, "Shouldn't be equal!");
 
   for (var i = 0; i < tests.length; ++i) {
     doTest(i);
   }
 
   // Now check what xhr does
@@ -99,17 +103,17 @@ addLoadEvent(function() {
   xhr.open("POST", document.location.href);
   xhr.send(createDoc());
   is(SpecialPowers.wrap(xhr).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel)
                 .getRequestHeader("Content-Type"),
      "application/xml; charset=UTF-8", "Testing correct type on the wire");
   xhr.abort();
                      
   SimpleTest.finish();
-});
+};
 
 
 
 
 </script>
 </pre>
 </body>
 </html>
--- a/content/base/test/test_bug804395.html
+++ b/content/base/test/test_bug804395.html
@@ -14,41 +14,41 @@ https://bugzilla.mozilla.org/show_bug.cg
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 function test200() {
-  var xhr = SpecialPowers.createSystemXHR();
+  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
   xhr.onreadystatechange = function() {
     if (xhr.readyState == 4) {
       ok(xhr.status == 200, "Existing file must have Status 200!");
       runTests();
     }
   }
   xhr.send(null);
 }
 
 function test404() {
-  var xhr = SpecialPowers.createSystemXHR();
+  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.do_not_exist', true);
   xhr.onreadystatechange = function() {
     if (xhr.readyState == 4) {
       ok(xhr.status == 404, "Non existing file must have Status 404!");
       runTests();
     }
   }
   xhr.send(null);
 }
 
 function test0() {
-  var xhr = SpecialPowers.createSystemXHR();
+  var xhr = new XMLHttpRequest({mozAnon: false, mozSystem: true});
   xhr.open('GET', 'jar:http://example.org/tests/content/base/test/file_bug804395.jar!/foo.bar', true);
   ok(xhr.status == 0, "Not Sent request must have status 0");
   runTests();
 }
 
 var tests = [ test200, test404, test0 ];
 function runTests() {
   if (!tests.length) {
@@ -56,15 +56,17 @@ function runTests() {
     return;
   }
 
   var test = tests.shift();
   test();
 }
 
 /** Test for Bug 804395 **/
-runTests();
 SimpleTest.waitForExplicitFinish();
 
+addLoadEvent(function() {
+   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runTests);
+});
 </script>
 </pre>
 </body>
 </html>
--- a/content/base/test/test_xhr_forbidden_headers.html
+++ b/content/base/test/test_xhr_forbidden_headers.html
@@ -44,43 +44,53 @@ var headers = [
   "viA",
   "pRoxy-",
   "sEc-",
   "proxy-fOobar",
   "sec-bAZbOx"
 ];
 var i, request;
 
-// Try setting headers in unprivileged context
-request = new XMLHttpRequest();
-request.open("GET", window.location.href);
-for (i = 0; i < headers.length; i++)
-  request.setRequestHeader(headers[i], "test" + i);
+function  startTest() {
+  // Try setting headers in unprivileged context
+  request = new XMLHttpRequest();
+  request.open("GET", window.location.href);
+  for (i = 0; i < headers.length; i++)
+    request.setRequestHeader(headers[i], "test" + i);
+
+  // Read out headers
+  var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
+  for (i = 0; i < headers.length; i++) {
+    // Retrieving Content-Length will throw an exception
+    var value = null;
+    try {
+      value = channel.getRequestHeader(headers[i]);
+    }
+    catch(e) {}
 
-// Read out headers
-var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
-for (i = 0; i < headers.length; i++) {
-  // Retrieving Content-Length will throw an exception
-  var value = null;
-  try {
-    value = channel.getRequestHeader(headers[i]);
+    isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
   }
-  catch(e) {}
+
+  // Try setting headers in privileged context
+  request = new XMLHttpRequest({mozAnon: false, mozSystem: true});
+  request.open("GET", window.location.href);
+  for (i = 0; i < headers.length; i++)
+    request.setRequestHeader(headers[i], "test" + i);
 
-  isnot(value, "test" + i, "Setting " + headers[i] + " header in unprivileged context");
+  // Read out headers
+  var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
+  for (i = 0; i < headers.length; i++) {
+    var value = channel.getRequestHeader(headers[i]);
+    is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
+  }
+
+  SimpleTest.finish();
 }
 
-// Try setting headers in privileged context
-request = SpecialPowers.createSystemXHR();
-request.open("GET", window.location.href);
-for (i = 0; i < headers.length; i++)
-  request.setRequestHeader(headers[i], "test" + i);
+SimpleTest.waitForExplicitFinish();
 
-// Read out headers
-var channel = request.channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
-for (i = 0; i < headers.length; i++) {
-  var value = channel.getRequestHeader(headers[i]);
-  is(value, "test" + i, "Setting " + headers[i] + " header in privileged context");
-}
+addLoadEvent(function() {
+   SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], startTest);
+});
 </script>
 </pre>
 </body>
 </html>
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -10,16 +10,20 @@
 #include "prsystem.h"
 
 #if defined(XP_UNIX)
 #include "unistd.h"
 #include "dirent.h"
 #include "sys/stat.h"
 #endif // defined(XP_UNIX)
 
+#if defined(XP_LINUX)
+#include <linux/fadvise.h>
+#endif // defined(XP_LINUX)
+
 #if defined(XP_MACOSX)
 #include "copyfile.h"
 #endif // defined(XP_MACOSX)
 
 #if defined(XP_WIN)
 #include <windows.h>
 #endif // defined(XP_WIN)
 
@@ -372,16 +376,20 @@ static const dom::ConstantSpec gLibcProp
 #endif //defined(AT_EACCESS)
 #if defined(AT_FDCWD)
   INT_CONSTANT(AT_FDCWD),
 #endif //defined(AT_FDCWD)
 #if defined(AT_SYMLINK_NOFOLLOW)
   INT_CONSTANT(AT_SYMLINK_NOFOLLOW),
 #endif //defined(AT_SYMLINK_NOFOLLOW)
 
+#if defined(POSIX_FADV_SEQUENTIAL)
+  INT_CONSTANT(POSIX_FADV_SEQUENTIAL),
+#endif //defined(POSIX_FADV_SEQUENTIAL)
+
   // access
 #if defined(F_OK)
   INT_CONSTANT(F_OK),
   INT_CONSTANT(R_OK),
   INT_CONSTANT(W_OK),
   INT_CONSTANT(X_OK),
 #endif // defined(F_OK)
 
--- 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,48 +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() {
+        final int tabFaviconSize = getResources().getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
 
-            @Override
-            public void onFaviconLoaded(String pageUrl, Bitmap favicon) {
-                // Leave favicon UI untouched if we failed to load the image
-                // for some reason.
-                if (favicon == null)
-                    return;
+        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/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -1116,17 +1116,17 @@ public class BrowserToolbar extends Geck
     private void setFavicon(Bitmap image) {
         if (Tabs.getInstance().getSelectedTab().getState() == Tab.STATE_LOADING)
             return;
 
         if (image != null) {
             image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
             mFavicon.setImageBitmap(image);
         } else {
-            mFavicon.setImageResource(R.drawable.favicon);
+            mFavicon.setImageBitmap(null);
         }
     }
     
     private void setSecurityMode(String mode) {
         int imageLevel = SiteIdentityPopup.getSecurityImageLevel(mode);
         mSiteSecurity.setImageLevel(imageLevel);
         mShowSiteSecurity = (imageLevel != SiteIdentityPopup.LEVEL_UKNOWN);
 
--- 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,148 +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;
 
+    // The default Favicon to show if no other can be found.
+    public static Bitmap sDefaultFavicon;
+
+    // The density-adjusted default Favicon dimensions.
+    public static int sDefaultFaviconSize;
+
     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();
-        }
-    };
+
+    // 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 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);
+    public static String getFaviconURLForPageURLFromCache(String pageURL) {
+        return sPageURLMappings.get(pageURL);
+    }
 
-    // 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,
+    /**
+     * 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);
@@ -156,52 +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));
+    /**
+     * 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;
         }
-        if (sFaviconLargeSize < 0) {
-            sFaviconLargeSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_large));
+
+        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,191 @@
 
 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.LinkedList;
 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 LinkedList<LoadFaviconTask> mChainees;
+    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 +201,204 @@ 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;
+                // If we are chaining, we want to keep the first task started to do this job as the one
+                // in the hashmap so subsequent tasks will add themselves to its chaining list.
+                return null;
+            }
+
+            // We do not want to update the hashmap if the task has chained - other tasks need to
+            // chain onto the same parent task.
+            loadsInFlight.put(mFaviconUrl, this);
+        }
+
+        if (isCancelled()) {
+            return null;
         }
 
-        if (isCancelled())
+        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, etc.
+        processResult(image);
+
+        synchronized (loadsInFlight) {
+            // Prevent any other tasks from chaining on this one.
+            loadsInFlight.remove(mFaviconUrl);
+        }
+
+        // Since any update to mChainees is done while holding the loadsInFlight lock, once we reach
+        // this point no further updates to that list can possibly take place (As far as other tasks
+        // are concerned, there is no longer a task to chain from. The above block will have waited
+        // for any tasks that were adding themselves to the list before reaching this point.)
+
+        // As such, I believe we're safe to do the following without holding the lock.
+        // This is nice - we do not want to take the lock unless we have to anyway, and chaining rarely
+        // actually happens outside of the strange situations unit tests create.
+
+        // Share the result with all chained tasks.
+        if (mChainees != null) {
+            for (LoadFaviconTask t : mChainees) {
+                t.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);
     }
 
     @Override
     protected void onCancelled() {
         Favicons.removeLoadTask(mId);
 
+        synchronized(loadsInFlight) {
+            // Only remove from the hashmap if the task there is the one that's being canceled.
+            // Cancellation of a task that would have chained is not interesting to the hashmap.
+            final LoadFaviconTask primary = loadsInFlight.get(mFaviconUrl);
+            if (primary == this) {
+                loadsInFlight.remove(mFaviconUrl);
+                return;
+            }
+            if (primary == null) {
+                // This shouldn't happen.
+                return;
+            }
+            if (primary.mChainees != null) {
+              primary.mChainees.remove(this);
+            }
+        }
+
         // 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).
+     * The loadsInFlight lock must be held when calling this function.
+     *
+     * @param aChainee LoadFaviconTask
+     */
+    private void chainTasks(LoadFaviconTask aChainee) {
+        if (mChainees == null) {
+            mChainees = new LinkedList<LoadFaviconTask>();
+        }
+
+        mChainees.add(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/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -136,18 +136,17 @@
         <ImageButton android:id="@+id/favicon"
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_favicon_size"
                      android:layout_height="fill_parent"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="8dip"
                      android:paddingLeft="4dip"
                      android:paddingRight="4dip"
-                     android:layout_gravity="center_vertical"
-                     android:src="@drawable/favicon"/>
+                     android:layout_gravity="center_vertical"/>
 
         <ImageButton android:id="@+id/site_security"
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
                      android:src="@drawable/site_security_level"
                      android:contentDescription="@string/site_security"
--- 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/tests/AboutHomeTest.java.in
+++ b/mobile/android/base/tests/AboutHomeTest.java.in
@@ -93,17 +93,17 @@ abstract class AboutHomeTest extends Bas
             mAsserter.ok(false, url + " is not one of the displayed bookmarks", "Please make sure the url provided is bookmarked");
         }
     }
 
     // @return the View associated with bookmark for the provided url or null if the link is not bookmarked
     protected View getDisplayedBookmark(String url) {
         openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
         ListView bookmarksTabList = findListViewWithTag("bookmarks");
-        waitForListToLoad(bookmarksTabList);
+        waitForNonEmptyListToLoad(bookmarksTabList);
         ListAdapter adapter = bookmarksTabList.getAdapter();
         if (adapter != null) {
             for (int i = 0; i < adapter.getCount(); i++ ) {
                 // I am unable to click the view taken with getView for some reason so getting the child at i
                 bookmarksTabList.smoothScrollToPosition(i);
                 View bookmarkView = bookmarksTabList.getChildAt(i);
                 if (bookmarkView instanceof android.widget.LinearLayout) {
                     ViewGroup bookmarkItemView = (ViewGroup) bookmarkView;
@@ -122,39 +122,45 @@ abstract class AboutHomeTest extends Bas
                     }
                 }
             }
         }
         return null;
     }
 
     /**
-     * Waits for the given ListView to have a non-empty adapter.
+     * Waits for the given ListView to have a non-empty adapter and be populated
+     * with a minimum number of items.
      *
-     * This method will return false if the given ListView or its adapter are null.
+     * This method will return false if the given ListView or its adapter is null,
+     * or if the ListView does not have the minimum number of items.
      */
-    protected boolean waitForListToLoad(final ListView listView) {
+    protected boolean waitForListToLoad(final ListView listView, final int minSize) {
         Condition listWaitCondition = new Condition() {
             @Override
             public boolean isSatisfied() {
                 if (listView == null) {
                     return false;
                 }
 
                 final ListAdapter adapter = listView.getAdapter();
                 if (adapter == null) {
                     return false;
                 }
 
-                return (adapter.getCount() > 0);
+                return (listView.getCount() - listView.getHeaderViewsCount() >= minSize);
             }
         };
         return waitForCondition(listWaitCondition, MAX_WAIT_MS);
     }
 
+    protected boolean waitForNonEmptyListToLoad(final ListView listView) {
+        return waitForListToLoad(listView, 1);
+    }
+
     /**
      * Get an active ListView with the specified tag .
      *
      * This method uses the predefined tags in HomePager.
      */
     protected final ListView findListViewWithTag(String tag) {
         for (ListView listView : mSolo.getCurrentViews(ListView.class)) {
             final String listTag = (String) listView.getTag();
--- a/mobile/android/base/tests/testBookmarklets.java.in
+++ b/mobile/android/base/tests/testBookmarklets.java.in
@@ -39,17 +39,17 @@ public class testBookmarklets extends Ab
         // add the bookmarklet to the database. there's currently no way to
         // add this using the UI, so we go through the content provider.
         mDatabaseHelper.addOrUpdateMobileBookmark(title, js);
 
         // Open about:home in the Bookmarks page
         openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
 
         ListView bookmarks = findListViewWithTag("bookmarks");
-        mAsserter.is(waitForListToLoad(bookmarks), true, "list is properly loaded");
+        mAsserter.is(waitForNonEmptyListToLoad(bookmarks), true, "list is properly loaded");
 
         int width = mDriver.getGeckoWidth();
         int height = mDriver.getGeckoHeight();
 
         // Scroll down so that the bookmarks list has more items on screen.
         mActions.drag(width / 2, width / 2, height - 10, height / 2);
 
         // Verify that bookmarklets clicked in awesomescreen work
--- a/mobile/android/base/tests/testHistory.java.in
+++ b/mobile/android/base/tests/testHistory.java.in
@@ -28,17 +28,17 @@ public class testHistory extends AboutHo
         inputAndLoadUrl(url2);
         verifyPageTitle("Browser Blank Page 02");
         inputAndLoadUrl(url3);
         verifyPageTitle("Browser Blank Page 03");
 
         openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
 
         final ListView hList = findListViewWithTag("most_recent");
-        mAsserter.is(waitForListToLoad(hList), true, "list is properly loaded");
+        mAsserter.is(waitForNonEmptyListToLoad(hList), true, "list is properly loaded");
 
         // Click on the history item and wait for the page to load
         // wait for the history list to be populated
         mFirstChild = null;
         boolean success = waitForTest(new BooleanTest() {
             @Override
             public boolean test() {
                 mFirstChild = hList.getChildAt(1);
--- a/mobile/android/base/tests/testShareLink.java.in
+++ b/mobile/android/base/tests/testShareLink.java.in
@@ -67,17 +67,17 @@ public class testShareLink extends About
         float left = mDriver.getGeckoLeft() + mDriver.getGeckoWidth() / 2;
         mSolo.clickLongOnScreen(left, top);
         verifySharePopup("Share Link",shareOptions,"Link");
 
         // Test the share popup in the Bookmarks page
         openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
 
         ListView bookmarksList = findListViewWithTag("bookmarks");
-        mAsserter.is(waitForListToLoad(bookmarksList), true, "list is properly loaded");
+        mAsserter.is(waitForNonEmptyListToLoad(bookmarksList), true, "list is properly loaded");
 
         View bookmarksItem = bookmarksList.getChildAt(bookmarksList.getHeaderViewsCount());
         mSolo.clickLongOnView(bookmarksItem);
         verifySharePopup(shareOptions,"bookmarks");
 
         // Prepopulate top sites with history items to overflow tiles.
         // We are trying to move away from using reflection and doing more black-box testing.
         inputAndLoadUrl(getAbsoluteUrl("/robocop/robocop_blank_01.html"));
@@ -96,26 +96,26 @@ public class testShareLink extends About
         openAboutHomeTab(AboutHomeTabs.TOP_SITES);
 
         // Scroll down a bit so that the top sites list has more items on screen.
         int width = mDriver.getGeckoWidth();
         int height = mDriver.getGeckoHeight();
         mActions.drag(width / 2, width / 2, height - 10, height / 2);
 
         ListView topSitesList = findListViewWithTag("top_sites");
-        mAsserter.is(waitForListToLoad(topSitesList), true, "list is properly loaded");
+        mAsserter.is(waitForNonEmptyListToLoad(topSitesList), true, "list is properly loaded");
         View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
         mSolo.clickLongOnView(mostVisitedItem);
         verifySharePopup(shareOptions,"top_sites");
 
         // Test the share popup in the Most Recent tab
         openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
 
         ListView mostRecentList = findListViewWithTag("most_recent");
-        mAsserter.is(waitForListToLoad(mostRecentList), true, "list is properly loaded");
+        mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
 
         // Getting second child after header views because the first is the "Today" label
         View mostRecentItem = mostRecentList.getChildAt(mostRecentList.getHeaderViewsCount() + 1);
         mSolo.clickLongOnView(mostRecentItem);
         verifySharePopup(shareOptions,"most recent");
     }
 
     public void verifySharePopup(ArrayList<String> shareOptions, String openedFrom) {
--- 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();
     }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -238,16 +238,17 @@
 #ifdef MOZ_CAPTIVEDETECT
 @BINPATH@/components/captivedetect.xpt
 #endif
 @BINPATH@/components/shellservice.xpt
 @BINPATH@/components/shistory.xpt
 @BINPATH@/components/spellchecker.xpt
 @BINPATH@/components/storage.xpt
 @BINPATH@/components/telemetry.xpt
+@BINPATH@/components/toolkit_finalizationwitness.xpt
 @BINPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @BINPATH@/components/toolkitremote.xpt
 #endif
 @BINPATH@/components/txtsvc.xpt
 @BINPATH@/components/txmgr.xpt
 @BINPATH@/components/uconv.xpt
 @BINPATH@/components/unicharutil.xpt
--- a/testing/mochitest/android.json
+++ b/testing/mochitest/android.json
@@ -31,17 +31,16 @@
  "content/base/test/test_range_bounds.html": "",
  "content/base/test/test_reentrant_flush.html": "RANDOM",
  "content/base/test/test_sync_xhr_timer.xhtml": "RANDOM",
  "content/base/test/test_websocket.html": "",
  "content/base/test/test_websocket_basic.html": "",
  "content/base/test/test_websocket_hello.html": "",
  "content/base/test/test_x-frame-options.html": "",
  "content/base/test/test_xhr_abort_after_load.html": "",
- "content/base/test/test_xhr_forbidden_headers.html": "",
  "content/base/test/test_xhr_progressevents.html": "",
  "content/base/test/websocket_hybi/test_receive-arraybuffer.html": "",
  "content/base/test/websocket_hybi/test_receive-blob.html": "",
  "content/base/test/websocket_hybi/test_send-arraybuffer.html": "",
  "content/base/test/websocket_hybi/test_send-blob.html": "",
  "content/canvas/test/webgl": "bug 865443- seperate suite",
  "content/events/test/test_bug409604.html": "TIMED_OUT",
  "content/events/test/test_bug426082.html": "",
--- a/testing/mochitest/androidx86.json
+++ b/testing/mochitest/androidx86.json
@@ -32,17 +32,16 @@
  "content/base/test/test_range_bounds.html": "",
  "content/base/test/test_reentrant_flush.html": "RANDOM",
  "content/base/test/test_sync_xhr_timer.xhtml": "RANDOM",
  "content/base/test/test_websocket.html": "",
  "content/base/test/test_websocket_basic.html": "",
  "content/base/test/test_websocket_hello.html": "",
  "content/base/test/test_x-frame-options.html": "",
  "content/base/test/test_xhr_abort_after_load.html": "",
- "content/base/test/test_xhr_forbidden_headers.html": "",
  "content/base/test/test_xhr_progressevents.html": "",
  "content/base/test/websocket_hybi/test_receive-arraybuffer.html": "",
  "content/base/test/websocket_hybi/test_receive-blob.html": "",
  "content/base/test/websocket_hybi/test_send-arraybuffer.html": "",
  "content/base/test/websocket_hybi/test_send-blob.html": "",
  "content/canvas/test/test_2d.composite.canvas.color-burn.html": "x86 only bug 913662",
  "content/canvas/test/test_2d.composite.canvas.color-dodge.html": "x86 only bug 913662",
  "content/canvas/test/test_2d.composite.canvas.color.html": "x86 only bug 913662",
--- a/testing/mochitest/b2g.json
+++ b/testing/mochitest/b2g.json
@@ -87,18 +87,17 @@
 
     "content/base/test/test_XHRSendData.html":"seems to stall",
     "content/base/test/test_XHR_parameters.html":"86 total, 4 failing - testing mozAnon - got false, expected true",
     "content/base/test/test_XHR_system.html":"12 total, 2 failing - .mozSystem == true - got false, expected true + ",
 
     "content/base/test/test_bug431701.html":"xmlhttprequest causes crash, bug 902271",
     "content/base/test/test_bug422537.html":"xmlhttprequest causes crash, bug 902271",
 
-    "content/base/test/test_bug338583.html":"43 total - bug 901343, specialpowers.wrap issue createsystemxhr",
-    "content/base/test/test_bug804395.html":"bug 901343, specialpowers.wrap issue createsystemxhr",
+    "content/base/test/test_bug338583.html":"https not working, bug 907770",
 
     "content/base/test/test_bug475156.html":"36 total - bug 902611",
     "content/base/test/test_bug422403-1.html":"bug 901343, specialpowers.wrap issue [nsIChannel.open]",
 
     "content/base/test/test_child_process_shutdown_message.html":"specialpowers.wrap issue, NS_ERROR_XPC_GS_RETURNED_FAILURE",
     "content/base/test/test_messagemanager_assertpermission.html":"specialpowers.wrap issue, NS_ERROR_XPC_GS_RETURNED_FAILURE",
 
 
@@ -244,17 +243,16 @@
     "content/svg/content/test/test_text_selection.html":"Mouse selection not workin on b2g",
     "content/svg/content/test/test_SVGAnimatedImageSMILDisabled.html":"",
     "content/xml/document/test/test_bug392338.html":"",
     "content/base/test/csp/test_bothCSPheaders.html":"",
     "content/base/test/test_bug383430.html":"",
     "content/base/test/test_bug422403-2.xhtml":"",
     "content/base/test/test_bug424359-1.html":"",
     "content/base/test/test_bug424359-2.html":"",
-    "content/base/test/test_bug426308.html":"",
     "content/base/test/test_mixed_content_blocker_bug803225.html":"",
     "content/html/document/test/test_non-ascii-cookie.html":"",
 
     "docshell/test/navigation/test_bug13871.html":"",
     "docshell/test/navigation/test_bug270414.html":"",
     "docshell/test/navigation/test_bug344861.html":"",
     "docshell/test/navigation/test_bug386782.html":"",
     "docshell/test/navigation/test_not-opener.html":"",
@@ -316,17 +314,17 @@
     "dom/media/tests/mochitest/test_peerConnection_setLocalAnswerInStable.html":"",
     "dom/media/tests/mochitest/test_peerConnection_setLocalOfferInHaveRemoteOffer.html":"",
     "dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html":"",
     "dom/media/tests/mochitest/test_peerConnection_setRemoteAnswerInStable.html":"",
     "dom/media/tests/mochitest/test_peerConnection_setRemoteOfferInHaveLocalOffer.html":"",
     "dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html":"",
 
     "dom/network/tests/test_networkstats_basics.html":"Will be fixed in bug 858005",
-    "dom/permission/tests/test_permission_basics.html":"Bug 907770",
+    "dom/permission/tests/test_permission_basics.html":"https not working, bug 907770",
 
     "dom/tests/mochitest/bugs/test_bug335976.xhtml":"",
     "dom/tests/mochitest/bugs/test_bug369306.html":"test timed out, can't focus back from popup window to opener?",
     "dom/tests/mochitest/bugs/test_bug396843.html":"",
     "dom/tests/mochitest/bugs/test_bug406375.html":"",
 
     "dom/tests/mochitest/bugs/test_bug427744.html":"",
     "dom/tests/mochitest/bugs/test_bug641552.html":"",
--- a/testing/mochitest/roboextender/Makefile.in
+++ b/testing/mochitest/roboextender/Makefile.in
@@ -1,18 +1,30 @@
 #
 # 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/.
 
+DEPTH           = ../../..
+topsrcdir       = @top_srcdir@
+srcdir          = @srcdir@
+VPATH           = @srcdir@
+relativesrcdir  = testing/mochitest/roboextender
+TESTPATH        = $(topsrcdir)/mobile/android/base/tests/roboextender
+
+include $(DEPTH)/config/autoconf.mk
+
 _TEST_FILES = \
   bootstrap.js \
   install.rdf \
+  chrome.manifest \
   $(NULL)
 
 TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
 
 include $(topsrcdir)/config/rules.mk
 
 libs:: $(_TEST_FILES)
 	$(MKDIR) -p $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org
 	$(INSTALL) $(foreach f,$^,"$f") $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/
-	
+	$(MKDIR) -p $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/base
+	$(MKDIR) -p $(TESTPATH)
+	-cp $(TESTPATH)/* $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/base
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/roboextender/chrome.manifest
@@ -0,0 +1,1 @@
+content roboextender base/
\ No newline at end of file
--- a/testing/mochitest/tests/test_SpecialPowersExtension.html
+++ b/testing/mochitest/tests/test_SpecialPowersExtension.html
@@ -73,38 +73,33 @@ function starttest(){
   
   // Test Complex Pref - TODO: Without chrome access, I don't know how you'd actually
   // set this preference since you have to create an XPCOM object.
   // Leaving untested for now.
 
   // Test a DOMWindowUtils method and property
   is(SpecialPowers.DOMWindowUtils.getClassName(window), "Proxy");
   is(SpecialPowers.DOMWindowUtils.docCharsetIsForced, false);
-
-  //Run the createSystemXHR method
-  var xhr = SpecialPowers.createSystemXHR();
-  ok(xhr, "createSystemXHR should not return null");
-  is(xhr.readyState, XMLHttpRequest.UNSENT, "createSystemXHR should create an unsent XMLHttpRequest object");
   
   // QueryInterface and getPrivilegedProps tests
   is(SpecialPowers.can_QI(SpecialPowers), false);
   ok(SpecialPowers.can_QI(window));
   ok(SpecialPowers.do_QueryInterface(window, "nsIDOMWindow"));
   is(SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(window, "nsIDOMWindow"), "document.nodeName"), "#document");
   
   //try to run garbage collection
   SpecialPowers.gc();
 
   //
   // Test the SpecialPowers wrapper.
   //
 
   // Try some basic stuff with XHR.
   var xhr2 = SpecialPowers.Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(SpecialPowers.Ci.nsIXMLHttpRequest);
-  is(xhr.readyState, XMLHttpRequest.UNSENT, "Should be able to get props off privileged objects");
+  is(xhr2.readyState, XMLHttpRequest.UNSENT, "Should be able to get props off privileged objects");
   var testURI = SpecialPowers.Cc['@mozilla.org/network/standard-url;1']
                                               .createInstance(SpecialPowers.Ci.nsIURI);
   testURI.spec = "http://www.foobar.org/";
   is(testURI.spec, "http://www.foobar.org/", "Getters/Setters should work correctly");
   is(SpecialPowers.wrap(document).getElementsByTagName('details').length, 0, "Should work with proxy-based DOM bindings.");
 
   // Play with the window object.
   var webnav = SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -1173,20 +1173,16 @@ SpecialPowersAPI.prototype = {
 
   emulateMedium: function(window, mediaType) {
     this._getMUDV(window).emulateMedium(mediaType);
   },
   stopEmulatingMedium: function(window) {
     this._getMUDV(window).stopEmulatingMedium();
   },
 
-  createSystemXHR: function() {
-    return this.wrap(Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest));
-  },
-
   snapshotWindowWithOptions: function (win, rect, bgcolor, options) {
     var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
     if (rect === undefined) {
       rect = { top: win.scrollY, left: win.scrollX,
                width: win.innerWidth, height: win.innerHeight };
     }
     if (bgcolor === undefined) {
       bgcolor = "rgb(255,255,255)";
--- a/toolkit/components/build/Makefile.in
+++ b/toolkit/components/build/Makefile.in
@@ -23,16 +23,17 @@ SHARED_LIBRARY_LIBS = \
   ../find/$(LIB_PREFIX)mozfind_s.$(LIB_SUFFIX) \
   ../typeaheadfind/$(LIB_PREFIX)fastfind_s.$(LIB_SUFFIX) \
   ../startup/$(LIB_PREFIX)appstartup_s.$(LIB_SUFFIX) \
   ../statusfilter/$(LIB_PREFIX)mozbrwsr_s.$(LIB_SUFFIX) \
   ../downloads/$(LIB_PREFIX)download_s.$(LIB_SUFFIX) \
   ../jsdownloads/src/$(LIB_PREFIX)jsdownloads_s.$(LIB_SUFFIX) \
   ../protobuf/$(LIB_PREFIX)protobuf_s.$(LIB_SUFFIX) \
   ../intl/$(LIB_PREFIX)intl_s.$(LIB_SUFFIX) \
+  ../finalizationwitness/$(LIB_PREFIX)finalizationwitness_s.$(LIB_SUFFIX) \
   $(NULL)
 
 ifndef MOZ_DISABLE_PARENTAL_CONTROLS
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 SHARED_LIBRARY_LIBS += ../parentalcontrols/$(LIB_PREFIX)parentalcontrols_s.$(LIB_SUFFIX)
 LOCAL_INCLUDES += \
   -I$(srcdir)/../parentalcontrols \
   $(NULL)
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -29,16 +29,17 @@
 #include "ApplicationReputation.h"
 #include "nsUrlClassifierDBService.h"
 #include "nsUrlClassifierStreamUpdater.h"
 #include "nsUrlClassifierUtils.h"
 #include "nsUrlClassifierPrefixSet.h"
 #endif
 
 #include "nsBrowserStatusFilter.h"
+#include "mozilla/FinalizationWitnessService.h"
 
 /////////////////////////////////////////////////////////////////////////////
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUserInfo)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFindService)
 
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -80,16 +81,17 @@ nsUrlClassifierDBServiceConstructor(nsIS
     return rv;
 }
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
 #if defined(USE_MOZ_UPDATER)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
 #endif
+NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService)
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_DOWNLOADMANAGER_CID);
@@ -104,16 +106,17 @@ NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSE
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID);
 #if defined(USE_MOZ_UPDATER)
 NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
 #endif
+NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID);
 
 static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor },
   { &kNS_USERINFO_CID, false, nullptr, nsUserInfoConstructor },
   { &kNS_ALERTSSERVICE_CID, false, nullptr, nsAlertsServiceConstructor },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
   { &kNS_PARENTALCONTROLSSERVICE_CID, false, nullptr, nsParentalControlsServiceWinConstructor },
 #endif
@@ -129,16 +132,17 @@ static const mozilla::Module::CIDEntry k
   { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, nullptr, nsUrlClassifierStreamUpdaterConstructor },
   { &kNS_URLCLASSIFIERUTILS_CID, false, nullptr, nsUrlClassifierUtilsConstructor },
 #endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, nullptr, nsBrowserStatusFilterConstructor },
   { &kNS_CHARSETMENU_CID, false, nullptr, NS_NewCharsetMenu },
 #if defined(USE_MOZ_UPDATER)
   { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor },
 #endif
+  { &kFINALIZATIONWITNESSSERVICE_CID, false, NULL, FinalizationWitnessServiceConstructor },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -157,16 +161,17 @@ static const mozilla::Module::ContractID
   { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID },
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
 #endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
   { NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_CHARSETMENU_PID, &kNS_CHARSETMENU_CID },
 #if defined(USE_MOZ_UPDATER)
   { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
 #endif
+  { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID },
   { nullptr }
 };
 
 static const mozilla::Module kToolkitModule = {
   mozilla::Module::kVersion,
   kToolkitCIDs,
   kToolkitContracts
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.cpp
@@ -0,0 +1,213 @@
+/* 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/. */
+
+#include "FinalizationWitnessService.h"
+
+#include "nsString.h"
+#include "jsapi.h"
+#include "js/CallNonGenericMethod.h"
+#include "mozJSComponentLoader.h"
+#include "nsZipArchive.h"
+
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "mozilla/NullPtr.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+
+
+// Implementation of nsIFinalizationWitnessService
+
+namespace mozilla {
+
+namespace {
+
+/**
+ * An event meant to be dispatched to the main thread upon finalization
+ * of a FinalizationWitness, unless method |forget()| has been called.
+ *
+ * Held as private data by each instance of FinalizationWitness.
+ * Important note: we maintain the invariant that these private data
+ * slots are already addrefed.
+ */
+class FinalizationEvent MOZ_FINAL: public nsRunnable
+{
+public:
+  FinalizationEvent(const char* aTopic,
+                  const jschar* aValue)
+    : mTopic(aTopic)
+    , mValue(aValue)
+  { }
+
+  NS_METHOD Run() {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    if (!observerService) {
+      // This is either too early or, more likely, too late for notifications.
+      // Bail out.
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+    (void)observerService->
+      NotifyObservers(nullptr, mTopic.get(), mValue.get());
+    return NS_OK;
+  }
+private:
+  /**
+   * The topic on which to broadcast the notification of finalization.
+   *
+   * Deallocated on the main thread.
+   */
+  const nsCString mTopic;
+
+  /**
+   * The result of converting the exception to a string.
+   *
+   * Deallocated on the main thread.
+   */
+  const nsString mValue;
+};
+
+enum {
+  WITNESS_SLOT_EVENT,
+  WITNESS_INSTANCES_SLOTS
+};
+
+/**
+ * Extract the FinalizationEvent from an instance of FinalizationWitness
+ * and clear the slot containing the FinalizationEvent.
+ */
+already_AddRefed<FinalizationEvent>
+ExtractFinalizationEvent(JSObject *objSelf)
+{
+  JS::Value slotEvent = JS_GetReservedSlot(objSelf, WITNESS_SLOT_EVENT);
+  if (slotEvent.isUndefined()) {
+    // Forget() has been called
+    return nullptr;
+  }
+
+  JS_SetReservedSlot(objSelf, WITNESS_SLOT_EVENT, JS::UndefinedValue());
+
+  return dont_AddRef(static_cast<FinalizationEvent*>(slotEvent.toPrivate()));
+}
+
+/**
+ * Finalizer for instances of FinalizationWitness.
+ *
+ * Unless method Forget() has been called, the finalizer displays an error
+ * message.
+ */
+void Finalize(JSFreeOp *fop, JSObject *objSelf)
+{
+  nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
+  if (event == nullptr) {
+    // Forget() has been called
+    return;
+  }
+
+  // Notify observers. Since we are executed during garbage-collection,
+  // we need to dispatch the notification to the main thread.
+  (void)NS_DispatchToMainThread(event);
+  // We may fail at dispatching to the main thread if we arrive too late
+  // during shutdown. In that case, there is not much we can do.
+}
+
+static const JSClass sWitnessClass = {
+  "FinalizationWitness",
+  JSCLASS_HAS_RESERVED_SLOTS(WITNESS_INSTANCES_SLOTS),
+  JS_PropertyStub /* addProperty */,
+  JS_DeletePropertyStub /* delProperty */,
+  JS_PropertyStub /* getProperty */,
+  JS_StrictPropertyStub /* setProperty */,
+  JS_EnumerateStub /* enumerate */,
+  JS_ResolveStub /* resolve */,
+  JS_ConvertStub /* convert */,
+  Finalize /* finalize */
+};
+
+bool IsWitness(JS::Handle<JS::Value> v)
+{
+  return v.isObject() && JS_GetClass(&v.toObject()) == &sWitnessClass;
+}
+
+
+/**
+ * JS method |forget()|
+ *
+ * === JS documentation
+ *
+ *  Neutralize the witness. Once this method is called, the witness will
+ *  never report any error.
+ */
+bool ForgetImpl(JSContext* cx, JS::CallArgs args)
+{
+  if (args.length() != 0) {
+    JS_ReportError(cx, "forget() takes no arguments");
+    return false;
+  }
+  JS::Rooted<JS::Value> valSelf(cx, args.thisv());
+  JS::Rooted<JSObject*> objSelf(cx, &valSelf.toObject());
+
+  nsRefPtr<FinalizationEvent> event = ExtractFinalizationEvent(objSelf);
+  if (event == nullptr) {
+    JS_ReportError(cx, "forget() called twice");
+    return false;
+  }
+
+  args.rval().setUndefined();
+  return true;
+}
+
+bool Forget(JSContext *cx, unsigned argc, JS::Value *vp)
+{
+  JS::CallArgs args = CallArgsFromVp(argc, vp);
+  return JS::CallNonGenericMethod<IsWitness, ForgetImpl>(cx, args);
+}
+
+static const JSFunctionSpec sWitnessClassFunctions[] = {
+  JS_FN("forget", Forget, 0, JSPROP_READONLY | JSPROP_PERMANENT),
+  JS_FS_END
+};
+
+}
+
+NS_IMPL_ISUPPORTS1(FinalizationWitnessService, nsIFinalizationWitnessService)
+
+/**
+ * Create a new Finalization Witness.
+ *
+ * A finalization witness is an object whose sole role is to notify
+ * observers when it is gc-ed. Once the witness is created, call its
+ * method |forget()| to prevent the observers from being notified.
+ *
+ * @param aTopic The notification topic.
+ * @param aValue The notification value. Converted to a string.
+ *
+ * @constructor
+ */
+NS_IMETHODIMP
+FinalizationWitnessService::Make(const char* aTopic,
+                                 const PRUnichar* aValue,
+                                 JSContext* aCx,
+                                 JS::Value *aRetval) {
+  MOZ_ASSERT(aRetval);
+
+  JS::Rooted<JSObject*> objResult(aCx, JS_NewObject(aCx, &sWitnessClass, nullptr, nullptr));
+  if (!objResult) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  if (!JS_DefineFunctions(aCx, objResult, sWitnessClassFunctions)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<FinalizationEvent> event = new FinalizationEvent(aTopic, aValue);
+
+  // Transfer ownership of the addrefed |event| to |objResult|.
+  JS_SetReservedSlot(objResult, WITNESS_SLOT_EVENT,
+                     JS::PrivateValue(event.forget().get()));
+
+  aRetval->setObject(*objResult);
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/FinalizationWitnessService.h
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#ifndef mozilla_finalizationwitnessservice_h__
+#define mozilla_finalizationwitnessservice_h__
+
+#include "nsIFinalizationWitnessService.h"
+
+namespace mozilla {
+
+/**
+ * XPConnect initializer, for use in the main thread.
+ */
+class FinalizationWitnessService MOZ_FINAL : public nsIFinalizationWitnessService
+{
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIFINALIZATIONWITNESSSERVICE
+ private:
+  void operator=(const FinalizationWitnessService* other) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_finalizationwitnessservice_h__
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MODULE = 'finalizationwitness'
+
+CPP_SOURCES += [
+  'FinalizationWitnessService.cpp',
+]
+
+XPIDL_SOURCES += [
+  'nsIFinalizationWitnessService.idl',
+]
+
+XPIDL_MODULE = 'toolkit_finalizationwitness'
+
+EXPORTS.mozilla += [
+    'FinalizationWitnessService.h',
+]
+
+LOCAL_INCLUDES += [
+    '/js/xpconnect/loader',
+]
+
+LIBRARY_NAME = 'finalizationwitness_s'
+LIBXUL_LIBRARY = True
new file mode 100644
--- /dev/null
+++ b/toolkit/components/finalizationwitness/nsIFinalizationWitnessService.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=40: */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+
+[scriptable, uuid(15686f9d-483e-4361-98cd-37f1e8f1e61d)]
+interface nsIFinalizationWitnessService: nsISupports
+{
+  /**
+   * Create a new Finalization Witness.
+   *
+   * A finalization witness is an object whose sole role is to
+   * broadcast when it is garbage-collected. Once the witness is
+   * created, call method its method |forget()| to prevent the
+   * broadcast.
+   *
+   * @param aTopic The topic that the witness will broadcast using
+   *               Services.obs.
+   * @param aString The string that the witness will broadcast.
+   * @return An object with a single method |forget()|.
+   */
+  [implicit_jscontext]
+  jsval make(in string aTopic, in wstring aString);
+};
+
+%{ C++
+
+#define FINALIZATIONWITNESSSERVICE_CID {0x15686f9d,0x483e,0x4361,{0x98,0xcd,0x37,0xf1,0xe8,0xf1,0xe6,0x1d}}
+#define FINALIZATIONWITNESSSERVICE_CONTRACTID "@mozilla.org/toolkit/finalizationwitness;1"
+
+%}
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -15,16 +15,17 @@ PARALLEL_DIRS += [
     'commandlines',
     'console',
     'contentprefs',
     'cookie',
     'diskspacewatcher',
     'downloads',
     'exthelper',
     'filepicker',
+    'finalizationwitness',
     'find',
     'intl',
     'jsdownloads',
     'mediasniffer',
     'microformats',
     'osfile',
     'parentalcontrols',
     'passwordmgr',
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -94,57 +94,19 @@ if (!("localProfileDir" in OS.Constants.
         // Ignore errors: localProfileDir is still not available
       }
       return path;
     }
   });
 }
 
 /**
- * A global constant used as a default refs parameter value when cloning.
- */
-const noRefs = [];
-
-/**
  * Return a shallow clone of the enumerable properties of an object.
- *
- * Utility used whenever normalizing options requires making (shallow)
- * changes to an option object. The copy ensures that we do not modify
- * a client-provided object by accident.
- *
- * Note: to reference and not copy specific fields, provide an optional
- * |refs| argument containing their names.
- *
- * @param {JSON} object Options to be cloned.
- * @param {Array} refs An optional array of field names to be passed by
- * reference instead of copying.
  */
-let clone = function clone(object, refs = noRefs) {
-  let result = {};
-  // Make a reference between result[key] and object[key].
-  let refer = function refer(result, key, object) {
-    Object.defineProperty(result, key, {
-        enumerable: true,
-        get: function() {
-            return object[key];
-        },
-        set: function(value) {
-            object[key] = value;
-        }
-    });
-  };
-  for (let k in object) {
-    if (refs.indexOf(k) < 0) {
-      result[k] = object[k];
-    } else {
-      refer(result, k, object);
-    }
-  }
-  return result;
-};
+let clone = SharedAll.clone;
 
 let worker = new PromiseWorker(
   "resource://gre/modules/osfile/osfile_async_worker.js", LOG);
 let Scheduler = {
   /**
    * |true| once we have sent at least one message to the worker.
    */
   launched: false,
@@ -416,19 +378,18 @@ File.prototype = {
    * @return {promise}
    * @resolves {number} The number of bytes effectively read.
    * @rejects {OS.File.Error}
    */
   readTo: function readTo(buffer, options = {}) {
     // If |buffer| is a typed array and there is no |bytes| options, we
     // need to extract the |byteLength| now, as it will be lost by
     // communication
-    if (isTypedArray(buffer) && (!options || !("bytes" in options))) {
-      // Preserve the reference to |outExecutionDuration| option if it is
-      // passed.
+    if (isTypedArray(buffer) && !("bytes" in options)) {
+      // Preserve reference to option |outExecutionDuration|, if it is passed.
       options = clone(options, ["outExecutionDuration"]);
       options.bytes = buffer.byteLength;
     }
     // Note: Type.void_t.out_ptr.toMsg ensures that
     // - the buffer is effectively shared (not neutered) between both
     //   threads;
     // - we take care of any |byteOffset|.
     return Scheduler.post("File_prototype_readTo",
@@ -454,19 +415,18 @@ File.prototype = {
    * if |buffer| is a C pointer.
    *
    * @return {number} The number of bytes actually written.
    */
   write: function write(buffer, options = {}) {
     // If |buffer| is a typed array and there is no |bytes| options,
     // we need to extract the |byteLength| now, as it will be lost
     // by communication
-    if (isTypedArray(buffer) && (!options || !("bytes" in options))) {
-      // Preserve the reference to |outExecutionDuration| option if it is
-      // passed.
+    if (isTypedArray(buffer)) {
+      // Preserve reference to option |outExecutionDuration|, if it is passed.
       options = clone(options, ["outExecutionDuration"]);
       options.bytes = buffer.byteLength;
     }
     // Note: Type.void_t.out_ptr.toMsg ensures that
     // - the buffer is effectively shared (not neutered) between both
     //   threads;
     // - we take care of any |byteOffset|.
     return Scheduler.post("File_prototype_write",
@@ -477,23 +437,24 @@ File.prototype = {
   },
 
   /**
    * Read bytes from this file to a new buffer.
    *
    * @param {number=} bytes If unspecified, read all the remaining bytes from
    * this file. If specified, read |bytes| bytes, or less if the file does not
    * contain that many bytes.
+   * @param {JSON} options
    * @return {promise}
    * @resolves {Uint8Array} An array containing the bytes read.
    */
-  read: function read(nbytes) {
+  read: function read(nbytes, options = {}) {
     let promise = Scheduler.post("File_prototype_read",
       [this._fdmsg,
-       nbytes]);
+       nbytes, options]);
     return promise.then(
       function onSuccess(data) {
         return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
       });
   },
 
   /**
    * Return the current position in the file, as bytes.
@@ -688,16 +649,21 @@ File.makeDir = function makeDir(path, op
 
 /**
  * Return the contents of a file
  *
  * @param {string} path The path to the file.
  * @param {number=} bytes Optionally, an upper bound to the number of bytes
  * to read.
  * @param {JSON} options Additional options.
+ * - {boolean} sequential A flag that triggers a population of the page cache
+ * with data from a file so that subsequent reads from that file would not
+ * block on disk I/O. If |true| or unspecified, inform the system that the
+ * contents of the file will be read in order. Otherwise, make no such
+ * assumption. |true| by default.
  *
  * @resolves {Uint8Array} A buffer holding the bytes
  * read from the file.
  */
 File.read = function read(path, bytes, options) {
   let promise = Scheduler.post("read",
     [Type.path.toMsg(path), bytes, options], path);
   return promise.then(
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -266,17 +266,17 @@ if (this.Components) {
      let file = File.open(filePath, mode, options);
      return OpenedFiles.add(file, {
        // Adding path information to keep track of opened files
        // to report leaks when debugging.
        path: filePath
      });
    },
    read: function read(path, bytes, options) {
-     let data = File.read(Type.path.fromMsg(path), bytes);
+     let data = File.read(Type.path.fromMsg(path), bytes, options);
      return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]);
    },
    exists: function exists(path) {
      return File.exists(Type.path.fromMsg(path));
    },
    writeAtomic: function writeAtomic(path, buffer, options) {
      if (options.tmpPath) {
        options.tmpPath = Type.path.fromMsg(options.tmpPath);
--- a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
@@ -29,16 +29,17 @@ if (typeof Components != "undefined") {
   const Ci = Components.interfaces;
   const Cc = Components.classes;
 
   Cu.import("resource://gre/modules/Services.jsm", this);
 }
 
 let EXPORTED_SYMBOLS = [
   "LOG",
+  "clone",
   "Config",
   "Constants",
   "Type",
   "HollowStructure",
   "OSError",
   "declareFFI",
   "projectValue",
   "isTypedArray",
@@ -157,16 +158,56 @@ let LOG = function (...args) {
       Services.console.logStringMessage(message + "\n");
     };
   }
   logFunc.apply(null, [stringifyArg(arg) for (arg of args)]);
 };
 
 exports.LOG = LOG;
 
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ *
+ * Utility used whenever normalizing options requires making (shallow)
+ * changes to an option object. The copy ensures that we do not modify
+ * a client-provided object by accident.
+ *
+ * Note: to reference and not copy specific fields, provide an optional
+ * |refs| argument containing their names.
+ *
+ * @param {JSON} object Options to be cloned.
+ * @param {Array} refs An optional array of field names to be passed by
+ * reference instead of copying.
+ */
+let clone = function (object, refs = []) {
+  let result = {};
+  // Make a reference between result[key] and object[key].
+  let refer = function refer(result, key, object) {
+    Object.defineProperty(result, key, {
+      enumerable: true,
+      get: function() {
+        return object[key];
+      },
+      set: function(value) {
+        object[key] = value;
+      }
+    });
+  };
+  for (let k in object) {
+    if (refs.indexOf(k) < 0) {
+      result[k] = object[k];
+    } else {
+      refer(result, k, object);
+    }
+  }
+  return result;
+};
+
+exports.clone = clone;
+
 ///////////////////// Abstractions above js-ctypes
 
 /**
  * Abstraction above js-ctypes types.
  *
  * Use values of this type to register FFI functions. In addition to the
  * usual features of js-ctypes, values of this type perform the necessary
  * transformations to ensure that C errors are handled nicely, to connect
@@ -969,16 +1010,17 @@ exports.OSError = OSError;
 ///////////////////// Temporary boilerplate
 // Boilerplate, to simplify the transition to require()
 // Do not rely upon this symbol, it will disappear with
 // bug 883050.
 exports.OS = {
   Constants: exports.Constants,
   Shared: {
     LOG: LOG,
+    clone: clone,
     Type: Type,
     HollowStructure: HollowStructure,
     Error: OSError,
     declareFFI: declareFFI,
     projectValue: projectValue,
     isTypedArray: isTypedArray,
     defineLazyGetter: defineLazyGetter,
     offsetBy: offsetBy
@@ -1010,9 +1052,8 @@ Object.defineProperty(exports.OS.Shared,
 
 ///////////////////// Permanent boilerplate
 if (typeof Components != "undefined") {
   this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
   for (let symbol of EXPORTED_SYMBOLS) {
     this[symbol] = exports[symbol];
   }
 }
-
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -12,16 +12,17 @@
 if (typeof Components != "undefined") {
   throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
 }
 (function(exports) {
 
 exports.OS = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm").OS;
 
 let LOG = exports.OS.Shared.LOG.bind(OS.Shared, "Shared front-end");
+let clone = exports.OS.Shared.clone;
 
 /**
  * Code shared by implementations of File.
  *
  * @param {*} fd An OS-specific file handle.
  * @constructor
  */
 let AbstractFile = function AbstractFile(fd) {
@@ -39,27 +40,27 @@ AbstractFile.prototype = {
       return this._fd;
     }
     throw OS.File.Error.closed();
   },
   /**
    * Read bytes from this file to a new buffer.
    *
    * @param {number=} bytes If unspecified, read all the remaining bytes from
-   * this file. If specified, read |bytes| bytes, or less if the file does not
+   * this file. If specified, read |bytes| bytes, or less if the file does notclone
    * contain that many bytes.
+   * @param {JSON} options
    * @return {Uint8Array} An array containing the bytes read.
    */
-  read: function read(bytes) {
-    if (bytes == null) {
-      bytes = this.stat().size;
-    }
-    let buffer = new Uint8Array(bytes);
-    let size = this.readTo(buffer, {bytes: bytes});
-    if (size == bytes) {
+  read: function read(bytes, options = {}) {
+    options = clone(options);
+    options.bytes = bytes == null ? this.stat().size : bytes;
+    let buffer = new Uint8Array(options.bytes);
+    let size = this.readTo(buffer, options);
+    if (size == options.bytes) {
       return buffer;
     } else {
       return buffer.subarray(0, size);
     }
   },
 
   /**
    * Read bytes from this file to an existing buffer.
@@ -287,24 +288,25 @@ AbstractFile.normalizeOpenMode = functio
 };
 
 /**
  * Return the contents of a file.
  *
  * @param {string} path The path to the file.
  * @param {number=} bytes Optionally, an upper bound to the number of bytes
  * to read.
+ * @param {JSON} options Optionally contains additional options.
  *
  * @return {Uint8Array} A buffer holding the bytes
  * and the number of bytes read from the file.
  */
-AbstractFile.read = function read(path, bytes) {
+AbstractFile.read = function read(path, bytes, options = {}) {
   let file = exports.OS.File.open(path);
   try {
-    return file.read(bytes);
+    return file.read(bytes, options);
   } finally {
     file.close();
   }
 };
 
 /**
  * Write a file, atomically.
  *
--- a/toolkit/components/osfile/modules/osfile_unix_back.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_back.jsm
@@ -442,16 +442,24 @@
 
        UnixFile.read =
          declareFFI("read", ctypes.default_abi,
                     /*return*/Types.negativeone_or_ssize_t,
                     /*fd*/    Types.fd,
                     /*buf*/   Types.void_t.out_ptr,
                     /*nbytes*/Types.size_t);
 
+       UnixFile.posix_fadvise =
+         declareFFI("posix_fadvise", ctypes.default_abi,
+                    /*return*/ Types.int,
+                    /*fd*/     Types.fd,
+                    /*offset*/ Types.off_t,
+                    /*len*/    Types.off_t,
+                    /*advise*/ Types.int);
+
        if (OS.Constants.libc._DARWIN_FEATURE_64_BIT_INODE) {
          // Special case for MacOS X 10.5+
          // Symbol name "readdir" still exists but is used for a
          // deprecated function that does not match the
          // constants of |OS.Constants.libc|.
          UnixFile.readdir =
            declareFFI("readdir$INODE64", ctypes.default_abi,
                      /*return*/Types.null_or_dirent_ptr,
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -89,16 +89,23 @@
       * @param {*=} options Additional options for reading. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively read. If zero,
       * the end of the file has been reached.
       * @throws {OS.File.Error} In case of I/O error.
       */
      File.prototype._read = function _read(buffer, nbytes, options) {
+      // Populate the page cache with data from a file so the subsequent reads
+      // from that file will not block on disk I/O.
+       if (typeof(UnixFile.posix_fadvise) === 'function' &&
+           (options.sequential || !("sequential" in options))) {
+         UnixFile.posix_fadvise(this.fd, 0, nbytes,
+          OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
+       }
        return throw_on_negative("read",
          UnixFile.read(this.fd, buffer, nbytes)
        );
      };
 
      /**
       * Write some bytes to a file.
       *
--- a/toolkit/modules/Promise.jsm
+++ b/toolkit/modules/Promise.jsm
@@ -93,32 +93,154 @@ this.EXPORTED_SYMBOLS = [
 //// Globals
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const STATUS_PENDING = 0;
 const STATUS_RESOLVED = 1;
 const STATUS_REJECTED = 2;
 
 // These "private names" allow some properties of the Promise object to be
 // accessed only by this module, while still being visible on the object
 // manually when using a debugger.  They don't strictly guarantee that the
 // properties are inaccessible by other code, but provide enough protection to
 // avoid using them by mistake.
 const salt = Math.floor(Math.random() * 100);
 const Name = (n) => "{private:" + n + ":" + salt + "}";
 const N_STATUS = Name("status");
 const N_VALUE = Name("value");
 const N_HANDLERS = Name("handlers");
+const N_WITNESS = Name("witness");
 
+
+/////// Warn-upon-finalization mechanism
+//
+// One of the difficult problems with promises is locating uncaught
+// rejections. We adopt the following strategy: if a promise is rejected
+// at the time of its garbage-collection *and* if the promise is at the
+// end of a promise chain (i.e. |thatPromise.then| has never been
+// called), then we print a warning.
+//
+//  let deferred = Promise.defer();
+//  let p = deferred.promise.then();
+//  deferred.reject(new Error("I am un uncaught error"));
+//  deferred = null;
+//  p = null;
+//
+// In this snippet, since |deferred.promise| is not the last in the
+// chain, no error will be reported for that promise. However, since
+// |p| is the last promise in the chain, the error will be reported
+// for |p|.
+//
+// Note that this may, in some cases, cause an error to be reported more
+// than once. For instance, consider:
+//
+//   let deferred = Promise.defer();
+//   let p1 = deferred.promise.then();
+//   let p2 = deferred.promise.then();
+//   deferred.reject(new Error("I am an uncaught error"));
+//   p1 = p2 = deferred = null;
+//
+// In this snippet, the error is reported both by p1 and by p2.
+//
+
+XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService",
+                                   "@mozilla.org/toolkit/finalizationwitness;1",
+                                   "nsIFinalizationWitnessService");
+
+let PendingErrors = {
+  _counter: 0,
+  _map: new Map(),
+  register: function(error) {
+    let id = "pending-error-" + (this._counter++);
+    //
+    // At this stage, ideally, we would like to store the error itself
+    // and delay any treatment until we are certain that we will need
+    // to report that error. However, in the (unlikely but possible)
+    // case the error holds a reference to the promise itself, doing so
+    // would prevent the promise from being garbabe-collected, which
+    // would both cause a memory leak and ensure that we cannot report
+    // the uncaught error.
+    //
+    // To avoid this situation, we rather extract relevant data from
+    // the error and further normalize it to strings.
+    //
+    let value = {
+      date: new Date(),
+      message: "" + error,
+      fileName: null,
+      stack: null,
+      lineNumber: null
+    };
+    try { // Defend against non-enumerable values
+      if (typeof error == "object" && error) {
+        for (let k of ["fileName", "stack", "lineNumber"]) {
+          try { // Defend against fallible getters and string conversions
+            let v = error[k];
+            value[k] = v ? ("" + v):null;
+          } catch (ex) {
+            // Ignore field
+          }
+        }
+      }
+    } catch (ex) {
+      // Ignore value
+    }
+    this._map.set(id, value);
+    return id;
+  },
+  extract: function(id) {
+    let value = this._map.get(id);
+    this._map.delete(id);
+    return value;
+  },
+  unregister: function(id) {
+    this._map.delete(id);
+  }
+};
+
+// Actually print the finalization warning.
+Services.obs.addObserver(function observe(aSubject, aTopic, aValue) {
+  let error = PendingErrors.extract(aValue);
+  let {message, date, fileName, stack, lineNumber} = error;
+  let error = Cc['@mozilla.org/scripterror;1'].createInstance(Ci.nsIScriptError);
+  if (!error || !Services.console) {
+    // Too late during shutdown to use the nsIConsole
+    dump("*************************\n");
+    dump("A promise chain failed to handle a rejection\n\n");
+    dump("On: " + date + "\n");
+    dump("Full message: " + message + "\n");
+    dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n");
+    dump("Full stack: " + (stack||"not available") + "\n");
+    dump("*************************\n");
+    return;
+  }
+  if (stack) {
+    message += " at " + stack;
+  }
+  error.init(
+             /*message*/"A promise chain failed to handle a rejection: on " +
+               date + ", " + message,
+             /*sourceName*/ fileName,
+             /*sourceLine*/ lineNumber?("" + lineNumber):0,
+             /*lineNumber*/ lineNumber || 0,
+             /*columnNumber*/ 0,
+             /*flags*/ Ci.nsIScriptError.errorFlag,
+             /*category*/ "chrome javascript");
+  Services.console.logMessage(error);
+}, "promise-finalization-witness", false);
+
+///////// Additional warnings for developers
+//
 // The following error types are considered programmer errors, which should be
 // reported (possibly redundantly) so as to let programmers fix their code.
 const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError", "TypeError"];
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Promise
 
 /**
@@ -278,16 +400,23 @@ this.PromiseWalker = {
       return;
     }
 
     // Change the promise status and schedule our handlers for processing.
     aPromise[N_STATUS] = aStatus;
     aPromise[N_VALUE] = aValue;
     if (aPromise[N_HANDLERS].length > 0) {
       this.schedulePromise(aPromise);
+    } else if (aStatus == STATUS_REJECTED) {
+      // This is a rejection and the promise is the last in the chain.
+      // For the time being we therefore have an uncaught error.
+      let id = PendingErrors.register(aValue);
+      let witness =
+          FinalizationWitnessService.make("promise-finalization-witness", id);
+      aPromise[N_WITNESS] = [id, witness];
     }
   },
 
   /**
    * Sets up the PromiseWalker loop to start on the next tick of the event loop
    */
   scheduleWalkerLoop: function()
   {
@@ -451,16 +580,25 @@ function PromiseImpl()
   Object.defineProperty(this, N_VALUE, { writable: true });
 
   /*
    * Array of Handler objects registered by the "then" method, and not processed
    * yet.  Handlers are removed when the promise is resolved or rejected.
    */
   Object.defineProperty(this, N_HANDLERS, { value: [] });
 
+  /**
+   * When the N_STATUS property is STATUS_REJECTED and until there is
+   * a rejection callback, this contains an array
+   * - {string} id An id for use with |PendingErrors|;
+   * - {FinalizationWitness} witness A witness broadcasting |id| on
+   *   notification "promise-finalization-witness".
+   */
+  Object.defineProperty(this, N_WITNESS, { writable: true });
+
   Object.seal(this);
 }
 
 PromiseImpl.prototype = {
   /**
    * Calls one of the provided functions as soon as this promise is either
    * resolved or rejected.  A new promise is returned, whose state evolves
    * depending on this promise and the provided callback functions.
@@ -506,16 +644,25 @@ PromiseImpl.prototype = {
   then: function (aOnResolve, aOnReject)
   {
     let handler = new Handler(this, aOnResolve, aOnReject);
     this[N_HANDLERS].push(handler);
 
     // Ensure the handler is scheduled for processing if this promise is already
     // resolved or rejected.
     if (this[N_STATUS] != STATUS_PENDING) {
+
+      // This promise is not the last in the chain anymore. Remove any watchdog.
+      if (this[N_WITNESS] != null) {
+        let [id, witness] = this[N_WITNESS];
+        this[N_WITNESS] = null;
+        witness.forget();
+        PendingErrors.unregister(id);
+      }
+
       PromiseWalker.schedulePromise(this);
     }
 
     return handler.nextPromise;
   },
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -583,22 +730,25 @@ Handler.prototype = {
           ERRORS_TO_REPORT.indexOf(ex.name) != -1) {
 
         // We suspect that the exception is a programmer error, so we now
         // display it using dump().  Note that we do not use Cu.reportError as
         // we assume that this is a programming error, so we do not want end
         // users to see it. Also, if the programmer handles errors correctly,
         // they will either treat the error or log them somewhere.
 
+        dump("*************************\n");
         dump("A coding exception was thrown in a Promise " +
              ((nextStatus == STATUS_RESOLVED) ? "resolution":"rejection") +
-             " callback.\n");
+             " callback.\n\n");
         dump("Full message: " + ex + "\n");
         dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n");
         dump("Full stack: " + (("stack" in ex)?ex.stack:"not available") + "\n");
+        dump("*************************\n");
+
       }
 
       // Additionally, reject the next promise.
       nextStatus = STATUS_REJECTED;
       nextValue = ex;
     }
 
     // Propagate the newly determined state to the next promise.
--- a/toolkit/modules/Task.jsm
+++ b/toolkit/modules/Task.jsm
@@ -286,16 +286,18 @@ TaskImpl.prototype = {
 
       // We suspect that the exception is a programmer error, so we now
       // display it using dump().  Note that we do not use Cu.reportError as
       // we assume that this is a programming error, so we do not want end
       // users to see it. Also, if the programmer handles errors correctly,
       // they will either treat the error or log them somewhere.
 
       let stack = ("stack" in aException) ? aException.stack : "not available";
-      dump("A coding exception was thrown and uncaught in a Task.\n");
+      dump("*************************\n");
+      dump("A coding exception was thrown and uncaught in a Task.\n\n");
       dump("Full message: " + aException + "\n");
       dump("Full stack: " + stack + "\n");
+      dump("*************************\n");
     }
 
     this.deferred.reject(aException);
   }
 };
--- a/toolkit/modules/tests/xpcshell/test_Promise.js
+++ b/toolkit/modules/tests/xpcshell/test_Promise.js
@@ -28,52 +28,52 @@ let run_promise_tests = function run_pro
     let result = test();
     result.then(next, next);
   };
   return loop(0);
 };
 
 let make_promise_test = function(test) {
   return function runtest() {
-    do_print("Test starting: " + test);
+    do_print("Test starting: " + test.name);
     try {
       let result = test();
       if (result && "promise" in result) {
         result = result.promise;
       }
       if (!result || !("then" in result)) {
         let exn;
         try {
-          do_throw("Test " + test + " did not return a promise: " + result);
+          do_throw("Test " + test.name + " did not return a promise: " + result);
         } catch (x) {
           exn = x;
         }
         return Promise.reject(exn);
       }
       // The test returns a promise
       result = result.then(
         // Test complete
         function onResolve() {
-          do_print("Test complete: " + test);
+          do_print("Test complete: " + test.name);
         },
         // The test failed with an unexpected error
         function onReject(err) {
           let detail;
           if (err && typeof err == "object" && "stack" in err) {
             detail = err.stack;
           } else {
             detail = "(no stack)";
           }
-          do_throw("Test " + test + " rejected with the following reason: "
+          do_throw("Test " + test.name + " rejected with the following reason: "
               + err + detail);
       });
       return result;
     } catch (x) {
       // The test failed because of an error outside of a promise
-      do_throw("Error in body of test " + test + ": " + x + " at " + x.stack);
+      do_throw("Error in body of test " + test.name + ": " + x + " at " + x.stack);
       return Promise.reject();
     }
   };
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Tests
 
@@ -709,14 +709,132 @@ tests.push(
     do_print("Setting wait for second promise");
     return promise2.then(null, error => {return 3;})
     .then(
       count => {
         shouldExitNestedEventLoop = true;
       });
   }));
 
+function wait_for_uncaught(aMustAppear, aTimeout = undefined) {
+  let remaining = new Set();
+  for (let k of aMustAppear) {
+    remaining.add(k);
+  }
+  let deferred = Promise.defer();
+  let print = do_print;
+  let execute_soon = do_execute_soon;
+  let observer = function(aMessage) {
+    execute_soon(function() {
+      let message = aMessage.message;
+      print("Observing " + message);
+      for (let expected of remaining) {
+        if (message.indexOf(expected) != -1) {
+          print("I found " + expected);
+          remaining.delete(expected);
+        }
+      }
+      if (remaining.size == 0 && observer) {
+        Services.console.unregisterListener(observer);
+        observer = null;
+        deferred.resolve();
+      }
+    });
+  };
+  Services.console.registerListener(observer);
+  if (aTimeout) {
+    do_timeout(aTimeout, function timeout() {
+      if (observer) {
+        Services.console.unregisterListener(observer);
+        observer = null;
+      }
+      deferred.reject(new Error("Timeout"));
+    });
+  }
+  return deferred.promise;
+}
+
+// Test that uncaught errors are reported as uncaught
+(function() {
+  let make_string_rejection = function make_string_rejection() {
+    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
+    let string = "This is an uncaught rejection " + salt;
+    return {mustFind: [string], error: string};
+  };
+  let make_num_rejection = function make_num_rejection() {
+    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
+    return {mustFind: [salt], error: salt};
+  };
+  let make_undefined_rejection = function make_undefined_rejection() {
+    return {mustFind: [], error: undefined};
+  };
+  let make_error_rejection = function make_error_rejection() {
+    let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
+    let error = new Error("This is an uncaught error " + salt);
+    return {
+      mustFind: [error.message, error.fileName, error.lineNumber, error.stack],
+      error: error
+    };
+  };
+  for (let make_rejection of [make_string_rejection,
+    make_num_rejection,
+    make_undefined_rejection,
+    make_error_rejection]) {
+      let {mustFind, error} = make_rejection();
+      let name = make_rejection.name;
+      tests.push(make_promise_test(function test_uncaught_is_reported() {
+        do_print("Testing with rejection " + name);
+        let promise = wait_for_uncaught(mustFind);
+        (function() {
+          // For the moment, we cannot be absolutely certain that a value is
+          // garbage-collected, even if it is not referenced anymore, due to
+          // the conservative stack-scanning algorithm.
+          //
+          // To be _almost_ certain that a value will be garbage-collected, we
+          // 1. isolate that value in an anonymous closure;
+          // 2. allocate 100 values instead of 1 (gc-ing a single value from
+          //    these is sufficient for the test);
+          // 3. place everything in a loop, as the JIT typically reuses memory;
+          // 4. call all the GC methods we can.
+          //
+          // Unfortunately, we might still have intermittent failures,
+          // materialized as timeouts.
+          //
+          for (let i = 0; i < 100; ++i) {
+            Promise.reject(error);
+          }
+        })();
+        Components.utils.forceGC();
+        Components.utils.forceCC();
+        Components.utils.forceShrinkingGC();
+        return promise;
+      }));
+  }
+})();
+
+
+// Test that caught errors are not reported as uncaught
+tests.push(
+make_promise_test(function test_caught_is_not_reported() {
+  let salt = (Math.random() * ( Math.pow(2, 24) - 1 ));
+  let promise = wait_for_uncaught([salt], 500);
+  (function() {
+    let uncaught = Promise.reject("This error, on the other hand, is caught " + salt);
+    uncaught.then(null, function() { /* ignore rejection */});
+    uncaught = null;
+  })();
+  // Isolate this in a function to increase likelihood that the gc will
+  // realise that |uncaught| has remained uncaught.
+  Components.utils.forceGC();
+
+  return promise.then(function onSuccess() {
+    throw new Error("This error was caught and should not have been reported");
+  }, function onError() {
+    do_print("The caught error was not reported, all is fine");
+  }
+  );
+}));
 
 function run_test()
 {
   do_test_pending();
   run_promise_tests(tests, do_test_finished);
 }
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -15,8 +15,9 @@ support-files =
 [test_Promise.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_task.js]
 [test_TelemetryTimestamps.js]
 [test_timer.js]
+