Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Thu, 01 May 2014 18:16:02 -0700
changeset 181683 b673caba0c8ed7dac816da4560f0b1a06f77aa5f
parent 181628 e8afdde798c3369e1efbc88c252fb27b7aa6c7c5 (current diff)
parent 181682 ac0e2abada090fc1c7eac3663814fc5d1d17850d (diff)
child 181703 e2e1b19fcffccba73d438fd89bea963a2d78bb57
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone32.0a1
Merge fx-team to m-c
browser/themes/linux/devtools/font-inspector.css
browser/themes/osx/devtools/font-inspector.css
browser/themes/windows/devtools/font-inspector.css
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -323,16 +323,19 @@ skip-if = e10s # Bug ?????? - uncaught e
 [browser_offlineQuotaNotification.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {...})
 [browser_overflowScroll.js]
 skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_pageInfo.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_page_style_menu.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
+
+[browser_parsable_css.js]
+
 [browser_pinnedTabs.js]
 skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_plainTextLinks.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (creates and fetches elements directly from content document)
 [browser_popupNotification.js]
 skip-if = toolkit == "windows" || e10s # Disabled on Windows due to frequent failures (bugs 825739, 841341) / e10s - Bug ?????? - popup notification test probably confused re content process notifications etc
 [browser_popupUI.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -0,0 +1,216 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' CSS issues to remain, while we
+ * detect newly occurring issues in shipping CSS. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * Every property of the objects in it needs to consist of a regular expression
+ * matching the offending error. If an object has multiple regex criteria, they
+ * ALL need to match an error in order for that error not to cause a test
+ * failure. */
+const kWhitelist = [
+  {sourceName: /cleopatra.*(tree|ui)\.css/i}, /* Cleopatra is imported as-is, see bug 1004421 */
+  {sourceName: /codemirror\.css/i}, /* CodeMirror is imported as-is, see bug 1004423 */
+  {sourceName: /web\/viewer\.css/i, errorMessage: /Unknown pseudo-class.*(fullscreen|selection)/i }, /* PDFjs is futureproofing its pseudoselectors, and those rules are dropped. */
+  {sourceName: /aboutaccounts\/(main|normalize)\.css/i}, /* Tracked in bug 1004428 */
+];
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in kWhitelist
+ *
+ * @param aErrorObject the error to check
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(aErrorObject) {
+  for (let whitelistItem of kWhitelist) {
+    let matches = true;
+    for (let prop in whitelistItem) {
+      if (!whitelistItem[prop].test(aErrorObject[prop] || "")) {
+        matches = false;
+        break;
+      }
+    }
+    if (matches) {
+      return true;
+    }
+  }
+  return false;
+}
+
+
+/**
+ * Returns a promise that is resolved with a list of CSS files to check,
+ * represented by their nsIURI objects.
+ *
+ * @param appDir the application directory to scan for CSS files (nsIFile)
+ */
+function generateURIsFromDirTree(appDir) {
+  let rv = [];
+  let dirQueue = [appDir.path];
+  return Task.spawn(function*() {
+    while (dirQueue.length) {
+      let nextDir = dirQueue.shift();
+      let {subdirs, cssfiles} = yield iterateOverPath(nextDir);
+      dirQueue = dirQueue.concat(subdirs);
+      rv = rv.concat(cssfiles);
+    }
+    return rv;
+  });
+}
+
+/* Shorthand constructor to construct an nsI(Local)File */
+let LocalFile = Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath");
+
+/**
+ * Uses OS.File.DirectoryIterator to asynchronously iterate over a directory.
+ * It returns a promise that is resolved with an object with two properties:
+ *  - cssfiles: an array of nsIURIs corresponding to CSS that needs checking
+ *  - subdirs: an array of paths for subdirectories we need to recurse into
+ *             (handled by generateURIsFromDirTree above)
+ *
+ * @param path the path to check (string)
+ */
+function iterateOverPath(path) {
+  let iterator = new OS.File.DirectoryIterator(path);
+  let parentDir = new LocalFile(path);
+  let subdirs = [];
+  let cssfiles = [];
+  // Iterate through the directory
+  let promise = iterator.forEach(
+    function onEntry(entry) {
+      if (entry.isDir) {
+        let subdir = parentDir.clone();
+        subdir.append(entry.name);
+        subdirs.push(subdir.path);
+      } else if (entry.name.endsWith(".css")) {
+        let file = parentDir.clone();
+        file.append(entry.name);
+        let uriSpec = getURLForFile(file);
+        cssfiles.push(Services.io.newURI(uriSpec, null, null));
+      } else if (entry.name.endsWith(".ja")) {
+        let file = parentDir.clone();
+        file.append(entry.name);
+        let subentries = [uri for (uri of generateEntriesFromJarFile(file))];
+        cssfiles = cssfiles.concat(subentries);
+      }
+    }
+  );
+
+  let outerPromise = Promise.defer();
+  promise.then(function() {
+    outerPromise.resolve({cssfiles: cssfiles, subdirs: subdirs});
+    iterator.close();
+  }, function(e) {
+    outerPromise.reject(e);
+    iterator.close();
+  });
+  return outerPromise.promise;
+}
+
+/* Helper function to generate a URI spec (NB: not an nsIURI yet!)
+ * given an nsIFile object */
+function getURLForFile(file) {
+  let fileHandler = Services.io.getProtocolHandler("file");
+  fileHandler = fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
+  return fileHandler.getURLSpecFromFile(file);
+}
+
+/**
+ * A generator that generates nsIURIs for CSS files found in jar files
+ * like omni.ja.
+ *
+ * @param jarFile an nsIFile object for the jar file that needs checking.
+ */
+function* generateEntriesFromJarFile(jarFile) {
+  const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
+  let zr = new ZipReader(jarFile);
+  let entryEnumerator = zr.findEntries("*.css$");
+
+  const kURIStart = getURLForFile(jarFile);
+  while (entryEnumerator.hasMore()) {
+    let entry = entryEnumerator.getNext();
+    let entryURISpec = "jar:" + kURIStart + "!/" + entry;
+    yield Services.io.newURI(entryURISpec, null, null);
+  }
+  zr.close();
+}
+
+/**
+ * The actual test.
+ */
+add_task(function checkAllTheCSS() {
+  let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+  // This asynchronously produces a list of URLs (sadly, mostly sync on our
+  // test infrastructure because it runs against jarfiles there, and
+  // our zipreader APIs are all sync)
+  let uris = yield generateURIsFromDirTree(appDir);
+
+  // Create a clean iframe to load all the files into:
+  let hiddenWin = Services.appShell.hiddenDOMWindow;
+  let iframe = hiddenWin.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+  hiddenWin.document.documentElement.appendChild(iframe);
+  let doc = iframe.contentWindow.document;
+
+
+  // Listen for errors caused by the CSS:
+  let errorListener = {
+    observe: function(aMessage) {
+      if (!aMessage || !(aMessage instanceof Ci.nsIScriptError)) {
+        return;
+      }
+      // Only care about CSS errors generated by our iframe:
+      if (aMessage.category.contains("CSS") && aMessage.innerWindowID === 0 && aMessage.outerWindowID === 0) {
+        // Check if this error is whitelisted in kWhitelist
+        if (!ignoredError(aMessage)) {
+          ok(false, "Got error message for " + aMessage.sourceName + ": " + aMessage.errorMessage);
+          errors++;
+        } else {
+          info("Ignored error for " + aMessage.sourceName + " because of filter.");
+        }
+      }
+    }
+  };
+
+  // We build a list of promises that get resolved when their respective
+  // files have loaded and produced no errors.
+  let allPromises = [];
+  let errors = 0;
+  // Register the error listener to keep track of errors.
+  Services.console.registerListener(errorListener);
+  for (let uri of uris) {
+    let linkEl = doc.createElement("link");
+    linkEl.setAttribute("rel", "stylesheet");
+    let promiseForThisSpec = Promise.defer();
+    let onLoad = (e) => {
+      promiseForThisSpec.resolve();
+      linkEl.removeEventListener("load", onLoad);
+      linkEl.removeEventListener("error", onError);
+    };
+    let onError = (e) => {
+      promiseForThisSpec.reject({error: e, href: linkEl.getAttribute("href")});
+      linkEl.removeEventListener("load", onLoad);
+      linkEl.removeEventListener("error", onError);
+    };
+    linkEl.addEventListener("load", onLoad);
+    linkEl.addEventListener("error", onError);
+    linkEl.setAttribute("type", "text/css");
+    linkEl.setAttribute("href", uri.spec);
+    allPromises.push(promiseForThisSpec.promise);
+    doc.head.appendChild(linkEl);
+  }
+
+  // Wait for all the files to have actually loaded:
+  yield Promise.all(allPromises);
+  // Count errors (the test output will list actual issues for us, as well
+  // as the ok(false) in the error listener)
+  is(errors, 0, "All the styles (" + allPromises.length + ") loaded without errors.");
+
+  // Clean up to avoid leaks:
+  Services.console.unregisterListener(errorListener);
+  iframe.remove();
+  doc.head.innerHTML = '';
+  doc = null;
+  iframe = null;
+});
--- a/browser/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
+++ b/browser/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
@@ -1,23 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const { interfaces: Ci, classes: Cc } = Components;
+const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+
+function notify() {
+  // Log objects so makeDebuggeeValue can get the global to use
+  console.log({ msg: "Hello again" });
+}
 
 function startup(aParams, aReason) {
-  Components.utils.import("resource://gre/modules/Services.jsm");
+  Cu.import("resource://gre/modules/Services.jsm");
   let res = Services.io.getProtocolHandler("resource")
                        .QueryInterface(Ci.nsIResProtocolHandler);
   res.setSubstitution("browser_dbg_addon4", aParams.resourceURI);
 
   // Load a JS module
-  Components.utils.import("resource://browser_dbg_addon4/test.jsm");
+  Cu.import("resource://browser_dbg_addon4/test.jsm");
+  // Log objects so makeDebuggeeValue can get the global to use
+  console.log({ msg: "Hello from the test add-on" });
+
+  Services.obs.addObserver(notify, "addon-test-ping", false);
 }
 
 function shutdown(aParams, aReason) {
+  Services.obs.removeObserver(notify, "addon-test-ping");
+
   // Unload the JS module
-  Components.utils.unload("resource://browser_dbg_addon4/test.jsm");
+  Cu.unload("resource://browser_dbg_addon4/test.jsm");
 
   let res = Services.io.getProtocolHandler("resource")
                        .QueryInterface(Ci.nsIResProtocolHandler);
   res.setSubstitution("browser_dbg_addon4", null);
 }
index 31c5331b69dee6d15183647bb598f90195ff1c2a..56dc98f6e01e766f60b9eeb08c5ce4a3c56d4b02
GIT binary patch
literal 3049
zc%03adpMM7AIG1;FwQgcQjX=cPDPH(IxRKMtff#P$01fTVFo3|I8!7>M5LxE7Fnm*
zqMdatMlwbQ=|DN9p~zTky$|iaJ5!go_x<a=uj_YR_w!uO^L_5m@A=)|-~GKkH;)*=
z1;Bv8#!IM}#D;fM5C9<a0Dur61NdRFxCmUh?{UqL2-gGr0QUic=YBdE_-H`@!t)+{
z6nq;1=ocKZG;??|b46USq)2^%&{ihSm%BqM5oL3rihk0R5qgMu(73j2Z|-{Uu)G?N
zdRsiVS*5-=bG7H@uITNNJ~u}!gABMbODP?a8bfjLE?(d45T?+m%a2|+t$=F8bQMtt
zQQxQ`72+Y_>0DPXeW6g^J+|Fu2ESxSAxfQd=tT@A2ol07h2QG;jPjg)+|!y>V2+$z
z4I*&gO_Hpag$n&@aP5&1R56o_2YGvp=sy&sZAam&F<(-Y^7SFOouPPC<kY8@)H}hf
z_^MSCq-V<pTbA=AB4Tf)o46H+Hl^gv>5D~8nZ`FT_CZn>s8jQ;xNWju;jbT=y6I6I
zcjRiIfb}=E2bwCru{(qcuzTdAj8-hqyEKta7Z@KzzoRSOwtOS}thTPGXsy^kvwU|n
z!nW)DJzD94M2W|?@zcWhR?D6E$1$h(diHvXN4KC_ru2n+=hUqZUBA?BJ2OYLl$}n~
zHq)z*?Q`7s+%em92h~jlvfO>*&GBDvN%zg4hFWLrabeZhA_GqFE-)jTG6zJyS~Krz
zy|EZu-}Ktya!6J9YyZox7+vOgspA!if$vs_gC_}8^TA==BS$)vZTC4nPolAc9oq65
zSd+Fv50)v*MKPk=Ni_J1bpLuMHECkaoF)DJf2zf?<RnVoEqGoFECD|*01yPLB^VQd
z^9>Ev3=at0ES8!m-TexfLmw9l{sI+~paoB4nj~3bh^hQkD322H>JIg#ez#^TWy3Vr
zb-5U|Iw$W=hzh9^Tf7z+)fwJ01GO$WAWaTHXub+-R8NpQ!!S|s$R@+G0LSF8$M|<|
zPFF2L_VnwuhY@m=`j^afypc9LZFL2?aYnuQ&e{tCOH@OZDhp(%y+iwbQ^7gI#J9rw
z##E{5ALLxhwxw6ob)-Edb9iBs*>V@=${@2wjt|PZ4tlvA#wX7l^O9{KRS`X_D0^R>
zJev8iVp+J<zK}uhxWlJ}s5?~~dwx!m(QL$Py||cO?D`};q^R3s%tZ~#s~2q7r-A>)
z@~s#=_R4&pUeO$kRX}r>(BFy7eX&z$a2+k>Oqp1Pzm$1xmro$=T)Lwhjp#_4zjaP#
zC5Rwx*!linM2aY9(j&%RPGx{uyaEOcOgau7fdhBvvCm~({{cmSU@m6$e9fdi%YOUV
zu(E~#-c^-xH5FDUiIZ#A8RN<rGesOOZnc*G`U#A>Hs$pMHVXKkqY)2s``Y+Bn=nOh
zWFPlv(sTi&vQJx*nk(CyD_%9c#H0>A9(Xv^lQ%Y~t8on~uXKGefM>d_OSDt3i8#6T
z^;6=(ab!RbMgNbSGV<z5#|$4Q%y_*K;x8EfKVfwK2O8vvxLt#)g5RZ&XtY6^)-{P!
zPhh_TvlRx1i;}cRVX7s|DF$6l^cH)uI4hDjqTpE_>!R*x!>BNS%B9~VM;QGhP%^qk
zu8oviaMmYAJ)<?O@M-xwvt5zmgN>*Hkw^M#n>6GPGy)(E0H_o4QSbM}U6cjq8zBnP
z_#X|&9z$y$^Th-Qf?%6WK4%W?--wTx{IM7u8iSMb3&%!9pu@cb{DQoF0|KxZJvmK*
zzjL<nl{abb_BG%u5C~fVMB_t0C-0a=K@zltP|Q=7DpAtSMsZJFwEOLj7APN{pREjE
z$YW4jLWjGJYEL-lo%iEYiAl77syF9umWpwBkv70`SQg<ep*aM#Wz*=|55yD*kB*g!
z<hr$61n}j;v1R$`Y5no;ULh5^ev^wLJ-uX;7pRffD7EpaT;;3RGiTItg#GqUP^+Ah
zTeQhU`4r1_tm$a?oVvvQR(J8r)%Wso_k`~3qBCW_4URg~I8)!aJX<Kr$^6W4jM)Hk
za6{PZ<e#DYL1@rtHlUZL05kiSS#`+6<g{&n+_?Rq-6POo6-_(xxPY2blPG3v$WZiL
zB?o`|)&l2`bRu;G{T!vmDhm8rF0%-e=}d+r&SC`dSL)P76pXJVvM#2V<*Uj@)30~f
zsYQOb9?xLpS{EHHysV0rx!jMc8*ny7*I0z6-(@V_?=$GA)0wEAVpgWaG<iW4Pq1!Y
z#jl6%<YEc`bjLaN;X+R2>hDmu+V*DOr*|Qo;1}znev1HElE4u76CA|;s~vAauF-8F
zLePRk`(m9IXyQs%BIcvhz4il<b}DMRWFff!UE|%J_wM@sa#=^v_fbJbwS$jEsZ}5E
zE|s<pY^#h}eiyvnE#2z=+-}?DA8e+7${xzuu2?#_;#!_Ov8U@tls~bm^D$)oylYs(
znWR_N%d^%|z}Cwa{Cn9t|Bvg2aP8pU>I5VKYz*)cAjNOYIL|}S59gteAlMZ@?UL+@
zn&2Ido(cWndH*2D*#X!EuW<ux{ou>gI4zi6?Kls>ZpfdL+sXsk<;<YKr;Z5yPz#?~
z<ENI#E=qy|YzFvcYMf@mu9nXWuv^QQ$#HH`cDZamfMdh{_j;W5Vgmy>chXjZP+nm3
NZ}I~)uFVNJ{tf1CW}*N9
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -85,16 +85,17 @@ support-files =
   testactors.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 [browser_dbg_addonactor.js]
 [browser_dbg_addon-sources.js]
 [browser_dbg_addon-modules.js]
 [browser_dbg_addon-modules-unpacked.js]
 [browser_dbg_addon-panels.js]
+[browser_dbg_addon-console.js]
 [browser_dbg_auto-pretty-print-01.js]
 [browser_dbg_auto-pretty-print-02.js]
 [browser_dbg_bfcache.js]
 [browser_dbg_blackboxing-01.js]
 [browser_dbg_blackboxing-02.js]
 [browser_dbg_blackboxing-03.js]
 [browser_dbg_blackboxing-04.js]
 [browser_dbg_blackboxing-05.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_addon-console.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the we can see console messages from the add-on
+
+const ADDON_URL = EXAMPLE_URL + "addon4.xpi";
+
+function getCachedMessages(webConsole) {
+  let deferred = promise.defer();
+  webConsole.getCachedMessages(["ConsoleAPI"], (aResponse) => {
+    if (aResponse.error) {
+      deferred.reject(aResponse.error);
+      return;
+    }
+    deferred.resolve(aResponse.messages);
+  });
+  return deferred.promise;
+}
+
+function test() {
+  Task.spawn(function () {
+    let addon = yield addAddon(ADDON_URL);
+    let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+    let webConsole = addonDebugger.webConsole;
+    let messages = yield getCachedMessages(webConsole);
+    is(messages.length, 1, "Should be one cached message");
+    is(messages[0].arguments[0].type, "object", "Should have logged an object");
+    is(messages[0].arguments[0].preview.ownProperties.msg.value, "Hello from the test add-on", "Should have got the right message");
+
+    let consolePromise = addonDebugger.once("console");
+
+    console.log("Bad message");
+    Services.obs.notifyObservers(null, "addon-test-ping", "");
+
+    let messageGrip = yield consolePromise;
+    is(messageGrip.arguments[0].type, "object", "Should have logged an object");
+    is(messageGrip.arguments[0].preview.ownProperties.msg.value, "Hello again", "Should have got the right message");
+
+    yield addonDebugger.destroy();
+    yield removeAddon(addon);
+    finish();
+  });
+}
--- a/browser/devtools/debugger/test/browser_dbg_addon-panels.js
+++ b/browser/devtools/debugger/test/browser_dbg_addon-panels.js
@@ -20,25 +20,30 @@ function test() {
 
     // Store and enable all optional dev tools panels
     let originalPrefs = PREFS.map(pref => {
       let original = Services.prefs.getBoolPref(pref);
       Services.prefs.setBoolPref(pref, true)
       return original;
     });
 
+    // Check only valid tabs are shown
     let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children;
-    let expectedTabs = ["options", "jsdebugger"];
+    let expectedTabs = ["options", "webconsole", "jsdebugger", "scratchpad"];
 
-    is(tabs.length, 2, "displaying only 2 tabs in addon debugger");
+    is(tabs.length, expectedTabs.length, "displaying only " + expectedTabs.length + " tabs in addon debugger");
     Array.forEach(tabs, (tab, i) => {
       let toolName = expectedTabs[i];
       is(tab.getAttribute("toolid"), toolName, "displaying " + toolName);
     });
 
+    // Check no toolbox buttons are shown
+    let buttons = addonDebugger.frame.contentDocument.getElementById("toolbox-buttons").children;
+    is(buttons.length, 0, "no toolbox buttons for the addon debugger");
+
     yield addonDebugger.destroy();
     yield removeAddon(addon);
 
     PREFS.forEach((pref, i) => Services.prefs.setBoolPref(pref, originalPrefs[i]));
 
     finish();
   });
 }
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -17,16 +17,17 @@ let { Promise: promise } = Cu.import("re
 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let { require } = devtools;
 let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
 let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
 let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
 let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+let EventEmitter = require("devtools/toolkit/event-emitter");
 const { promiseInvoke } = require("devtools/async-utils");
 let TargetFactory = devtools.TargetFactory;
 let Toolbox = devtools.Toolbox;
 
 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
@@ -517,16 +518,18 @@ function initDebugger(aTarget, aWindow) 
 // object must be destroyed before finishing the test
 function initAddonDebugger(aUrl) {
   let addonDebugger = new AddonDebugger();
   return addonDebugger.init(aUrl).then(() => addonDebugger);
 }
 
 function AddonDebugger() {
   this._onMessage = this._onMessage.bind(this);
+  this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
+  EventEmitter.decorate(this);
 }
 
 AddonDebugger.prototype = {
   init: Task.async(function*(aUrl) {
     info("Initializing an addon debugger panel.");
 
     if (!DebuggerServer.initialized) {
       DebuggerServer.init(() => true);
@@ -543,46 +546,72 @@ AddonDebugger.prototype = {
 
     let connected = promise.defer();
     this.client.connect(connected.resolve);
     yield connected.promise;
 
     let addonActor = yield getAddonActorForUrl(this.client, aUrl);
 
     let targetOptions = {
-      form: { addonActor: addonActor.actor, title: addonActor.name },
+      form: {
+        addonActor: addonActor.actor,
+        consoleActor: addonActor.consoleActor,
+        title: addonActor.name
+      },
       client: this.client,
       chrome: true
     };
 
     let toolboxOptions = {
       customIframe: this.frame
     };
 
-    let target = devtools.TargetFactory.forTab(targetOptions);
-    let toolbox = yield gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
+    this.target = devtools.TargetFactory.forTab(targetOptions);
+    let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
 
     info("Addon debugger panel shown successfully.");
 
     this.debuggerPanel = toolbox.getCurrentPanel();
 
     // Wait for the initial resume...
     yield waitForClientEvents(this.debuggerPanel, "resumed");
     yield prepareDebugger(this.debuggerPanel);
+    yield this._attachConsole();
   }),
 
   destroy: Task.async(function*() {
     let deferred = promise.defer();
     this.client.close(deferred.resolve);
     yield deferred.promise;
     yield this.debuggerPanel._toolbox.destroy();
     this.frame.remove();
     window.removeEventListener("message", this._onMessage);
   }),
 
+  _attachConsole: function() {
+    let deferred = promise.defer();
+    this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => {
+      if (aResponse.error) {
+        deferred.reject(aResponse);
+      }
+      else {
+        this.webConsole = aWebConsoleClient;
+        this.client.addListener("consoleAPICall", this._onConsoleAPICall);
+        deferred.resolve();
+      }
+    });
+    return deferred.promise;
+  },
+
+  _onConsoleAPICall: function(aType, aPacket) {
+    if (aPacket.from != this.webConsole.actor)
+      return;
+    this.emit("console", aPacket.message);
+  },
+
   /**
    * Returns a list of the groups and sources in the UI. The returned array
    * contains objects for each group with properties name and sources. The
    * sources property contains an array with objects for each source for that
    * group with properties label and url.
    */
   getSourceGroups: Task.async(function*() {
     let debuggerWin = this.debuggerPanel.panelWin;
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -32,17 +32,21 @@ function connect() {
   );
   gClient = new DebuggerClient(transport);
   gClient.connect(() => {
     let addonID = getParameterByName("addonID");
 
     if (addonID) {
       gClient.listAddons(({addons}) => {
         let addonActor = addons.filter(addon => addon.id === addonID).pop();
-        openToolbox({ addonActor: addonActor.actor, title: addonActor.name });
+        openToolbox({
+          addonActor: addonActor.actor,
+          consoleActor: addonActor.consoleActor,
+          title: addonActor.name
+        });
       });
     } else {
       gClient.listTabs(openToolbox);
     }
   });
 }
 
 window.addEventListener("load", connect);
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -522,17 +522,19 @@ Toolbox.prototype = {
       this._buildTabForTool(definition);
     }
   },
 
   /**
    * Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
    */
   _buildButtons: function() {
-    this._buildPickerButton();
+    if (!this.target.isAddon) {
+      this._buildPickerButton();
+    }
 
     if (!this.target.isLocalTab) {
       return;
     }
 
     let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
     let environment = CommandUtils.createEnvironment(this, '_target');
     this._requisition = CommandUtils.createRequisition(environment);
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -100,17 +100,17 @@ Tools.webConsole = {
   onkey: function(panel, toolbox) {
     if (toolbox.splitConsole)
       return toolbox.focusConsoleInput();
 
     panel.focusInput();
   },
 
   isTargetSupported: function(target) {
-    return !target.isAddon;
+    return true;
   },
   build: function(iframeWindow, toolbox) {
     let panel = new WebConsolePanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
 
 Tools.inspector = {
@@ -309,17 +309,17 @@ Tools.scratchpad = {
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/scratchpad.xul",
   label: l10n("scratchpad.label", scratchpadStrings),
   tooltip: l10n("scratchpad.tooltip", scratchpadStrings),
   inMenu: false,
   commands: "devtools/scratchpad/scratchpad-commands",
 
   isTargetSupported: function(target) {
-    return !target.isAddon && target.isRemote;
+    return target.isRemote;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new ScratchpadPanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
 
--- a/browser/devtools/shared/widgets/spectrum.css
+++ b/browser/devtools/shared/widgets/spectrum.css
@@ -139,25 +139,25 @@ http://www.briangrinstead.com/blog/keep-
   position: absolute;
   top: -3px;
   bottom: -3px;
   width: 5px;
   left: 50%;
 }
 
 .spectrum-sat {
-  background-image: -moz-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
+  background-image: linear-gradient(to right, #FFF, rgba(204, 154, 129, 0));
 }
 
 .spectrum-val {
-  background-image: -moz-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
+  background-image: linear-gradient(to top, #000000, rgba(204, 154, 129, 0));
 }
 
 .spectrum-hue {
-  background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+  background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
 }
 
 .spectrum-dragger {
   position: absolute;
   top: 0px;
   left: 0px;
   cursor: pointer;
   border-radius: 50%;
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -33,16 +33,17 @@ RequestExecutionLevel user
 
 Var TmpVal
 Var InstallType
 Var AddStartMenuSC
 Var AddQuickLaunchSC
 Var AddDesktopSC
 Var InstallMaintenanceService
 Var PageName
+Var PreventRebootRequired
 
 ; By defining NO_STARTMENU_DIR an installer that doesn't provide an option for
 ; an application's Start Menu PROGRAMS directory and doesn't define the
 ; StartMenuDir variable can use the common InstallOnInitCommon macro.
 !define NO_STARTMENU_DIR
 
 ; On Vista and above attempt to elevate Standard Users in addition to users that
 ; are a member of the Administrators group.
@@ -90,16 +91,17 @@ VIAddVersionKey "OriginalFilename" "setu
 !insertmacro InitHashAppModelId
 !insertmacro IsHandlerForInstallDir
 !insertmacro IsPinnedToTaskBar
 !insertmacro IsUserAdmin
 !insertmacro LogDesktopShortcut
 !insertmacro LogQuickLaunchShortcut
 !insertmacro LogStartMenuShortcut
 !insertmacro ManualCloseAppPrompt
+!insertmacro OnStubInstallUninstall
 !insertmacro PinnedToStartMenuLnkCount
 !insertmacro RegCleanAppHandler
 !insertmacro RegCleanMain
 !insertmacro RegCleanUninstall
 !ifdef MOZ_METRO
 !insertmacro RemoveDEHRegistrationIfMatching
 !endif
 !insertmacro SetAppLSPCategories
@@ -198,17 +200,34 @@ ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\
 Section "-InstallStartCleanup"
   SetDetailsPrint both
   DetailPrint $(STATUS_CLEANUP)
   SetDetailsPrint none
 
   SetOutPath "$INSTDIR"
   ${StartInstallLog} "${BrandFullName}" "${AB_CD}" "${AppVersion}" "${GREVersion}"
 
-  ; Delete the app exe to prevent launching the app while we are installing.
+  StrCpy $PreventRebootRequired "false"
+  ${GetParameters} $R8
+  ${GetOptions} "$R8" "/INI=" $R7
+  ${Unless} ${Errors}
+    ; The configuration file must also exist
+    ${If} ${FileExists} "$R7"
+      ReadINIStr $R8 $R7 "Install" "PreventRebootRequired"
+      ${If} $R8 == "true"
+        StrCpy $PreventRebootRequired "true"
+        StrCpy $R2 "false"
+        StrCpy $R3 "false"
+        ${OnStubInstallUninstall} "$R2" "$R3"
+      ${EndIf}
+    ${EndIf}
+  ${EndUnless}
+
+  ; Delete the app exe if present to prevent launching the app while we are
+  ; installing.
   ClearErrors
   ${DeleteFile} "$INSTDIR\${FileMainEXE}"
   ${If} ${Errors}
     ; If the user closed the application it can take several seconds for it to
     ; shut down completely. If the application is being used by another user we
     ; can rename the file and then delete is when the system is restarted.
     Sleep 5000
     ${DeleteFile} "$INSTDIR\${FileMainEXE}"
@@ -578,16 +597,20 @@ Section "-InstallEndCleanup"
   ${GetShortcutsLogPath} $0
   WriteIniStr "$0" "TASKBAR" "Migrated" "true"
 
   ; Refresh desktop icons
   System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"
 
   ${InstallEndCleanupCommon}
 
+  ${If} $PreventRebootRequired == "true"
+    SetRebootFlag false
+  ${EndIf}
+
   ${If} ${RebootFlag}
     ; When a reboot is required give SHChangeNotify time to finish the
     ; refreshing the icons so the OS doesn't display the icons from helper.exe
     Sleep 10000
     ${LogHeader} "Reboot Required To Finish Installation"
     ; ${FileMainEXE}.moz-upgrade should never exist but just in case...
     ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
       Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-upgrade"
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -273,17 +273,17 @@ browser.jar:
   skin/classic/browser/devtools/tool-scratchpad.svg         (../shared/devtools/images/tool-scratchpad.svg)
   skin/classic/browser/devtools/close.png                   (../shared/devtools/images/close.png)
   skin/classic/browser/devtools/close@2x.png                (../shared/devtools/images/close@2x.png)
   skin/classic/browser/devtools/vview-delete.png            (../shared/devtools/images/vview-delete.png)
   skin/classic/browser/devtools/vview-lock.png              (../shared/devtools/images/vview-lock.png)
   skin/classic/browser/devtools/vview-edit.png              (../shared/devtools/images/vview-edit.png)
   skin/classic/browser/devtools/vview-open-inspector.png    (../shared/devtools/images/vview-open-inspector.png)
   skin/classic/browser/devtools/undock@2x.png               (../shared/devtools/images/undock@2x.png)
-  skin/classic/browser/devtools/font-inspector.css          (devtools/font-inspector.css)
+  skin/classic/browser/devtools/font-inspector.css          (../shared/devtools/font-inspector.css)
   skin/classic/browser/devtools/computedview.css            (devtools/computedview.css)
   skin/classic/browser/devtools/arrow-e.png                 (devtools/arrow-e.png)
   skin/classic/browser/devtools/responsiveui-rotate.png     (../shared/devtools/responsiveui-rotate.png)
   skin/classic/browser/devtools/responsiveui-touch.png      (../shared/devtools/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
   skin/classic/browser/devtools/app-manager/connection-footer.css     (../shared/devtools/app-manager/connection-footer.css)
   skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
   skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
deleted file mode 100644
--- a/browser/themes/osx/devtools/font-inspector.css
+++ /dev/null
@@ -1,78 +0,0 @@
-* {
-  box-sizing: border-box;
-}
-
-body {
-  margin: 0;
-  padding-bottom: 20px;
-}
-
-#all-fonts {
-  padding: 0;
-  margin: 0;
-}
-
-#showall {
-  border-radius: 0;
-  border: 1px solid black;
-  margin: 3px;
-  cursor: pointer;
-  position: fixed;
-  bottom: 0;
-  right: 0;
-}
-
-.font {
-  padding: 10px 10px;
-}
-
-.theme-dark .font {
-  border-bottom: 1px solid #444;
-}
-
-.theme-light .font {
-  border-bottom: 1px solid #DDD;
-}
-
-.font:last-of-type {
-  border-bottom: 0;
-}
-
-.theme-light .font:nth-child(even) {
-  background: #F4F4F4;
-}
-
-.font-preview {
-  height: 60px;
-  width: 100%;
-  border: 0;
-  display: block;
-}
-
-.font-info {
-  display: block;
-}
-
-.font-name {
-  display: inline;
-}
-
-.font-css-code {
-  max-width: 100%;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  padding: 5px;
-}
-
-.theme-light .font-css-code,
-.theme-light .font-url {
-  border: 1px solid #CCC;
-  background: white;
-}
-
-.theme-dark .font-css-code,
-.theme-dark .font-url {
-  border: 1px solid #333;
-  background: black;
-  color: white;
-}
--- a/browser/themes/osx/downloads/downloads.css
+++ b/browser/themes/osx/downloads/downloads.css
@@ -212,23 +212,23 @@ richlistitem[type="download"]:hover > st
 }
 #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"]:hover[selected] > stack > .downloadButton.downloadCancel:active {
   -moz-image-region: rect(0px, 128px, 16px, 112px);
 }
 
 .downloadButton.downloadShow {
   -moz-image-region: rect(16px, 16px, 32px, 0px);
 }
-#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow {
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow {
   -moz-image-region: rect(16px, 32px, 32px, 16px);
 }
-#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow:hover {
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow:hover {
   -moz-image-region: rect(16px, 48px, 32px, 32px);
 }
-#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow:active {
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow:active {
   -moz-image-region: rect(16px, 64px, 32px, 48px);
 }
 #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"][selected] > stack > .downloadButton.downloadShow {
   -moz-image-region: rect(16px, 80px, 32px, 64px);
 }
 #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow,
 #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"]:hover[selected] > stack > .downloadButton.downloadShow {
   -moz-image-region: rect(16px, 96px, 32px, 80px);
@@ -299,23 +299,23 @@ richlistitem[type="download"]:hover > st
   }
   #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"]:hover[selected] > stack > .downloadButton.downloadCancel:active {
     -moz-image-region: rect(0px, 256px, 32px, 224px);
   }
 
   .downloadButton.downloadShow {
     -moz-image-region: rect(32px, 32px, 64px, 0px);
   }
-  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow {
+  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow {
     -moz-image-region: rect(32px, 64px, 64px, 32px);
   }
-  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow:hover {
+  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow:hover {
     -moz-image-region: rect(32px, 96px, 64px, 64px);
   }
-  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not[selected] > stack > .downloadButton.downloadShow:active {
+  #downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover:not([selected]) > stack > .downloadButton.downloadShow:active {
     -moz-image-region: rect(32px, 128px, 64px, 96px);
   }
   #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"][selected] > stack > .downloadButton.downloadShow {
     -moz-image-region: rect(32px, 160px, 64px, 128px);
   }
   #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow,
   #downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"]:hover[selected] > stack > .downloadButton.downloadShow {
     -moz-image-region: rect(32px, 192px, 64px, 160px);
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -394,17 +394,17 @@ browser.jar:
   skin/classic/browser/devtools/tool-scratchpad.svg         (../shared/devtools/images/tool-scratchpad.svg)
   skin/classic/browser/devtools/close.png                   (../shared/devtools/images/close.png)
   skin/classic/browser/devtools/close@2x.png                (../shared/devtools/images/close@2x.png)
   skin/classic/browser/devtools/vview-delete.png            (../shared/devtools/images/vview-delete.png)
   skin/classic/browser/devtools/vview-lock.png              (../shared/devtools/images/vview-lock.png)
   skin/classic/browser/devtools/vview-edit.png              (../shared/devtools/images/vview-edit.png)
   skin/classic/browser/devtools/vview-open-inspector.png    (../shared/devtools/images/vview-open-inspector.png)
   skin/classic/browser/devtools/undock@2x.png               (../shared/devtools/images/undock@2x.png)
-  skin/classic/browser/devtools/font-inspector.css          (devtools/font-inspector.css)
+  skin/classic/browser/devtools/font-inspector.css          (../shared/devtools/font-inspector.css)
   skin/classic/browser/devtools/computedview.css            (devtools/computedview.css)
   skin/classic/browser/devtools/arrow-e.png                 (devtools/arrow-e.png)
   skin/classic/browser/devtools/responsiveui-rotate.png     (../shared/devtools/responsiveui-rotate.png)
   skin/classic/browser/devtools/responsiveui-touch.png      (../shared/devtools/responsiveui-touch.png)
   skin/classic/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
   skin/classic/browser/devtools/app-manager/connection-footer.css     (../shared/devtools/app-manager/connection-footer.css)
   skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
   skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -136,17 +136,17 @@
 
 .theme-fg-contrast { /* To be used for text on theme-bg-contrast */
   color: black;
 }
 
 .ruleview-colorswatch,
 .computedview-colorswatch,
 .markupview-colorswatch {
-  box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
+  box-shadow: 0 0 0 1px #818181;
 }
 
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
   font-family: inherit;
rename from browser/themes/linux/devtools/font-inspector.css
rename to browser/themes/shared/devtools/font-inspector.css
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -135,17 +135,17 @@
   color: #585959;
   background-color: #f0f1f2;
   border-color: #aaa;
 }
 
 .ruleview-colorswatch,
 .computedview-colorswatch,
 .markupview-colorswatch {
-  box-shadow: 0 0 0 1px #EFEFEF;
+  box-shadow: 0 0 0 1px #c4c4c4;
 }
 
 /* CodeMirror specific styles.
  * Best effort to match the existing theme, some of the colors
  * are duplicated here to prevent weirdness in the main theme. */
 
 .CodeMirror { /* Inherit platform specific font sizing and styles */
   font-family: inherit;
deleted file mode 100644
--- a/browser/themes/windows/devtools/font-inspector.css
+++ /dev/null
@@ -1,78 +0,0 @@
-* {
-  box-sizing: border-box;
-}
-
-body {
-  margin: 0;
-  padding-bottom: 20px;
-}
-
-#all-fonts {
-  padding: 0;
-  margin: 0;
-}
-
-#showall {
-  border-radius: 0;
-  border: 1px solid black;
-  margin: 3px;
-  cursor: pointer;
-  position: fixed;
-  bottom: 0;
-  right: 0;
-}
-
-.font {
-  padding: 10px 10px;
-}
-
-.theme-dark .font {
-  border-bottom: 1px solid #444;
-}
-
-.theme-light .font {
-  border-bottom: 1px solid #DDD;
-}
-
-.font:last-of-type {
-  border-bottom: 0;
-}
-
-.theme-light .font:nth-child(even) {
-  background: #F4F4F4;
-}
-
-.font-preview {
-  height: 60px;
-  width: 100%;
-  border: 0;
-  display: block;
-}
-
-.font-info {
-  display: block;
-}
-
-.font-name {
-  display: inline;
-}
-
-.font-css-code {
-  max-width: 100%;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  padding: 5px;
-}
-
-.theme-light .font-css-code,
-.theme-light .font-url {
-  border: 1px solid #CCC;
-  background: white;
-}
-
-.theme-dark .font-css-code,
-.theme-dark .font-url {
-  border: 1px solid #333;
-  background: black;
-  color: white;
-}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -309,17 +309,17 @@ browser.jar:
         skin/classic/browser/devtools/tool-scratchpad.svg           (../shared/devtools/images/tool-scratchpad.svg)
         skin/classic/browser/devtools/close.png                     (../shared/devtools/images/close.png)
         skin/classic/browser/devtools/close@2x.png                  (../shared/devtools/images/close@2x.png)
         skin/classic/browser/devtools/vview-delete.png              (../shared/devtools/images/vview-delete.png)
         skin/classic/browser/devtools/vview-lock.png                (../shared/devtools/images/vview-lock.png)
         skin/classic/browser/devtools/vview-edit.png                (../shared/devtools/images/vview-edit.png)
         skin/classic/browser/devtools/vview-open-inspector.png      (../shared/devtools/images/vview-open-inspector.png)
         skin/classic/browser/devtools/undock@2x.png                 (../shared/devtools/images/undock@2x.png)
-        skin/classic/browser/devtools/font-inspector.css            (devtools/font-inspector.css)
+        skin/classic/browser/devtools/font-inspector.css            (../shared/devtools/font-inspector.css)
         skin/classic/browser/devtools/computedview.css              (devtools/computedview.css)
         skin/classic/browser/devtools/arrow-e.png                   (devtools/arrow-e.png)
         skin/classic/browser/devtools/responsiveui-rotate.png       (../shared/devtools/responsiveui-rotate.png)
         skin/classic/browser/devtools/responsiveui-touch.png        (../shared/devtools/responsiveui-touch.png)
         skin/classic/browser/devtools/responsiveui-screenshot.png   (../shared/devtools/responsiveui-screenshot.png)
         skin/classic/browser/devtools/app-manager/connection-footer.css     (../shared/devtools/app-manager/connection-footer.css)
         skin/classic/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
         skin/classic/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
@@ -671,17 +671,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/tool-scratchpad.svg       (../shared/devtools/images/tool-scratchpad.svg)
         skin/classic/aero/browser/devtools/close.png                 (../shared/devtools/images/close.png)
         skin/classic/aero/browser/devtools/close@2x.png              (../shared/devtools/images/close@2x.png)
         skin/classic/aero/browser/devtools/vview-delete.png          (../shared/devtools/images/vview-delete.png)
         skin/classic/aero/browser/devtools/vview-lock.png            (../shared/devtools/images/vview-lock.png)
         skin/classic/aero/browser/devtools/vview-edit.png            (../shared/devtools/images/vview-edit.png)
         skin/classic/aero/browser/devtools/vview-open-inspector.png  (../shared/devtools/images/vview-open-inspector.png)
         skin/classic/aero/browser/devtools/undock@2x.png             (../shared/devtools/images/undock@2x.png)
-        skin/classic/aero/browser/devtools/font-inspector.css        (devtools/font-inspector.css)
+        skin/classic/aero/browser/devtools/font-inspector.css        (../shared/devtools/font-inspector.css)
         skin/classic/aero/browser/devtools/computedview.css          (devtools/computedview.css)
         skin/classic/aero/browser/devtools/arrow-e.png               (devtools/arrow-e.png)
         skin/classic/aero/browser/devtools/responsiveui-rotate.png   (../shared/devtools/responsiveui-rotate.png)
         skin/classic/aero/browser/devtools/responsiveui-touch.png    (../shared/devtools/responsiveui-touch.png)
         skin/classic/aero/browser/devtools/responsiveui-screenshot.png (../shared/devtools/responsiveui-screenshot.png)
         skin/classic/aero/browser/devtools/app-manager/connection-footer.css     (../shared/devtools/app-manager/connection-footer.css)
         skin/classic/aero/browser/devtools/app-manager/index.css                 (../shared/devtools/app-manager/index.css)
         skin/classic/aero/browser/devtools/app-manager/device.css                (../shared/devtools/app-manager/device.css)
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -695,22 +695,28 @@ public class Tab {
         }, 500);
     }
 
     void handleContentLoaded() {
         setLoadProgressIfLoading(LOAD_PROGRESS_LOADED);
     }
 
     protected void saveThumbnailToDB() {
+        final BitmapDrawable thumbnail = mThumbnail;
+        if (thumbnail == null) {
+            return;
+        }
+
         try {
             String url = getURL();
-            if (url == null)
+            if (url == null) {
                 return;
+            }
 
-            BrowserDB.updateThumbnailForUrl(getContentResolver(), url, mThumbnail);
+            BrowserDB.updateThumbnailForUrl(getContentResolver(), url, thumbnail);
         } catch (Exception e) {
             // ignore
         }
     }
 
     public void addPluginView(View view) {
         mPluginViews.add(view);
     }
--- a/mobile/android/base/db/LocalBrowserDB.java
+++ b/mobile/android/base/db/LocalBrowserDB.java
@@ -768,17 +768,17 @@ public class LocalBrowserDB implements B
     @Override
     public LoadFaviconResult getFaviconForUrl(ContentResolver cr, String faviconURL) {
         Cursor c = null;
         byte[] b = null;
 
         try {
             c = cr.query(mFaviconsUriWithProfile,
                          new String[] { Favicons.DATA },
-                         Favicons.URL + " = ?",
+                         Favicons.URL + " = ? AND " + Favicons.DATA + " IS NOT NULL",
                          new String[] { faviconURL },
                          null);
 
             if (!c.moveToFirst()) {
                 return null;
             }
 
             final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA);
@@ -861,28 +861,31 @@ public class LocalBrowserDB implements B
     }
 
     @Override
     public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
         Cursor c = null;
         byte[] b = null;
         try {
             c = cr.query(mThumbnailsUriWithProfile,
-                         new String[]{Thumbnails.DATA},
-                         Thumbnails.URL + " = ?",
-                         new String[]{uri},
+                         new String[]{ Thumbnails.DATA },
+                         Thumbnails.URL + " = ? AND " + Thumbnails.DATA + " IS NOT NULL",
+                         new String[]{ uri },
                          null);
 
-            if (c.moveToFirst()) {
-                int thumbnailIndex = c.getColumnIndexOrThrow(Thumbnails.DATA);
-                b = c.getBlob(thumbnailIndex);
+            if (!c.moveToFirst()) {
+                return null;
             }
+
+            int thumbnailIndex = c.getColumnIndexOrThrow(Thumbnails.DATA);
+            b = c.getBlob(thumbnailIndex);
         } finally {
-            if (c != null)
+            if (c != null) {
                 c.close();
+            }
         }
 
         return b;
     }
 
     /**
      * Query for non-null thumbnails matching the provided <code>urls</code>.
      * The returned cursor will have no more than, but possibly fewer than,
--- a/mobile/android/base/favicons/LoadFaviconTask.java
+++ b/mobile/android/base/favicons/LoadFaviconTask.java
@@ -92,16 +92,20 @@ public class LoadFaviconTask extends UiA
     // Runs in background thread
     private LoadFaviconResult loadFaviconFromDb() {
         ContentResolver resolver = context.getContentResolver();
         return BrowserDB.getFaviconForFaviconUrl(resolver, faviconURL);
     }
 
     // Runs in background thread
     private void saveFaviconToDb(final byte[] encodedFavicon) {
+        if (encodedFavicon == null) {
+            return;
+        }
+
         if ((flags & FLAG_PERSIST) == 0) {
             return;
         }
 
         ContentResolver resolver = context.getContentResolver();
         BrowserDB.updateFaviconForUrl(resolver, pageUrl, encodedFavicon, faviconURL);
     }
 
@@ -371,16 +375,19 @@ public class LoadFaviconTask extends UiA
         } catch (URISyntaxException e) {
             Log.e(LOGTAG, "The provided favicon URL is not valid");
             return null;
         } catch (Exception e) {
             Log.e(LOGTAG, "Couldn't download favicon.", e);
         }
 
         if (loadedBitmaps != null) {
+            // Fetching bytes to store can fail. saveFaviconToDb will
+            // do the right thing, but we still choose to cache the
+            // downloaded icon in memory.
             saveFaviconToDb(loadedBitmaps.getBytesForDatabaseStorage());
             return pushToCacheAndGetResult(loadedBitmaps);
         }
 
         if (isUsingDefaultURL) {
             Favicons.putFaviconInFailedCache(faviconURL);
             return null;
         }
--- a/mobile/android/base/favicons/decoders/LoadFaviconResult.java
+++ b/mobile/android/base/favicons/decoders/LoadFaviconResult.java
@@ -29,46 +29,48 @@ public class LoadFaviconResult {
     Iterator<Bitmap> bitmapsDecoded;
 
     public Iterator<Bitmap> getBitmaps() {
         return bitmapsDecoded;
     }
 
     /**
      * Return a representation of this result suitable for storing in the database.
-     * For
      *
-     * @return A byte array containing the bytes from which this result was decoded.
+     * @return A byte array containing the bytes from which this result was decoded,
+     *         or null if re-encoding failed.
      */
     public byte[] getBytesForDatabaseStorage() {
         // Begin by normalising the buffer.
         if (offset != 0 || length != faviconBytes.length) {
             final byte[] normalised = new byte[length];
             System.arraycopy(faviconBytes, offset, normalised, 0, length);
             offset = 0;
             faviconBytes = normalised;
         }
 
-        // For results containing a single image, we re-encode the result as a PNG in an effort to
-        // save space.
-        if (!isICO) {
-            Bitmap favicon = ((FaviconDecoder.SingleBitmapIterator) bitmapsDecoded).peek();
-            byte[] data = null;
-            ByteArrayOutputStream stream = new ByteArrayOutputStream();
-
-            if (favicon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
-                data = stream.toByteArray();
-            } else {
-                Log.w(LOGTAG, "Favicon compression failed.");
-            }
-
-            return data;
-        }
-
         // For results containing multiple images, we store the result verbatim. (But cutting the
         // buffer to size first).
         // We may instead want to consider re-encoding the entire ICO as a collection of efficiently
         // encoded PNGs. This may not be worth the CPU time (Indeed, the encoding of single-image
         // favicons may also not be worth the time/space tradeoff.).
-        return faviconBytes;
+        if (isICO) {
+            return faviconBytes;
+        }
+
+        // For results containing a single image, we re-encode the
+        // result as a PNG in an effort to save space.
+        final Bitmap favicon = ((FaviconDecoder.SingleBitmapIterator) bitmapsDecoded).peek();
+        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        try {
+            if (favicon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
+                return stream.toByteArray();
+            }
+        } catch (OutOfMemoryError e) {
+            Log.w(LOGTAG, "Out of memory re-compressing favicon.");
+        }
+
+        Log.w(LOGTAG, "Favicon re-compression failed.");
+        return null;
     }
 
 }
--- a/mobile/android/base/home/BookmarksPanel.java
+++ b/mobile/android/base/home/BookmarksPanel.java
@@ -60,17 +60,17 @@ public class BookmarksPanel extends Home
     private CursorLoaderCallbacks mLoaderCallbacks;
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
 
         mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
 
-        mList.setContextMenuInfoFactory(new HomeListView.ContextMenuInfoFactory() {
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
                 if (type == Bookmarks.TYPE_FOLDER) {
                     // We don't show a context menu for folders
                     return null;
                 }
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
--- a/mobile/android/base/home/DynamicPanel.java
+++ b/mobile/android/base/home/DynamicPanel.java
@@ -10,16 +10,17 @@ import org.json.JSONObject;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.HomeItems;
 import org.mozilla.gecko.db.DBUtils;
 import org.mozilla.gecko.db.HomeProvider;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.home.PanelLayout.ContextMenuRegistry;
 import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
 import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 
 import android.app.Activity;
 import android.content.ContentResolver;
@@ -221,20 +222,28 @@ public class DynamicPanel extends HomeFr
     private boolean requiresAuth() {
         return mPanelConfig.getAuthConfig() != null;
     }
 
     /**
      * Lazily creates layout for panel data.
      */
     private void createPanelLayout() {
+        final ContextMenuRegistry contextMenuRegistry = new ContextMenuRegistry() {
+            @Override
+            public void register(View view) {
+                registerForContextMenu(view);
+            }
+        };
+
         switch(mPanelConfig.getLayoutType()) {
             case FRAME:
                 final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
-                mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler, mUrlOpenListener);
+                mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler,
+                        mUrlOpenListener, contextMenuRegistry);
                 break;
 
             default:
                 throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
         }
 
         Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
         mView.addView(mPanelLayout);
--- a/mobile/android/base/home/FramePanelLayout.java
+++ b/mobile/android/base/home/FramePanelLayout.java
@@ -14,18 +14,19 @@ import android.util.Log;
 import android.view.View;
 
 class FramePanelLayout extends PanelLayout {
     private static final String LOGTAG = "GeckoFramePanelLayout";
 
     private final View mChildView;
     private final ViewConfig mChildConfig;
 
-    public FramePanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, OnUrlOpenListener urlOpenListener) {
-        super(context, panelConfig, datasetHandler, urlOpenListener);
+    public FramePanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
+        OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
+        super(context, panelConfig, datasetHandler, urlOpenListener, contextMenuRegistry);
 
         // This layout can only hold one view so we simply
         // take the first defined view from PanelConfig.
         mChildConfig = panelConfig.getViewAt(0);
         if (mChildConfig == null) {
             throw new IllegalStateException("FramePanelLayout requires a view in PanelConfig");
         }
 
--- a/mobile/android/base/home/HomeContextMenuInfo.java
+++ b/mobile/android/base/home/HomeContextMenuInfo.java
@@ -3,21 +3,21 @@
  * 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.db.BrowserContract.Combined;
 import org.mozilla.gecko.util.StringUtils;
 
+import android.database.Cursor;
 import android.text.TextUtils;
 import android.view.View;
 import android.widget.AdapterView.AdapterContextMenuInfo;
 
-
 /**
  * A ContextMenuInfo for HomeListView
  */
 public class HomeContextMenuInfo extends AdapterContextMenuInfo {
 
     public String url;
     public String title;
     public boolean isFolder = false;
@@ -47,9 +47,16 @@ public class HomeContextMenuInfo extends
     }
 
     public String getDisplayTitle() {
         if (!TextUtils.isEmpty(title)) {
             return title;
         }
         return StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
     }
-}
\ No newline at end of file
+
+    /*
+     * Interface for creating ContextMenuInfo from cursors
+     */
+    public interface Factory {
+        public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor);
+    }
+}
--- a/mobile/android/base/home/HomeListView.java
+++ b/mobile/android/base/home/HomeListView.java
@@ -31,17 +31,17 @@ public class HomeListView extends ListVi
 
     // On URL open listener
     protected OnUrlOpenListener mUrlOpenListener;
 
     // Top divider
     private boolean mShowTopDivider;
 
     // ContextMenuInfo maker
-    private ContextMenuInfoFactory mContextMenuInfoFactory;
+    private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
 
     public HomeListView(Context context) {
         this(context, null);
     }
 
     public HomeListView(Context context, AttributeSet attrs) {
         this(context, attrs, R.attr.homeListViewStyle);
     }
@@ -116,27 +116,20 @@ public class HomeListView extends ListVi
                     position--;
                 }
 
                 listener.onItemClick(parent, view, position, id);
             }
         });
     }
 
-    public void setContextMenuInfoFactory(final ContextMenuInfoFactory factory) {
+    public void setContextMenuInfoFactory(final HomeContextMenuInfo.Factory factory) {
         mContextMenuInfoFactory = factory;
     }
 
     public OnUrlOpenListener getOnUrlOpenListener() {
         return mUrlOpenListener;
     }
 
     public void setOnUrlOpenListener(OnUrlOpenListener listener) {
         mUrlOpenListener = listener;
     }
-
-    /*
-     * Interface for creating ContextMenuInfo from cursors
-     */
-    public interface ContextMenuInfoFactory {
-    	public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor);
-    }
 }
--- a/mobile/android/base/home/LastTabsPanel.java
+++ b/mobile/android/base/home/LastTabsPanel.java
@@ -110,17 +110,17 @@ public class LastTabsPanel extends HomeF
                     return;
                 }
 
                 final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
                 mNewTabsListener.onNewTabs(new String[] { url });
             }
         });
 
-        mList.setContextMenuInfoFactory(new HomeListView.ContextMenuInfoFactory() {
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
                 return info;
             }
         });
--- a/mobile/android/base/home/MostRecentPanel.java
+++ b/mobile/android/base/home/MostRecentPanel.java
@@ -98,17 +98,17 @@ public class MostRecentPanel extends Hom
                 final Cursor c = mAdapter.getCursor(position);
                 final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
 
                 // This item is a TwoLinePageRow, so we allow switch-to-tab.
                 mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
             }
         });
 
-        mList.setContextMenuInfoFactory(new HomeListView.ContextMenuInfoFactory() {
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
                 info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
                 info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
                 final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
--- a/mobile/android/base/home/PanelGridView.java
+++ b/mobile/android/base/home/PanelGridView.java
@@ -27,27 +27,42 @@ import android.widget.GridView;
 public class PanelGridView extends GridView
                            implements DatasetBacked, PanelView {
     private static final String LOGTAG = "GeckoPanelGridView";
 
     private final ViewConfig viewConfig;
     private final PanelViewAdapter adapter;
     private PanelViewItemHandler itemHandler;
     private OnItemOpenListener itemOpenListener;
+    private HomeContextMenuInfo mContextMenuInfo;
+    private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
 
     public PanelGridView(Context context, ViewConfig viewConfig) {
         super(context, null, R.attr.panelGridViewStyle);
 
         this.viewConfig = viewConfig;
         itemHandler = new PanelViewItemHandler(viewConfig);
 
         adapter = new PanelViewAdapter(context, viewConfig);
         setAdapter(adapter);
 
         setOnItemClickListener(new PanelGridItemClickListener());
+        setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+            @Override
+            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+                Cursor cursor = (Cursor) parent.getItemAtPosition(position);
+                if (cursor == null || mContextMenuInfoFactory == null) {
+                    mContextMenuInfo = null;
+                    return false;
+                }
+
+                mContextMenuInfo = mContextMenuInfoFactory.makeInfoForCursor(view, position, id, cursor);
+                return showContextMenuForChild(PanelGridView.this);
+            }
+        });
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
         itemHandler.setOnItemOpenListener(itemOpenListener);
     }
 
@@ -76,9 +91,19 @@ public class PanelGridView extends GridV
     }
 
     private class PanelGridItemClickListener implements AdapterView.OnItemClickListener {
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             itemHandler.openItemAtPosition(adapter.getCursor(), position);
         }
     }
+
+    @Override
+    public HomeContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    @Override
+    public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory) {
+        mContextMenuInfoFactory = factory;
+    }
 }
--- a/mobile/android/base/home/PanelLayout.java
+++ b/mobile/android/base/home/PanelLayout.java
@@ -1,16 +1,17 @@
 /* -*- 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.R;
+import org.mozilla.gecko.db.BrowserContract.HomeItems;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig;
 import org.mozilla.gecko.home.HomeConfig.ItemHandler;
 import org.mozilla.gecko.home.HomeConfig.PanelConfig;
 import org.mozilla.gecko.home.HomeConfig.ViewConfig;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
@@ -74,16 +75,17 @@ import com.squareup.picasso.Picasso;
  */
 abstract class PanelLayout extends FrameLayout {
     private static final String LOGTAG = "GeckoPanelLayout";
 
     protected final SparseArray<ViewState> mViewStates;
     private final PanelConfig mPanelConfig;
     private final DatasetHandler mDatasetHandler;
     private final OnUrlOpenListener mUrlOpenListener;
+    private final ContextMenuRegistry mContextMenuRegistry;
 
     /**
      * To be used by panel views to express that they are
      * backed by datasets.
      */
     public interface DatasetBacked {
         public void setDataset(Cursor cursor);
         public void setFilterManager(FilterManager manager);
@@ -214,30 +216,37 @@ abstract class PanelLayout extends Frame
          * before.
          */
         public void resetDataset(int viewIndex);
     }
 
     public interface PanelView {
         public void setOnItemOpenListener(OnItemOpenListener listener);
         public void setOnKeyListener(OnKeyListener listener);
+        public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory);
     }
 
     public interface FilterManager {
         public FilterDetail getPreviousFilter();
         public boolean canGoBack();
         public void goBack();
     }
 
-    public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler, OnUrlOpenListener urlOpenListener) {
+    public interface ContextMenuRegistry {
+        public void register(View view);
+    }
+
+    public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
+            OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
         super(context);
         mViewStates = new SparseArray<ViewState>();
         mPanelConfig = panelConfig;
         mDatasetHandler = datasetHandler;
         mUrlOpenListener = urlOpenListener;
+        mContextMenuRegistry = contextMenuRegistry;
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
         final int count = mViewStates.size();
         for (int i = 0; i < count; i++) {
@@ -364,16 +373,27 @@ abstract class PanelLayout extends Frame
 
                 default:
                     throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName());
             }
 
             PanelView panelView = (PanelView) view;
             panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState));
             panelView.setOnKeyListener(new PanelKeyListener(viewState));
+            panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
+                @Override
+                public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
+                    final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
+                    info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL));
+                    info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE));
+                    return info;
+                }
+            });
+
+            mContextMenuRegistry.register(view);
 
             if (view instanceof DatasetBacked) {
                 DatasetBacked datasetBacked = (DatasetBacked) view;
                 datasetBacked.setFilterManager(new PanelFilterManager(viewState));
 
                 if (viewConfig.isRefreshEnabled()) {
                     view = new PanelRefreshLayout(getContext(), view,
                                                   mPanelConfig.getId(), viewConfig.getIndex());
--- a/mobile/android/base/home/ReadingListPanel.java
+++ b/mobile/android/base/home/ReadingListPanel.java
@@ -105,17 +105,17 @@ public class ReadingListPanel extends Ho
                 String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
                 url = ReaderModeUtils.getAboutReaderForUrl(url);
 
                 // This item is a TwoLinePageRow, so we allow switch-to-tab.
                 mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
             }
         });
 
-        mList.setContextMenuInfoFactory(new HomeListView.ContextMenuInfoFactory() {
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.URL));
                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(ReadingListItems.TITLE));
                 info.readingListItemId = cursor.getInt(cursor.getColumnIndexOrThrow(ReadingListItems._ID));
                 return info;
             }
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -181,17 +181,17 @@ public class TopSitesPanel extends HomeF
 
                 final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
 
                 // This item is a TwoLinePageRow, so we allow switch-to-tab.
                 mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
             }
         });
 
-        mList.setContextMenuInfoFactory(new HomeListView.ContextMenuInfoFactory() {
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
                 info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
                 final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
                 if (cursor.isNull(bookmarkIdCol)) {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -519,16 +519,18 @@ pref("accessibility.typeaheadfind.autost
 pref("accessibility.typeaheadfind.casesensitive", 0);
 pref("accessibility.typeaheadfind.linksonly", true);
 pref("accessibility.typeaheadfind.startlinksonly", false);
 pref("accessibility.typeaheadfind.timeout", 4000);
 pref("accessibility.typeaheadfind.enabletimeout", true);
 pref("accessibility.typeaheadfind.soundURL", "beep");
 pref("accessibility.typeaheadfind.enablesound", true);
 pref("accessibility.typeaheadfind.prefillwithselection", true);
+pref("accessibility.typeaheadfind.matchesCountTimeout", 250);
+pref("accessibility.typeaheadfind.matchesCountLimit", 100);
 
 // use Mac OS X Appearance panel text smoothing setting when rendering text, disabled by default
 pref("gfx.use_text_smoothing_setting", false);
 
 // loading and rendering of framesets and iframes
 pref("browser.frames.enabled", true);
 
 // Number of characters to consider emphasizing for rich autocomplete results
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -185,17 +185,17 @@ class MochitestRunner(MozbuildObject):
 
     def run_desktop_test(self, context, suite=None, test_paths=None, debugger=None,
         debugger_args=None, slowscript=False, screenshot_on_fail = False, shuffle=False, keep_open=False,
         rerun_failures=False, no_autorun=False, repeat=0, run_until_failure=False,
         slow=False, chunk_by_dir=0, total_chunks=None, this_chunk=None,
         jsdebugger=False, debug_on_failure=False, start_at=None, end_at=None,
         e10s=False, dmd=False, dump_output_directory=None,
         dump_about_memory_after_test=False, dump_dmd_after_test=False,
-        install_extension=None, quiet=False, environment=[], **kwargs):
+        install_extension=None, quiet=False, environment=[], app_override=None, **kwargs):
         """Runs a mochitest.
 
         test_paths are path to tests. They can be a relative path from the
         top source directory, an absolute filename, or a directory containing
         test files.
 
         suite is the type of mochitest to run. It can be one of ('plain',
         'chrome', 'browser', 'metro', 'a11y').
@@ -347,16 +347,21 @@ class MochitestRunner(MozbuildObject):
             options.debugger = debugger
 
         if debugger_args:
             if options.debugger == None:
                 print("--debugger-args passed, but no debugger specified.")
                 return 1
             options.debuggerArgs = debugger_args
 
+        if app_override == "dist":
+            options.app = self.get_binary_path(where='staged-package')
+        elif app_override:
+            options.app = app_override
+
         options = opts.verifyOptions(options, runner)
 
         if options is None:
             raise Exception('mochitest option validator failed.')
 
         # We need this to enable colorization of output.
         self.log_manager.enable_unstructured()
 
@@ -516,16 +521,23 @@ def MochitestCommand(func):
         help='Do not print test log lines unless a failure occurs.')
     func = quiet(func)
 
     setenv = CommandArgument('--setenv', default=[], action='append',
                              metavar='NAME=VALUE', dest='environment',
                              help="Sets the given variable in the application's environment")
     func = setenv(func)
 
+    app_override = CommandArgument('--app-override', default=None, action='store',
+        help="Override the default binary used to run tests with the path you provide, e.g. " \
+            " --app-override /usr/bin/firefox . " \
+            "If you have run ./mach package beforehand, you can specify 'dist' to " \
+            "run tests against the distribution bundle's binary.");
+    func = app_override(func)
+
     return func
 
 def B2GCommand(func):
     """Decorator that adds shared command arguments to b2g mochitest commands."""
 
     busybox = CommandArgument('--busybox', default=None,
         help='Path to busybox binary to install on device')
     func = busybox(func)
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -293,81 +293,16 @@ function FAIL(message, resultCode) {
 function limitURILength(str, len) {
   len = len || 140;
   if (str.length > len)
     return str.slice(0, len) + "...";
   return str;
 }
 
 /**
- * Utilities for dealing with promises and Task.jsm
- */
-const TaskUtils = {
-  /**
-   * Add logging to a promise.
-   *
-   * @param {Promise} promise
-   * @return {Promise} A promise behaving as |promise|, but with additional
-   * logging in case of uncaught error.
-   */
-  captureErrors: function captureErrors(promise) {
-    return promise.then(
-      null,
-      function onError(reason) {
-        LOG("Uncaught asynchronous error: " + reason + " at\n" + reason.stack);
-        throw reason;
-      }
-    );
-  },
-  /**
-   * Spawn a new Task from a generator.
-   *
-   * This function behaves as |Task.spawn|, with the exception that it
-   * adds logging in case of uncaught error. For more information, see
-   * the documentation of |Task.jsm|.
-   *
-   * @param {generator} gen Some generator.
-   * @return {Promise} A promise built from |gen|, with the same semantics
-   * as |Task.spawn(gen)|.
-   */
-  spawn: function spawn(gen) {
-    return this.captureErrors(Task.spawn(gen));
-  },
-  /**
-   * Execute a mozIStorage statement asynchronously, wrapping the
-   * result in a promise.
-   *
-   * @param {mozIStorageStaement} statement A statement to be executed
-   * asynchronously. The semantics are the same as these of |statement.execute|.
-   * @param {function*} onResult A callback, called for each successive result.
-   *
-   * @return {Promise} A promise, resolved successfully if |statement.execute|
-   * succeeds, rejected if it fails.
-   */
-  executeStatement: function executeStatement(statement, onResult) {
-    let deferred = Promise.defer();
-    onResult = onResult || function() {};
-    statement.executeAsync({
-      handleResult: onResult,
-      handleError: function handleError(aError) {
-        deferred.reject(aError);
-      },
-      handleCompletion: function handleCompletion(aReason) {
-        statement.finalize();
-        // Note that, in case of error, deferred.reject(aError)
-        // has already been called by this point, so the call to
-        // |deferred.resolve| is simply ignored.
-        deferred.resolve(aReason);
-      }
-    });
-    return deferred.promise;
-  }
-};
-
-/**
  * Ensures an assertion is met before continuing. Should be used to indicate
  * fatal errors.
  * @param  assertion
  *         An assertion that must be met
  * @param  message
  *         A message to display if the assertion is not met
  * @param  resultCode
  *         The NS_ERROR_* value to throw if the assertion is not met
@@ -1222,17 +1157,17 @@ Engine.prototype = {
   /**
    * Retrieves the data from the engine's file asynchronously. If the engine's
    * dataType is XML, the document element is placed in the engine's data field.
    *
    * @returns {Promise} A promise, resolved successfully if initializing from
    * data succeeds, rejected if it fails.
    */
   _asyncInitFromFile: function SRCH_ENG__asyncInitFromFile() {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       if (!this._file || !(yield OS.File.exists(this._file.path)))
         FAIL("File must exist before calling initFromFile!", Cr.NS_ERROR_UNEXPECTED);
 
       if (this._dataType == SEARCH_DATA_XML) {
         let fileURI = NetUtil.ioService.newFileURI(this._file);
         yield this._retrieveSearchXMLData(fileURI.spec);
       } else {
         ERROR("Unsuppored engine _dataType in _initFromFile: \"" +
@@ -1271,17 +1206,17 @@ Engine.prototype = {
 
   /**
    * Retrieves the engine data from a URI asynchronously and initializes it.
    *
    * @returns {Promise} A promise, resolved successfully if retrieveing data
    * succeeds.
    */
   _asyncInitFromURI: function SRCH_ENG__asyncInitFromURI() {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       LOG("_asyncInitFromURI: Loading engine from: \"" + this._uri.spec + "\".");
       yield this._retrieveSearchXMLData(this._uri.spec);
       // Now that the data is loaded, initialize the engine object
       this._initFromData();
     }.bind(this));
   },
 
   /**
@@ -2914,17 +2849,17 @@ SearchService.prototype = {
 
   /**
    * Asynchronous implementation of the initializer.
    *
    * @returns {Promise} A promise, resolved successfully if the initialization
    * succeeds.
    */
   _asyncInit: function SRCH_SVC__asyncInit() {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       LOG("_asyncInit start");
       try {
         yield checkForSyncCompletion(this._asyncLoadEngines());
       } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
         this._initRV = Cr.NS_ERROR_FAILURE;
         LOG("_asyncInit: failure loading engines: " + ex);
       }
       this._addObservers();
@@ -3111,17 +3046,17 @@ SearchService.prototype = {
 
   /**
    * Loads engines asynchronously.
    *
    * @returns {Promise} A promise, resolved successfully if loading data
    * succeeds.
    */
   _asyncLoadEngines: function SRCH_SVC__asyncLoadEngines() {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       LOG("_asyncLoadEngines: start");
       // See if we have a cache file so we don't have to parse a bunch of XML.
       let cache = {};
       let cacheEnabled = getBoolPref(BROWSER_SEARCH_PREF + "cache.enabled", true);
       if (cacheEnabled) {
         let cacheFilePath = OS.Path.join(OS.Constants.Path.profileDir, "search.json");
         cache = yield checkForSyncCompletion(this._asyncReadCacheFile(cacheFilePath));
       }
@@ -3151,17 +3086,17 @@ SearchService.prototype = {
       if (loadFromJARs) {
         Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "find-jar-engines");
         [chromeFiles, chromeURIs] =
           yield checkForSyncCompletion(this._asyncFindJAREngines());
       }
 
       let toLoad = chromeFiles.concat(loadDirs);
       function hasModifiedDir(aList) {
-        return TaskUtils.spawn(function() {
+        return Task.spawn(function() {
           let modifiedDir = false;
 
           for (let dir of aList) {
             if (!cache.directories || !cache.directories[dir.path]) {
               modifiedDir = true;
               break;
             }
 
@@ -3238,17 +3173,17 @@ SearchService.prototype = {
    * Read from a given cache file asynchronously.
    *
    * @param aPath the file path.
    *
    * @returns {Promise} A promise, resolved successfully if retrieveing data
    * succeeds.
    */
   _asyncReadCacheFile: function SRCH_SVC__asyncReadCacheFile(aPath) {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       let json;
       try {
         let bytes = yield OS.File.read(aPath);
         json = JSON.parse(new TextDecoder().decode(bytes));
       } catch (ex) {
         LOG("_asyncReadCacheFile: Error reading cache file: " + ex);
         json = {};
       }
@@ -3402,17 +3337,17 @@ SearchService.prototype = {
    * succeeds.
    */
   _asyncLoadEnginesFromDir: function SRCH_SVC__asyncLoadEnginesFromDir(aDir) {
     LOG("_asyncLoadEnginesFromDir: Searching in " + aDir.path + " for search engines.");
 
     // Check whether aDir is the user profile dir
     let isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
     let iterator = new OS.File.DirectoryIterator(aDir.path);
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       let osfiles = yield iterator.nextBatch();
       iterator.close();
 
       let engines = [];
       for (let osfile of osfiles) {
         if (osfile.isDir || osfile.isSymLink)
           continue;
 
@@ -3462,17 +3397,17 @@ SearchService.prototype = {
    * Loads engines from Chrome URLs asynchronously.
    *
    * @param aURLs a list of URLs.
    *
    * @returns {Promise} A promise, resolved successfully if loading data
    * succeeds.
    */
   _asyncLoadFromChromeURLs: function SRCH_SVC__asyncLoadFromChromeURLs(aURLs) {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       let engines = [];
       for (let url of aURLs) {
         try {
           LOG("_asyncLoadFromChromeURLs: loading engine from chrome url: " + url);
           let engine = new Engine(NetUtil.newURI(url), SEARCH_DATA_XML, true);
           yield checkForSyncCompletion(engine._asyncInitFromURI());
           engines.push(engine);
         } catch (ex if ex.result != Cr.NS_ERROR_ALREADY_INITIALIZED) {
@@ -3546,17 +3481,17 @@ SearchService.prototype = {
 
   /**
    * Loads jar engines asynchronously.
    *
    * @returns {Promise} A promise, resolved successfully if finding jar engines
    * succeeds.
    */
   _asyncFindJAREngines: function SRCH_SVC__asyncFindJAREngines() {
-    return TaskUtils.spawn(function() {
+    return Task.spawn(function() {
       LOG("_asyncFindJAREngines: looking for engines in JARs")
 
       let rootURIPref = "";
       try {
         rootURIPref = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + "jarURIs");
       } catch (ex) {}
 
       if (!rootURIPref) {
@@ -3751,42 +3686,42 @@ SearchService.prototype = {
 
   // nsIBrowserSearchService
   init: function SRCH_SVC_init(observer) {
     LOG("SearchService.init");
     let self = this;
     if (!this._initStarted) {
       TelemetryStopwatch.start("SEARCH_SERVICE_INIT_MS");
       this._initStarted = true;
-      TaskUtils.spawn(function task() {
+      Task.spawn(function task() {
         try {
           yield checkForSyncCompletion(engineMetadataService.init());
           // Complete initialization by calling asynchronous initializer.
           yield self._asyncInit();
           TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
         } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
           // No need to pursue asynchronous because synchronous fallback was
           // called and has finished.
           TelemetryStopwatch.finish("SEARCH_SERVICE_INIT_MS");
         } catch (ex) {
           self._initObservers.reject(ex);
           TelemetryStopwatch.cancel("SEARCH_SERVICE_INIT_MS");
         }
       });
     }
     if (observer) {
-      TaskUtils.captureErrors(this._initObservers.promise.then(
+      this._initObservers.promise.then(
         function onSuccess() {
           observer.onInitComplete(self._initRV);
         },
         function onError(aReason) {
           Components.utils.reportError("Internal error while initializing SearchService: " + aReason);
           observer.onInitComplete(Components.results.NS_ERROR_UNEXPECTED);
         }
-      ));
+      );
     }
   },
 
   get isInitialized() {
     return gInitialized;
   },
 
   getEngines: function SRCH_SVC_getEngines(aCount) {
@@ -4296,17 +4231,17 @@ var engineMetadataService = {
    * Asynchronous initializer
    *
    * Note: In the current implementation, initialization never fails.
    */
   init: function epsInit() {
     if (!this._initializer) {
       // Launch asynchronous initialization
       let initializer = this._initializer = Promise.defer();
-      TaskUtils.spawn((function task_init() {
+      Task.spawn((function task_init() {
         LOG("metadata init: starting");
         switch (this._initState) {
           case engineMetadataService._InitStates.NOT_STARTED:
             // 1. Load json file if it exists
             try {
               let contents = yield OS.File.read(this._jsonFile);
               if (this._initState == engineMetadataService._InitStates.FINISHED_SUCCESS) {
                 // No need to pursue asynchronous initialization,
@@ -4337,17 +4272,17 @@ var engineMetadataService = {
         function onSuccess() {
           initializer.resolve();
         },
         function onError() {
           initializer.reject();
         }
       );
     }
-    return TaskUtils.captureErrors(this._initializer.promise);
+    return this._initializer.promise;
   },
 
   /**
    * Synchronous implementation of initializer
    *
    * This initializer is able to pick wherever the async initializer
    * is waiting. The asynchronous initializer is expected to stop
    * if it detects that the synchronous initializer has completed
@@ -4495,18 +4430,17 @@ var engineMetadataService = {
         promise = promise.then(
           function onSuccess() {
             Services.obs.notifyObservers(null,
               SEARCH_SERVICE_TOPIC,
               SEARCH_SERVICE_METADATA_WRITTEN);
             LOG("metadata writeCommit: done");
           }
         );
-        // Use our error logging instead of the default one.
-        return TaskUtils.captureErrors(promise).then(null, () => {});
+        return promise;
       }
       this._lazyWriter = new DeferredTask(writeCommit, LAZY_SERIALIZE_DELAY);
     }
     LOG("metadata _commit: (re)setting timer");
     this._lazyWriter.disarm();
     this._lazyWriter.arm();
   },
   _lazyWriter: null
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -68,48 +68,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
   "resource://gre/modules/Deprecated.jsm");
 
 /**
  * Utilities for dealing with promises and Task.jsm
  */
 const TaskUtils = {
   /**
-   * Add logging to a promise.
-   *
-   * @param {Promise} promise
-   * @return {Promise} A promise behaving as |promise|, but with additional
-   * logging in case of uncaught error.
-   */
-  captureErrors: function captureErrors(promise) {
-    return promise.then(
-      null,
-      function onError(reason) {
-        Cu.reportError("Uncaught asynchronous error: " + reason + " at\n"
-          + reason.stack + "\n");
-        throw reason;
-      }
-    );
-  },
-
-  /**
-   * Spawn a new Task from a generator.
-   *
-   * This function behaves as |Task.spawn|, with the exception that it
-   * adds logging in case of uncaught error. For more information, see
-   * the documentation of |Task.jsm|.
-   *
-   * @param {generator} gen Some generator.
-   * @return {Promise} A promise built from |gen|, with the same semantics
-   * as |Task.spawn(gen)|.
-   */
-  spawn: function spawn(gen) {
-    return this.captureErrors(Task.spawn(gen));
-  },
-  /**
    * Read the bytes from a blob, asynchronously.
    *
    * @return {Promise}
    * @resolve {ArrayBuffer} In case of success, the bytes contained in the blob.
    * @reject {DOMError} In case of error, the underlying DOMError.
    */
   readBlob: function readBlob(blob) {
     let deferred = Promise.defer();
@@ -293,17 +261,17 @@ this.PageThumbs = {
 
     let url = aBrowser.currentURI.spec;
     let channel = aBrowser.docShell.currentDocumentChannel;
     let originalURL = channel.originalURI.spec;
 
     // see if this was an error response.
     let wasError = this._isChannelErrorResponse(channel);
 
-    TaskUtils.spawn((function task() {
+    Task.spawn((function task() {
       let isSuccess = true;
       try {
         let blob = yield this.captureToBlob(aBrowser.contentWindow);
         let buffer = yield TaskUtils.readBlob(blob);
         yield this._store(originalURL, url, buffer, wasError);
       } catch (_) {
         isSuccess = false;
       }
@@ -348,17 +316,17 @@ this.PageThumbs = {
    * @param aOriginalURL The URL with which the capture was initiated.
    * @param aFinalURL The URL to which aOriginalURL ultimately resolved.
    * @param aData An ArrayBuffer containing the image data.
    * @param aNoOverwrite If true and files for the URLs already exist, the files
    *                     will not be overwritten.
    * @return {Promise}
    */
   _store: function PageThumbs__store(aOriginalURL, aFinalURL, aData, aNoOverwrite) {
-    return TaskUtils.spawn(function () {
+    return Task.spawn(function () {
       let telemetryStoreTime = new Date();
       yield PageThumbsStorage.writeData(aFinalURL, aData, aNoOverwrite);
       Services.telemetry.getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
         .add(new Date() - telemetryStoreTime);
 
       Services.obs.notifyObservers(null, "page-thumbnail:create", aFinalURL);
       // We've been redirected. Create a copy of the current thumbnail for
       // the redirect source. We need to do this because:
--- a/toolkit/components/typeaheadfind/nsITypeAheadFind.idl
+++ b/toolkit/components/typeaheadfind/nsITypeAheadFind.idl
@@ -12,17 +12,17 @@
 
 /******************************** Declarations *******************************/
 
 interface nsIDocShell;
 
 
 /****************************** nsTypeAheadFind ******************************/
 
-[scriptable, uuid(0749a445-19d3-4eb9-9d66-78eca8c6f604)]
+[scriptable, uuid(f4411c5b-761b-498c-8050-dcfc8311f69e)]
 interface nsITypeAheadFind : nsISupports
 {
   /****************************** Initializer ******************************/
 
   /* Necessary initialization that can't happen in the constructor, either
    * because function calls here may fail, or because the docShell is
    * required. */
   void init(in nsIDocShell aDocShell);
@@ -32,32 +32,37 @@ interface nsITypeAheadFind : nsISupports
 
   /* Find aSearchString in page.  If aLinksOnly is true, only search the page's
    * hyperlinks for the string. */
   unsigned short find(in AString aSearchString, in boolean aLinksOnly);
 
   /* Find another match in the page. */
   unsigned short findAgain(in boolean findBackwards, in boolean aLinksOnly);
 
+  /* Return the range of the most recent match. */
+  nsIDOMRange getFoundRange();
+
 
   /**************************** Helper functions ***************************/
 
   /* Change searched docShell.  This happens when e.g. we use the same
    * nsITypeAheadFind object to search different tabs. */
   void setDocShell(in nsIDocShell aDocShell);
 
   /* Change the look of the the "found match" selection to aToggle, and repaint
    * the selection. */
   void setSelectionModeAndRepaint(in short toggle);
 
   /* Collapse the "found match" selection to its start.  Because not all
    * matches are owned by the same selection controller, this doesn't
    * necessarily happen automatically. */
   void collapseSelection();
 
+  /* Check if a range is visible */
+  boolean isRangeVisible(in nsIDOMRange aRange, in boolean aMustBeInViewPort);
 
   /******************************* Attributes ******************************/
 
   readonly attribute AString searchString;
                                         // Most recent search string
   attribute boolean caseSensitive;      // Searches are case sensitive
   readonly attribute nsIDOMElement foundLink;
                                         // Most recent elem found, if a link
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
@@ -61,17 +61,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTypeAheadFind)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTypeAheadFind)
 
 NS_IMPL_CYCLE_COLLECTION(nsTypeAheadFind, mFoundLink, mFoundEditable,
                          mCurrentWindow, mStartFindRange, mSearchRange,
                          mStartPointRange, mEndPointRange, mSoundInterface,
-                         mFind)
+                         mFind, mFoundRange)
 
 static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
 
 #define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1"
 
 nsTypeAheadFind::nsTypeAheadFind():
   mStartLinksOnlyPref(false),
   mCaretBrowsingOn(false),
@@ -171,16 +171,17 @@ nsTypeAheadFind::SetDocShell(nsIDocShell
 
   mStartFindRange = nullptr;
   mStartPointRange = nullptr;
   mSearchRange = nullptr;
   mEndPointRange = nullptr;
 
   mFoundLink = nullptr;
   mFoundEditable = nullptr;
+  mFoundRange = nullptr;
   mCurrentWindow = nullptr;
 
   mSelectionController = nullptr;
 
   mFind = nullptr;
 
   return NS_OK;
 }
@@ -270,16 +271,17 @@ nsTypeAheadFind::PlayNotFoundSound()
 nsresult
 nsTypeAheadFind::FindItNow(nsIPresShell *aPresShell, bool aIsLinksOnly,
                            bool aIsFirstVisiblePreferred, bool aFindPrev,
                            uint16_t* aResult)
 {
   *aResult = FIND_NOTFOUND;
   mFoundLink = nullptr;
   mFoundEditable = nullptr;
+  mFoundRange = nullptr;
   mCurrentWindow = nullptr;
   nsCOMPtr<nsIPresShell> startingPresShell (GetPresShell());
   if (!startingPresShell) {    
     nsCOMPtr<nsIDocShell> ds = do_QueryReferent(mDocShell);
     NS_ENSURE_TRUE(ds, NS_ERROR_FAILURE);
 
     startingPresShell = ds->GetPresShell();
     mPresShell = do_GetWeakReference(startingPresShell);    
@@ -432,16 +434,18 @@ nsTypeAheadFind::FindItNow(nsIPresShell 
             // Start at the end of returnRange
             returnRange->CloneRange(getter_AddRefs(mStartPointRange));
             mStartPointRange->Collapse(false);
           }
         }
         continue;
       }
 
+      mFoundRange = returnRange;
+
       // ------ Success! -------
       // Hide old selection (new one may be on a different controller)
       if (selection) {
         selection->CollapseToStart();
         SetSelectionModeAndRepaint(nsISelectionController::SELECTION_ON);
       }
 
       // Make sure new document is selected
@@ -1086,16 +1090,54 @@ nsTypeAheadFind::GetSelection(nsIPresShe
     frame->GetSelectionController(presContext, aSelCon);
     if (*aSelCon) {
       (*aSelCon)->GetSelection(nsISelectionController::SELECTION_NORMAL,
                                aDOMSel);
     }
   }
 }
 
+NS_IMETHODIMP
+nsTypeAheadFind::GetFoundRange(nsIDOMRange** aFoundRange)
+{
+  NS_ENSURE_ARG_POINTER(aFoundRange);
+  if (mFoundRange == nullptr) {
+    *aFoundRange = nullptr;
+    return NS_OK;
+  }
+
+  mFoundRange->CloneRange(aFoundRange);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTypeAheadFind::IsRangeVisible(nsIDOMRange *aRange,
+                                bool aMustBeInViewPort,
+                                bool *aResult)
+{
+  // Jump through hoops to extract the docShell from the range.
+  nsCOMPtr<nsIDOMNode> node;
+  aRange->GetStartContainer(getter_AddRefs(node));
+  nsCOMPtr<nsIDOMDocument> document;
+  node->GetOwnerDocument(getter_AddRefs(document));
+  nsCOMPtr<nsIDOMWindow> window;
+  document->GetDefaultView(getter_AddRefs(window));
+  nsCOMPtr<nsIWebNavigation> navNav (do_GetInterface(window));
+  nsCOMPtr<nsIDocShell> docShell (do_GetInterface(navNav));
+
+  // Set up the arguments needed to check if a range is visible.
+  nsCOMPtr<nsIPresShell> presShell (docShell->GetPresShell());
+  nsRefPtr<nsPresContext> presContext = presShell->GetPresContext();
+  nsCOMPtr<nsIDOMRange> startPointRange = new nsRange(presShell->GetDocument());
+  *aResult = IsRangeVisible(presShell, presContext, aRange,
+                            aMustBeInViewPort, false,
+                            getter_AddRefs(startPointRange),
+                            nullptr);
+  return NS_OK;
+}
 
 bool
 nsTypeAheadFind::IsRangeVisible(nsIPresShell *aPresShell,
                                 nsPresContext *aPresContext,
                                 nsIDOMRange *aRange, bool aMustBeInViewPort,
                                 bool aGetTopVisibleLeaf,
                                 nsIDOMRange **aFirstVisibleRange,
                                 bool *aUsesIndependentSelection)
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.h
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.h
@@ -75,16 +75,17 @@ protected:
   nsCString mNotFoundSoundURL;
 
   // PRBools are used instead of PRPackedBools because the address of the
   // boolean variable is getting passed into a method.
   bool mStartLinksOnlyPref;
   bool mCaretBrowsingOn;
   nsCOMPtr<nsIDOMElement> mFoundLink;     // Most recent elem found, if a link
   nsCOMPtr<nsIDOMElement> mFoundEditable; // Most recent elem found, if editable
+  nsCOMPtr<nsIDOMRange> mFoundRange;      // Most recent range found
   nsCOMPtr<nsIDOMWindow> mCurrentWindow;
   // mLastFindLength is the character length of the last find string.  It is used for
   // disabling the "not found" sound when using backspace or delete
   uint32_t mLastFindLength;
 
   // Sound is played asynchronously on some platforms.
   // If we destroy mSoundInterface before sound has played, it won't play
   nsCOMPtr<nsISound> mSoundInterface;
--- a/toolkit/content/tests/chrome/findbar_window.xul
+++ b/toolkit/content/tests/chrome/findbar_window.xul
@@ -96,18 +96,27 @@
       ok(gFindBar.hidden, "Failed to close findbar after testQuickFindText");
       testFindWithHighlight();
       gFindBar.close();
       ok(gFindBar.hidden, "Failed to close findbar after testFindWithHighlight");
       testFindbarSelection();
       testDrop();
       testQuickFindLink();
       if (gHasFindClipboard)
-        testStatusText();
-      testQuickFindClose();
+        testStatusText(afterStatusText);
+      else
+        afterStatusText();
+
+      function afterStatusText() {
+        testFindCountUI(function() {
+          gFindBar.close();
+          ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI");
+          testQuickFindClose();
+        });
+      }
     }
 
     function testFindbarSelection() {
       function checkFindbarState(aTestName, aExpSelection) {
         document.getElementById("cmd_find").doCommand();
         ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName);
         ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
            "testFindbarSelection: find field is not focused: " + aTestName);
@@ -158,19 +167,20 @@
         ok(gFindBar.hidden,
            "_isClosedCallback: Failed to auto-close quick find bar after " +
            gFindBar._quickFindTimeoutLength + "ms");
         finish();
       };
       setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100);
     }
 
-    function testStatusText() {
+    function testStatusText(aCallback) {
       var _delayedCheckStatusText = function() {
         ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link");
+        aCallback();
       };
       setTimeout(_delayedCheckStatusText, 100);
     }
 
     function enterStringIntoFindField(aString) {
       for (var i=0; i < aString.length; i++) {
         var event = document.createEvent("KeyEvents");
         event.initKeyEvent("keypress", true, true, null, false, false,
@@ -379,16 +389,110 @@
          "testQuickFindText: find field is not focused");
 
       enterStringIntoFindField(SEARCH_TEXT);
       ok(gBrowser.contentWindow.getSelection() == SEARCH_TEXT,
          "testQuickFindText: failed to find '" + SEARCH_TEXT + "'");
       testClipboardSearchString(SEARCH_TEXT);
     }
 
+    // Perform an async function in serial on each of the list items.
+    function asyncForEach(list, async, callback) {
+      let i = 0;
+      let len = list.length;
+
+      if (!len)
+        return callback();
+
+      async(list[i], function handler() {
+          i++;
+          if (i < len) {
+            async(list[i], handler, i);
+          } else {
+            callback();
+          }
+      }, i);
+    }
+
+    function testFindCountUI(callback) {
+      clearFocus();
+      document.getElementById("cmd_find").doCommand();
+
+      ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar");
+      ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField,
+         "testFindCountUI: find field is not focused");
+
+      let matchCase = gFindBar.getElement("find-case-sensitive");
+      if (matchCase.checked)
+        matchCase.click();
+
+      let foundMatches = gFindBar._foundMatches;
+      let tests = [{
+        text: "t",
+        current: 5,
+        total: 10,
+      }, {
+        text: "te",
+        current: 3,
+        total: 5,
+      }, {
+        text: "tes",
+        current: 1,
+        total: 2,
+      }, {
+        text: "texxx",
+        current: 0,
+        total: 0
+      }];
+      let regex = /([\d]*)\sof\s([\d]*)/;
+      let timeout = gFindBar._matchesCountTimeoutLength + 20;
+
+      function assertMatches(aTest, aMatches) {
+        window.opener.wrappedJSObject.SimpleTest.is(aTest.current, aMatches[1],
+          "Currently highlighted match should be at " + aTest.current);
+        window.opener.wrappedJSObject.SimpleTest.is(aTest.total, aMatches[2],
+          "Total amount of matches should be " + aTest.total);
+      }
+
+      function testString(aTest, aNext) {
+        gFindBar.clear();
+        enterStringIntoFindField(aTest.text);
+
+        setTimeout(function() {
+          let matches = foundMatches.value.match(regex);
+          if (!aTest.total) {
+            ok(!matches, "No message should be shown when 0 matches are expected");
+            aNext();
+          } else {
+            assertMatches(aTest, matches);
+            let cycleTests = [];
+            let cycles = aTest.total;
+            while (--cycles) {
+              aTest.current++;
+              if (aTest.current > aTest.total)
+                aTest.current = 1;
+              cycleTests.push({
+                current: aTest.current,
+                total: aTest.total
+              });
+            }
+            asyncForEach(cycleTests, function(aCycleTest, aNextCycle) {
+              gFindBar.onFindAgainCommand();
+              setTimeout(function() {
+                assertMatches(aCycleTest, foundMatches.value.match(regex));
+                aNextCycle();
+              }, timeout);
+            }, aNext);
+          }
+        }, timeout);
+      }
+
+      asyncForEach(tests, testString, callback);
+    }
+
     function testClipboardSearchString(aExpected) {
       if (!gHasFindClipboard)
         return;
 
       if (!aExpected)
         aExpected = "";
       var searchStr = gFindBar.browser.finder.clipboardSearchString;
       ok(searchStr.toLowerCase() == aExpected.toLowerCase(),
--- a/toolkit/content/tests/chrome/test_findbar.xul
+++ b/toolkit/content/tests/chrome/test_findbar.xul
@@ -1,37 +1,40 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet 
   href="chrome://mochikit/content/tests/SimpleTest/test.css"
   type="text/css"?>
 <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=257061
 https://bugzilla.mozilla.org/show_bug.cgi?id=288254
 -->
-<window title="Mozilla Bug 288254"
+<window title="Mozilla Bug 257061 and Bug 288254"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript" 
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>      
 
 <body  xmlns="http://www.w3.org/1999/xhtml">
-<a target="_blank" 
+<a target="_blank"
+   href="https://bugzilla.mozilla.org/show_bug.cgi?id=257061">Mozilla Bug 257061</a>
+<a target="_blank"
    href="https://bugzilla.mozilla.org/show_bug.cgi?id=288254">Mozilla Bug 288254</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 </pre>
 </body>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
-/** Test for Bug 288254 **/
+/** Test for Bug 257061 and Bug 288254 **/
 SimpleTest.waitForExplicitFinish();
 window.open("findbar_window.xul", "findbartest", 
             "chrome,width=600,height=600");
 
 ]]>
 </script>
 
 </window>
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -186,16 +186,17 @@
                          class="findbar-case-sensitive tabbable"
                          label="&caseSensitive.label;"
                          accesskey="&caseSensitive.accesskey;"
                          tooltiptext="&caseSensitive.tooltiptext;"
                          oncommand="_setCaseSensitivity(this.checked);"
                          type="checkbox"
                          xbl:inherits="accesskey=matchcaseaccesskey"/>
       <xul:label anonid="match-case-status" class="findbar-find-fast"/>
+      <xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
       <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
       <xul:description anonid="find-status"
                        control="findbar-textbox"
                        class="findbar-find-fast findbar-find-status">
       <!-- Do not use value, first child is used because it provides a11y with text change events -->
       </xul:description>
     </xul:hbox>
     <xul:toolbarbutton anonid="find-closebutton"
@@ -313,29 +314,34 @@
         }
       })]]></field>
 
       <field name="_destroyed">false</field>
 
       <constructor><![CDATA[
         // These elements are accessed frequently and are therefore cached
         this._findField = this.getElement("findbar-textbox");
+        this._foundMatches = this.getElement("found-matches");
         this._findStatusIcon = this.getElement("find-status-icon");
         this._findStatusDesc = this.getElement("find-status");
 
         this._foundURL = null;
 
         let prefsvc =
           Components.classes["@mozilla.org/preferences-service;1"]
                     .getService(Components.interfaces.nsIPrefBranch);
 
         this._quickFindTimeoutLength =
           prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
         this._flashFindBar =
           prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
+        this._matchesCountTimeoutLength =
+          prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountTimeout");
+        this._matchesCountLimit =
+          prefsvc.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
 
         prefsvc.addObserver("accessibility.typeaheadfind",
                             this._observer, false);
         prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
                             this._observer, false);
         prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
                             this._observer, false);
 
@@ -402,16 +408,20 @@
           if (this._highlightTimeout) {
             clearTimeout(this._highlightTimeout);
             this._highlightTimeout = null;
           }
           if (this._findResetTimeout) {
             clearTimeout(this._findResetTimeout);
             this._findResetTimeout = null;
           }
+          if (this._updateMatchesCountTimeout) {
+            clearTimeout(this._updateMatchesCountTimeout);
+            this._updateMatchesCountTimeout = null;
+          }
         ]]></body>
       </method>
 
       <method name="_setFindCloseTimeout">
         <body><![CDATA[
           if (this._quickFindTimeout)
             clearTimeout(this._quickFindTimeout);
 
@@ -425,16 +435,64 @@
           this._quickFindTimeout = setTimeout(() => {
              if (this._findMode != this.FIND_NORMAL)
                this.close();
              this._quickFindTimeout = null;
            }, this._quickFindTimeoutLength);
         ]]></body>
       </method>
 
+      <field name="_pluralForm">null</field>
+      <property name="pluralForm">
+        <getter><![CDATA[
+          if (!this._pluralForm) {
+            this._pluralForm = Components.utils.import(
+                               "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
+          }
+          return this._pluralForm;
+        ]]></getter>
+      </property>
+
+      <method name="_updateMatchesCountWorker">
+        <parameter name="aRes"/>
+        <body><![CDATA[
+          let word = this._findField.value;
+          if (aRes == this.nsITypeAheadFind.FIND_NOTFOUND || !word) {
+            this._foundMatches.hidden = true;
+            this._foundMatches.value = "";
+          } else {
+            let matchesCount = this.browser.finder.requestMatchesCount(
+              word, this._matchesCountLimit, this._findMode == this.FIND_LINKS);
+            window.clearTimeout(this._updateMatchesCountTimeout);
+            this._updateMatchesCountTimeout = null;
+          }
+        ]]></body>
+      </method>
+
+      <!--
+        - Updates the search match count after each find operation on a new string.
+        - @param aRes
+        -        the result of the find operation
+        -->
+      <method name="_updateMatchesCount">
+        <parameter name="aRes"/>
+        <body><![CDATA[
+          if (this._matchesCountLimit == 0 || !this._dispatchFindEvent("matchescount"))
+            return;
+
+          if (this._updateMatchesCountTimeout) {
+            window.clearTimeout(this._updateMatchesCountTimeout);
+            this._updateMatchesCountTimeout = null;
+          }
+          this._updateMatchesCountTimeout =
+            window.setTimeout(() => this._updateMatchesCountWorker(aRes),
+                              this._matchesCountTimeoutLength);
+        ]]></body>
+      </method>
+
       <!--
         - Turns highlight on or off.
         - @param aHighlight (boolean)
         -        Whether to turn the highlight on or off
         -->
       <method name="toggleHighlight">
         <parameter name="aHighlight"/>
         <body><![CDATA[
@@ -443,16 +501,19 @@
 
           let word = this._findField.value;
           // Bug 429723. Don't attempt to highlight ""
           if (aHighlight && !word)
             return;
 
           this.browser._lastSearchHighlight = aHighlight;
           this.browser.finder.highlight(aHighlight, word);
+
+          // Update the matches count
+          this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
         ]]></body>
       </method>
 
       <!--
         - Updates the case-sensitivity mode of the findbar and its UI.
         - @param [optional] aString
         -        The string for which case sensitivity might be turned on.
         -        This only used when case-sensitivity is in auto mode,
@@ -498,36 +559,46 @@
           // Just set the pref; our observer will change the find bar behavior
           prefsvc.setIntPref("accessibility.typeaheadfind.casesensitive",
                              aCaseSensitive ? 1 : 0);
 
           this._dispatchFindEvent("casesensitivitychange");
         ]]></body>
       </method>
 
+      <field name="_strBundle">null</field>
+      <property name="strBundle">
+        <getter><![CDATA[
+          if (!this._strBundle) {
+            this._strBundle =
+              Components.classes["@mozilla.org/intl/stringbundle;1"]
+                        .getService(Components.interfaces.nsIStringBundleService)
+                        .createBundle("chrome://global/locale/findbar.properties");
+          }
+          return this._strBundle;
+        ]]></getter>
+      </property>
+
       <!--
         - Opens and displays the find bar.
         -
         - @param aMode
         -        the find mode to be used, which is either FIND_NORMAL,
         -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
         -        find mode if any or FIND_NORMAL.
         - @returns true if the find bar wasn't previously open, false otherwise.
         -->
       <method name="open">
         <parameter name="aMode"/>
         <body><![CDATA[
           if (aMode != undefined)
             this._findMode = aMode;
 
           if (!this._notFoundStr) {
-            let stringsBundle =
-              Components.classes["@mozilla.org/intl/stringbundle;1"]
-                        .getService(Components.interfaces.nsIStringBundleService)
-                        .createBundle("chrome://global/locale/findbar.properties");
+            var stringsBundle = this.strBundle;
             this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
             this._wrappedToTopStr =
               stringsBundle.GetStringFromName("WrappedToTop");
             this._wrappedToBottomStr =
               stringsBundle.GetStringFromName("WrappedToBottom");
             this._normalFindStr =
               stringsBundle.GetStringFromName("NormalFind");
             this._fastFindStr =
@@ -948,16 +1019,17 @@
               break;
             case this.nsITypeAheadFind.FIND_FOUND:
             default:
               this._findStatusIcon.removeAttribute("status");
               this._findStatusDesc.textContent = "";
               this._findField.removeAttribute("status");
               break;
           }
+          this._updateMatchesCount(res);
         ]]></body>
       </method>
 
       <method name="updateControlState">
         <parameter name="aResult"/>
         <parameter name="aFindPrevious"/>
         <body><![CDATA[
           this._updateStatusUI(aResult, aFindPrevious);
@@ -1165,16 +1237,46 @@
             this._findFailedString = null;
 
           if (this._findMode != this.FIND_NORMAL)
             this._setFindCloseTimeout();
         ]]></body>
       </method>
 
       <!--
+        - This handles all the result changes for matches counts.
+        - @param aResult
+        -   Result Object, containing the total amount of matches and a vector
+        -   of the current result.
+        -->
+      <method name="onMatchesCountResult">
+        <parameter name="aResult"/>
+        <body><![CDATA[
+          if (aResult.total !== 0) {
+            if (aResult.total == -1) {
+              this._foundMatches.value = this.pluralForm.get(
+                this._matchesCountLimit,
+                this.strBundle.GetStringFromName("FoundTooManyMatches")
+              ).replace("#1", this._matchesCountLimit);
+            } else {
+              this._foundMatches.value = this.pluralForm.get(
+                aResult.total,
+                this.strBundle.GetStringFromName("FoundMatches")
+              ).replace("#1", aResult.current)
+               .replace("#2", aResult.total);
+            }
+            this._foundMatches.hidden = false;
+          } else {
+            this._foundMatches.hidden = true;
+            this._foundMatches.value = "";
+          }
+        ]]></body>
+      </method>
+
+      <!--
         - This handler may cancel a request to focus content by returning |false|
         - explicitly.
         -->
       <method name="shouldFocusContent">
         <body><![CDATA[
           const fm = Components.classes["@mozilla.org/focus-manager;1"]
                                .getService(Components.interfaces.nsIFocusManager);
           if (fm.focusedWindow != window)
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -521,16 +521,17 @@ function createMultiLineDumper(aLevel) {
  *        - stacktrace: for trace(). Holds the array of stack frames as given by
  *        getStack().
  */
 function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {})
 {
   let consoleEvent = {
     ID: "jsm",
     innerID: aConsole.innerID || aFrame.filename,
+    consoleID: aConsole.consoleID,
     level: aLevel,
     filename: aFrame.filename,
     lineNumber: aFrame.lineNumber,
     functionName: aFrame.functionName,
     timeStamp: Date.now(),
     arguments: aArgs,
   };
 
@@ -577,26 +578,29 @@ function sendConsoleAPIMessage(aConsole,
  *                            messages are logged based on their log level.
  *                            If falsy value, all messages will be logged.
  *                            If wrong value that doesn't match any key of
  *                            LOG_LEVELS, no message will be logged
  *        - dump {function} : An optional function to intercept all strings
  *                            written to stdout
  *        - innerID {string}: An ID representing the source of the message.
  *                            Normally the inner ID of a DOM window.
+ *        - consoleID {string} : String identified for the console, this will
+ *                            be passed through the console notifications
  * @return {object}
  *        A console API instance object
  */
 function ConsoleAPI(aConsoleOptions = {}) {
   // Normalize console options to set default values
   // in order to avoid runtime checks on each console method call.
   this.dump = aConsoleOptions.dump || dump;
   this.prefix = aConsoleOptions.prefix || "";
   this.maxLogLevel = aConsoleOptions.maxLogLevel || "all";
   this.innerID = aConsoleOptions.innerID || null;
+  this.consoleID = aConsoleOptions.consoleID || "";
 
   // Bind all the functions to this object.
   for (let prop in this) {
     if (typeof(this[prop]) === "function") {
       this[prop] = this[prop].bind(this);
     }
   }
 }
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -313,8 +313,28 @@ exports.dumpn = function dumpn(str) {
 // loader, so define it on dumpn instead.
 exports.dumpn.wantLogging = false;
 
 exports.dbg_assert = function dbg_assert(cond, e) {
   if (!cond) {
     return e;
   }
 }
+
+
+/**
+ * Utility function for updating an object with the properties of another
+ * object.
+ *
+ * @param aTarget Object
+ *        The object being updated.
+ * @param aNewAttrs Object
+ *        The new attributes being set on the target.
+ */
+exports.update = function update(aTarget, aNewAttrs) {
+  for (let key in aNewAttrs) {
+    let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key);
+
+    if (desc) {
+      Object.defineProperty(aTarget, key, desc);
+    }
+  }
+}
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -7,17 +7,17 @@
 "use strict";
 
 const Debugger = require("Debugger");
 const Services = require("Services");
 const { Cc, Ci, Cu, components } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
-const { dbg_assert, dumpn } = DevToolsUtils;
+const { dbg_assert, dumpn, update } = DevToolsUtils;
 const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
 const { all, defer, resolve } = promise;
 
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
 
 let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
@@ -5353,35 +5353,16 @@ function getFrameLocation(aFrame) {
   return {
     url: aFrame.script.url,
     line: aFrame.script.getOffsetLine(aFrame.offset),
     column: getOffsetColumn(aFrame.offset, aFrame.script)
   }
 }
 
 /**
- * Utility function for updating an object with the properties of another
- * object.
- *
- * @param aTarget Object
- *        The object being updated.
- * @param aNewAttrs Object
- *        The new attributes being set on the target.
- */
-function update(aTarget, aNewAttrs) {
-  for (let key in aNewAttrs) {
-    let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key);
-
-    if (desc) {
-      Object.defineProperty(aTarget, key, desc);
-    }
-  }
-}
-
-/**
  * Returns true if its argument is not null.
  */
 function isNotNull(aThing) {
   return aThing !== null;
 }
 
 /**
  * Performs a request to load the desired URL and returns a promise.
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -1217,17 +1217,18 @@ BrowserAddonList.prototype.onInstalled =
 BrowserAddonList.prototype.onUninstalled = function (aAddon) {
   this._actorByAddonId.delete(aAddon.id);
   this._onListChanged();
 };
 
 function BrowserAddonActor(aConnection, aAddon) {
   this.conn = aConnection;
   this._addon = aAddon;
-  this._contextPool = null;
+  this._contextPool = new ActorPool(this.conn);
+  this.conn.addActorPool(this._contextPool);
   this._threadActor = null;
   this._global = null;
   AddonManager.addAddonListener(this);
 }
 
 BrowserAddonActor.prototype = {
   actorPrefix: "addon",
 
@@ -1248,27 +1249,36 @@ BrowserAddonActor.prototype = {
   },
 
   get global() {
     return this._global;
   },
 
   form: function BAA_form() {
     dbg_assert(this.actorID, "addon should have an actorID.");
+    if (!this._consoleActor) {
+      let {AddonConsoleActor} = require("devtools/server/actors/webconsole");
+      this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
+      this._contextPool.addActor(this._consoleActor);
+    }
 
     return {
       actor: this.actorID,
       id: this.id,
       name: this._addon.name,
       url: this.url,
       debuggable: this._addon.isDebuggable,
+      consoleActor: this._consoleActor.actorID,
     };
   },
 
   disconnect: function BAA_disconnect() {
+    this.conn.removeActorPool(this._contextPool);
+    this._contextPool = null;
+    this._consoleActor = null;
     this._addon = null;
     this._global = null;
     AddonManager.removeAddonListener(this);
   },
 
   setOptions: function BAA_setOptions(aOptions) {
     if ("global" in aOptions) {
       this._global = aOptions.global;
@@ -1297,34 +1307,30 @@ BrowserAddonActor.prototype = {
   },
 
   onAttach: function BAA_onAttach() {
     if (this.exited) {
       return { type: "exited" };
     }
 
     if (!this.attached) {
-      this._contextPool = new ActorPool(this.conn);
-      this.conn.addActorPool(this._contextPool);
-
       this._threadActor = new AddonThreadActor(this.conn, this,
                                                this._addon.id);
       this._contextPool.addActor(this._threadActor);
     }
 
     return { type: "tabAttached", threadActor: this._threadActor.actorID };
   },
 
   onDetach: function BAA_onDetach() {
     if (!this.attached) {
       return { error: "wrongState" };
     }
 
-    this.conn.removeActorPool(this._contextPool);
-    this._contextPool = null;
+    this._contextPool.remoteActor(this._threadActor);
 
     this._threadActor = null;
 
     return { type: "detached" };
   },
 
   preNest: function() {
     let e = Services.wm.getEnumerator(null);
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 const Debugger = require("Debugger");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
+const { update } = require("devtools/toolkit/DevToolsUtils");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
   return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
@@ -41,17 +42,16 @@ for (let name of ["WebConsoleUtils", "Co
       }
       return require("devtools/toolkit/webconsole/utils")[prop];
     }.bind(null, name),
     configurable: true,
     enumerable: true
   });
 }
 
-
 /**
  * The WebConsoleActor implements capabilities needed for the Web Console
  * feature.
  *
  * @constructor
  * @param object aConnection
  *        The connection to the client, DebuggerServerConnection.
  * @param object [aParentActor]
@@ -1310,16 +1310,17 @@ WebConsoleActor.prototype =
    */
   prepareConsoleMessageForRemote:
   function WCA_prepareConsoleMessageForRemote(aMessage)
   {
     let result = WebConsoleUtils.cloneObject(aMessage);
     delete result.wrappedJSObject;
     delete result.ID;
     delete result.innerID;
+    delete result.consoleID;
 
     result.arguments = Array.map(aMessage.arguments || [], (aObj) => {
       let dbgObj = this.makeDebuggeeValue(aObj, true);
       return this.createValueGrip(dbgObj);
     });
 
     result.styles = Array.map(aMessage.styles || [], (aString) => {
       return this.createValueGrip(aString);
@@ -1394,16 +1395,101 @@ WebConsoleActor.prototype.requestTypes =
   evaluateJS: WebConsoleActor.prototype.onEvaluateJS,
   autocomplete: WebConsoleActor.prototype.onAutocomplete,
   clearMessagesCache: WebConsoleActor.prototype.onClearMessagesCache,
   getPreferences: WebConsoleActor.prototype.onGetPreferences,
   setPreferences: WebConsoleActor.prototype.onSetPreferences,
   sendHTTPRequest: WebConsoleActor.prototype.onSendHTTPRequest
 };
 
+
+/**
+ * The AddonConsoleActor implements capabilities needed for the add-on web
+ * console feature.
+ *
+ * @constructor
+ * @param object aAddon
+ *        The add-on that this console watches.
+ * @param object aConnection
+ *        The connection to the client, DebuggerServerConnection.
+ * @param object aParentActor
+ *        The parent BrowserAddonActor actor.
+ */
+function AddonConsoleActor(aAddon, aConnection, aParentActor)
+{
+  this.addon = aAddon;
+  WebConsoleActor.call(this, aConnection, aParentActor);
+}
+
+AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype);
+
+update(AddonConsoleActor.prototype, {
+  constructor: AddonConsoleActor,
+
+  actorPrefix: "addonConsole",
+
+  /**
+   * The add-on that this console watches.
+   */
+  addon: null,
+
+  /**
+   * The main add-on JS global
+   */
+  get window() {
+    return this.parentActor.global;
+  },
+
+  /**
+   * Destroy the current AddonConsoleActor instance.
+   */
+  disconnect: function ACA_disconnect()
+  {
+    WebConsoleActor.prototype.disconnect.call(this);
+    this.addon = null;
+  },
+
+  /**
+   * Handler for the "startListeners" request.
+   *
+   * @param object aRequest
+   *        The JSON request object received from the Web Console client.
+   * @return object
+   *         The response object which holds the startedListeners array.
+   */
+  onStartListeners: function ACA_onStartListeners(aRequest)
+  {
+    let startedListeners = [];
+
+    while (aRequest.listeners.length > 0) {
+      let listener = aRequest.listeners.shift();
+      switch (listener) {
+        case "ConsoleAPI":
+          if (!this.consoleAPIListener) {
+            this.consoleAPIListener =
+              new ConsoleAPIListener(null, this, "addon/" + this.addon.id);
+            this.consoleAPIListener.init();
+          }
+          startedListeners.push(listener);
+          break;
+      }
+    }
+    return {
+      startedListeners: startedListeners,
+      nativeConsoleAPI: true,
+      traits: this.traits,
+    };
+  },
+});
+
+AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes);
+AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners;
+
+exports.AddonConsoleActor = AddonConsoleActor;
+
 /**
  * Creates an actor for a network event.
  *
  * @constructor
  * @param object aChannel
  *        The nsIChannel associated with this event.
  * @param object aWebConsoleActor
  *        The parent WebConsoleActor instance for this object.
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tests/unit/test_consoleID.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { console, ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
+
+const { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const { ConsoleAPIListener } = require("devtools/toolkit/webconsole/utils");
+
+var seenMessages = 0;
+var seenTypes = 0;
+
+var callback = {
+  onConsoleAPICall: function(aMessage) {
+    switch (aMessage.consoleID) {
+      case "foo":
+        do_check_eq(aMessage.level, "warn");
+        do_check_eq(aMessage.arguments[0], "Warning from foo");
+        seenTypes |= 1;
+        break;
+      case "bar":
+        do_check_eq(aMessage.level, "error");
+        do_check_eq(aMessage.arguments[0], "Error from bar");
+        seenTypes |= 2;
+        break;
+      default:
+        do_check_eq(aMessage.level, "log");
+        do_check_eq(aMessage.arguments[0], "Hello from default console");
+        seenTypes |= 4;
+        break;
+    }
+    seenMessages++;
+  }
+};
+
+/**
+ * Tests that the consoleID property of the ConsoleAPI options gets passed
+ * through to console messages.
+ */
+function run_test() {
+  let console1 = new ConsoleAPI({
+    consoleID: "foo"
+  });
+  let console2 = new ConsoleAPI({
+    consoleID: "bar"
+  });
+
+  console.log("Hello from default console");
+  console1.warn("Warning from foo");
+  console2.error("Error from bar");
+
+  let listener = new ConsoleAPIListener(null, callback);
+  listener.init();
+  let messages = listener.getCachedMessages();
+
+  seenTypes = 0;
+  seenMessages = 0;
+  messages.forEach(callback.onConsoleAPICall);
+  do_check_eq(seenMessages, 3);
+  do_check_eq(seenTypes, 7);
+
+  seenTypes = 0;
+  seenMessages = 0;
+  console.log("Hello from default console");
+  console1.warn("Warning from foo");
+  console2.error("Error from bar");
+  do_check_eq(seenMessages, 3);
+  do_check_eq(seenTypes, 7);
+
+  listener.destroy();
+
+  listener = new ConsoleAPIListener(null, callback, "foo");
+  listener.init();
+  let messages = listener.getCachedMessages();
+
+  seenTypes = 0;
+  seenMessages = 0;
+  messages.forEach(callback.onConsoleAPICall);
+  do_check_eq(seenMessages, 2);
+  do_check_eq(seenTypes, 1);
+
+  seenTypes = 0;
+  seenMessages = 0;
+  console.log("Hello from default console");
+  console1.warn("Warning from foo");
+  console2.error("Error from bar");
+  do_check_eq(seenMessages, 1);
+  do_check_eq(seenTypes, 1);
+}
--- a/toolkit/devtools/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/tests/unit/xpcshell.ini
@@ -2,9 +2,10 @@
 head = head_devtools.js
 tail =
 
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_async-utils.js]
+[test_consoleID.js]
 [test_require_lazy.js]
\ No newline at end of file
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1286,21 +1286,24 @@ ConsoleServiceListener.prototype =
  * @param nsIDOMWindow aWindow
  *        Optional - the window object for which we are created. This is used
  *        for filtering out messages that belong to other windows.
  * @param object aOwner
  *        The owner object must have the following methods:
  *        - onConsoleAPICall(). This method is invoked with one argument, the
  *        Console API message that comes from the observer service, whenever
  *        a relevant console API call is received.
+ * @param string aConsoleID
+ *        Options - The consoleID that this listener should listen to
  */
-function ConsoleAPIListener(aWindow, aOwner)
+function ConsoleAPIListener(aWindow, aOwner, aConsoleID)
 {
   this.window = aWindow;
   this.owner = aOwner;
+  this.consoleID = aConsoleID;
   if (this.window) {
     this.layoutHelpers = new LayoutHelpers(this.window);
   }
 }
 exports.ConsoleAPIListener = ConsoleAPIListener;
 
 ConsoleAPIListener.prototype =
 {
@@ -1318,16 +1321,22 @@ ConsoleAPIListener.prototype =
    * console API call object that comes from the observer service.
    *
    * @type object
    * @see WebConsoleActor
    */
   owner: null,
 
   /**
+   * The consoleID that we listen for. If not null then only messages from this
+   * console will be returned.
+   */
+  consoleID: null,
+
+  /**
    * Initialize the window.console API observer.
    */
   init: function CAL_init()
   {
     // Note that the observer is process-wide. We will filter the messages as
     // needed, see CAL_observe().
     Services.obs.addObserver(this, "console-api-log-event", false);
   },
@@ -1350,16 +1359,19 @@ ConsoleAPIListener.prototype =
     let apiMessage = aMessage.wrappedJSObject;
     if (this.window) {
       let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
       if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
         // Not the same window!
         return;
       }
     }
+    if (this.consoleID && apiMessage.consoleID != this.consoleID) {
+      return;
+    }
 
     this.owner.onConsoleAPICall(apiMessage);
   },
 
   /**
    * Get the cached messages for the current inner window and its (i)frames.
    *
    * @param boolean [aIncludePrivate=false]
@@ -1380,16 +1392,20 @@ ConsoleAPIListener.prototype =
       messages = ConsoleAPIStorage.getEvents();
     } else {
       let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
       ids.forEach((id) => {
         messages = messages.concat(ConsoleAPIStorage.getEvents(id));
       });
     }
 
+    if (this.consoleID) {
+      messages = messages.filter((m) => m.consoleID == this.consoleID);
+    }
+
     if (aIncludePrivate) {
       return messages;
     }
 
     return messages.filter((m) => !m.private);
   },
 
   /**
--- a/toolkit/locales/en-US/chrome/global/findbar.properties
+++ b/toolkit/locales/en-US/chrome/global/findbar.properties
@@ -5,11 +5,17 @@
 # strings used by the Find bar, split from browser.properties
 NotFound=Phrase not found
 WrappedToTop=Reached end of page, continued from top
 WrappedToBottom=Reached top of page, continued from bottom
 NormalFind=Find in page
 FastFind=Quick find
 FastFindLinks=Quick find (links only)
 CaseSensitive=(Case sensitive)
-FoundMatchCount=%S match
-FoundMatchesCount=%S matches
-FoundTooManyMatches=More than %S matches
+# LOCALIZATION NOTE (FoundMatches): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is currently selected match and #2 the total amount of matches.
+FoundMatches=#1 of #2 match;#1 of #2 matches
+# LOCALIZATION NOTE (FoundTooManyMatches): Semicolon-separated list of plural
+# forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the total amount of matches allowed before counting stops.
+FoundTooManyMatches=More than #1 match;More than #1 matches
--- a/toolkit/modules/Finder.jsm
+++ b/toolkit/modules/Finder.jsm
@@ -69,17 +69,19 @@ Finder.prototype = {
       findBackwards: aFindBackwards,
       linkURL: linkURL,
       rect: this._getResultRect(),
       searchString: this._searchString,
       storeResult: aStoreResult
     };
 
     for (let l of this._listeners) {
-      l.onFindResult(data);
+      try {
+        l.onFindResult(data);
+      } catch (ex) {}
     }
   },
 
   get searchString() {
     if (!this._searchString && this._fastFind.searchString)
       this._searchString = this._fastFind.searchString;
     return this._searchString;
   },
@@ -189,18 +191,20 @@ Finder.prototype = {
   removeSelection: function() {
     this._fastFind.collapseSelection();
     this.enableSelection();
   },
 
   focusContent: function() {
     // Allow Finder listeners to cancel focusing the content.
     for (let l of this._listeners) {
-      if (!l.shouldFocusContent())
-        return;
+      try {
+        if (!l.shouldFocusContent())
+          return;
+      } catch (ex) {}
     }
 
     let fastFind = this._fastFind;
     const fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
     try {
       // Try to find the best possible match that should receive focus and
       // block scrolling on focus since find already scrolls. Further
       // scrolling is due to user action, so don't override this.
@@ -250,16 +254,190 @@ Finder.prototype = {
         controller.scrollLine(false);
         break;
       case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
         controller.scrollLine(true);
         break;
     }
   },
 
+  requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) {
+    let window = this._getWindow();
+    let result = this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, window);
+
+    // Count matches in (i)frames AFTER searching through the main window.
+    for (let frame of result._framesToCount) {
+      // We've reached our limit; no need to do more work.
+      if (result.total == -1 || result.total == aMatchLimit)
+        break;
+      this._countMatchesInWindow(aWord, aMatchLimit, aLinksOnly, frame, result);
+    }
+
+    // The `_currentFound` and `_framesToCount` properties are only used for
+    // internal bookkeeping between recursive calls.
+    delete result._currentFound;
+    delete result._framesToCount;
+
+    for (let l of this._listeners) {
+      try {
+        l.onMatchesCountResult(result);
+      } catch (ex) {}
+    }
+  },
+
+  /**
+   * Counts the number of matches for the searched word in the passed window's
+   * content.
+   * @param aWord
+   *        the word to search for.
+   * @param aMatchLimit
+   *        the maximum number of matches shown (for speed reasons).
+   * @param aLinksOnly
+   *        whether we should only search through links.
+   * @param aWindow
+   *        the window to search in. Passing undefined will search the
+   *        current content window. Optional.
+   * @param aStats
+   *        the Object that is returned by this function. It may be passed as an
+   *        argument here in the case of a recursive call.
+   * @returns an object stating the number of matches and a vector for the current match.
+   */
+  _countMatchesInWindow: function(aWord, aMatchLimit, aLinksOnly, aWindow = null, aStats = null) {
+    aWindow = aWindow || this._getWindow();
+    aStats = aStats || {
+      total: 0,
+      current: 0,
+      _framesToCount: new Set(),
+      _currentFound: false
+    };
+
+    // If we already reached our max, there's no need to do more work!
+    if (aStats.total == -1 || aStats.total == aMatchLimit) {
+      aStats.total = -1;
+      return aStats;
+    }
+
+    this._collectFrames(aWindow, aStats);
+
+    let foundRange = this._fastFind.getFoundRange();
+
+    this._findIterator(aWord, aWindow, aRange => {
+      if (!aLinksOnly || this._rangeStartsInLink(aRange)) {
+        ++aStats.total;
+        if (!aStats._currentFound) {
+          ++aStats.current;
+          aStats._currentFound = (foundRange &&
+            aRange.startContainer == foundRange.startContainer &&
+            aRange.startOffset == foundRange.startOffset &&
+            aRange.endContainer == foundRange.endContainer &&
+            aRange.endOffset == foundRange.endOffset);
+        }
+      }
+      if (aStats.total == aMatchLimit) {
+        aStats.total = -1;
+        return false;
+      }
+    });
+
+    return aStats;
+  },
+
+  /**
+   * Basic wrapper around nsIFind that provides invoking a callback `aOnFind`
+   * each time an occurence of `aWord` string is found.
+   *
+   * @param aWord
+   *        the word to search for.
+   * @param aWindow
+   *        the window to search in.
+   * @param aOnFind
+   *        the Function to invoke when a word is found. if Boolean `false` is
+   *        returned, the find operation will be stopped and the Function will
+   *        not be invoked again.
+   */
+  _findIterator: function(aWord, aWindow, aOnFind) {
+    let doc = aWindow.document;
+    let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
+               doc.body : doc.documentElement;
+
+    let searchRange = doc.createRange();
+    searchRange.selectNodeContents(body);
+
+    let startPt = searchRange.cloneRange();
+    startPt.collapse(true);
+
+    let endPt = searchRange.cloneRange();
+    endPt.collapse(false);
+
+    let retRange = null;
+
+    let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
+                   .createInstance()
+                   .QueryInterface(Ci.nsIFind);
+    finder.caseSensitive = this._fastFind.caseSensitive;
+
+    while ((retRange = finder.Find(aWord, searchRange, startPt, endPt))) {
+      if (aOnFind(retRange) === false)
+        break;
+      startPt = retRange.cloneRange();
+      startPt.collapse(false);
+    }
+  },
+
+  /**
+   * Helper method for `_countMatchesInWindow` that recursively collects all
+   * visible (i)frames inside a window.
+   *
+   * @param aWindow
+   *        the window to extract the (i)frames from.
+   * @param aStats
+   *        Object that contains a Set called '_framesToCount'
+   */
+  _collectFrames: function(aWindow, aStats) {
+    if (!aWindow.frames || !aWindow.frames.length)
+      return;
+    // Casting `aWindow.frames` to an Iterator doesn't work, so we're stuck with
+    // a plain, old for-loop.
+    for (let i = 0, l = aWindow.frames.length; i < l; ++i) {
+      let frame = aWindow.frames[i];
+      // Don't count matches in hidden frames.
+      let frameEl = frame && frame.frameElement;
+      if (!frameEl)
+        continue;
+      // Construct a range around the frame element to check its visiblity.
+      let range = aWindow.document.createRange();
+      range.setStart(frameEl, 0);
+      range.setEnd(frameEl, 0);
+      if (!this._fastFind.isRangeVisible(range, this._getDocShell(range), true))
+        continue;
+      // All good, so add it to the set to count later.
+      if (!aStats._framesToCount.has(frame))
+        aStats._framesToCount.add(frame);
+      this._collectFrames(frame, aStats);
+    }
+  },
+
+  /**
+   * Helper method to extract the docShell reference from a Window or Range object.
+   *
+   * @param aWindowOrRange
+   *        Window object to query. May also be a Range, from which the owner
+   *        window will be queried.
+   * @returns nsIDocShell
+   */
+  _getDocShell: function(aWindowOrRange) {
+    let window = aWindowOrRange;
+    // Ranges may also be passed in, so fetch its window.
+    if (aWindowOrRange instanceof Ci.nsIDOMRange)
+      window = aWindowOrRange.startContainer.ownerDocument.defaultView;
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+                 .getInterface(Ci.nsIWebNavigation)
+                 .QueryInterface(Ci.nsIDocShell);
+  },
+
   _getWindow: function () {
     return this._docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
   },
 
   /**
    * Get the bounding selection rect in CSS px relative to the origin of the
    * top-level content document.
    */
@@ -349,44 +527,21 @@ Finder.prototype = {
     let controller = this._getSelectionController(win);
     let doc = win.document;
     if (!controller || !doc || !doc.documentElement) {
       // Without the selection controller,
       // we are unable to (un)highlight any matches
       return found;
     }
 
-    let body = (doc instanceof Ci.nsIDOMHTMLDocument && doc.body) ?
-               doc.body : doc.documentElement;
-
     if (aHighlight) {
-      let searchRange = doc.createRange();
-      searchRange.selectNodeContents(body);
-
-      let startPt = searchRange.cloneRange();
-      startPt.collapse(true);
-
-      let endPt = searchRange.cloneRange();
-      endPt.collapse(false);
-
-      let retRange = null;
-      let finder = Cc["@mozilla.org/embedcomp/rangefind;1"]
-                     .createInstance()
-                     .QueryInterface(Ci.nsIFind);
-
-      finder.caseSensitive = this._fastFind.caseSensitive;
-
-      while ((retRange = finder.Find(aWord, searchRange,
-                                     startPt, endPt))) {
-        this._highlightRange(retRange, controller);
-        startPt = retRange.cloneRange();
-        startPt.collapse(false);
-
+      this._findIterator(aWord, win, aRange => {
+        this._highlightRange(aRange, controller);
         found = true;
-      }
+      });
     } else {
       // First, attempt to remove highlighting from main document
       let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
       sel.removeAllRanges();
 
       // Next, check our editor cache, for editors belonging to this
       // document
       if (this._editors) {
@@ -573,16 +728,51 @@ Finder.prototype = {
     }
 
     if (foundContainingRange)
       return range;
 
     return null;
   },
 
+  /**
+   * Determines whether a range is inside a link.
+   * @param aRange
+   *        the range to check
+   * @returns true if the range starts in a link
+   */
+  _rangeStartsInLink: function(aRange) {
+    let isInsideLink = false;
+    let node = aRange.startContainer;
+
+    if (node.nodeType == node.ELEMENT_NODE) {
+      if (node.hasChildNodes) {
+        let childNode = node.item(aRange.startOffset);
+        if (childNode)
+          node = childNode;
+      }
+    }
+
+    const XLink_NS = "http://www.w3.org/1999/xlink";
+    do {
+      if (node instanceof HTMLAnchorElement) {
+        isInsideLink = node.hasAttribute("href");
+        break;
+      } else if (typeof node.hasAttributeNS == "function" &&
+                 node.hasAttributeNS(XLink_NS, "href")) {
+        isInsideLink = (node.getAttributeNS(XLink_NS, "type") == "simple");
+        break;
+      }
+
+      node = node.parentNode;
+    } while (node);
+
+    return isInsideLink;
+  },
+
   // Start of nsIWebProgressListener implementation.
 
   onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
     if (!aWebProgress.isTopLevel)
       return;
 
     // Avoid leaking if we change the page.
     this._previousLink = null;
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -32,16 +32,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
                                   "resource:///modules/devtools/ToolboxProcess.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPI",
+                                  "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "ChromeRegistry",
                                    "@mozilla.org/chrome/chrome-registry;1",
                                    "nsIChromeRegistry");
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "ResProtocolHandler",
                                    "@mozilla.org/network/protocol;1?name=resource",
@@ -4121,16 +4123,19 @@ this.XPIProvider = {
         this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
 
       // Add other stuff that extensions want.
       const features = [ "Worker", "ChromeWorker" ];
 
       for (let feature of features)
         this.bootstrapScopes[aId][feature] = gGlobalScope[feature];
 
+      // Define a console for the add-on
+      this.bootstrapScopes[aId]["console"] = new ConsoleAPI({ consoleID: "addon/" + aId });
+
       // As we don't want our caller to control the JS version used for the
       // bootstrap file, we run loadSubScript within the context of the
       // sandbox with the latest JS version set explicitly.
       this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
       Components.utils.evalInSandbox(
         "Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
                    .createInstance(Components.interfaces.mozIJSSubScriptLoader) \
                    .loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/bootstrap_globals/bootstrap.js
@@ -0,0 +1,29 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let seenGlobals = new Set();
+let scope = this;
+function checkGlobal(name, type) {
+  if (scope[name] && typeof(scope[name]) == type)
+    seenGlobals.add(name);
+}
+
+let wrapped = {};
+Services.obs.notifyObservers({ wrappedJSObject: wrapped }, "bootstrap-request-globals", null);
+for (let [name, type] of wrapped.expectedGlobals) {
+  checkGlobal(name, type);
+}
+
+function install(data, reason) {
+}
+
+function startup(data, reason) {
+  Services.obs.notifyObservers({
+    wrappedJSObject: seenGlobals
+  }, "bootstrap-seen-globals", null);
+}
+
+function shutdown(data, reason) {
+}
+
+function uninstall(data, reason) {
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/addons/bootstrap_globals/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>bootstrap_globals@tests.mozilla.org</em:id>
+    <em:version>1.0</em:version>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>Test Bootstrap Globals</em:name>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>1</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bootstrap_globals.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This verifies that bootstrap.js has the expected globals defined
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+const EXPECTED_GLOBALS = [
+  ["Worker", "function"],
+  ["ChromeWorker", "function"],
+  ["console", "object"]
+];
+
+function run_test() {
+  do_test_pending();
+  startupManager();
+  let sawGlobals = false;
+
+  Services.obs.addObserver(function(subject) {
+    subject.wrappedJSObject.expectedGlobals = EXPECTED_GLOBALS;
+  }, "bootstrap-request-globals", false);
+
+  Services.obs.addObserver(function({ wrappedJSObject: seenGlobals }) {
+    for (let [name,] of EXPECTED_GLOBALS)
+      do_check_true(seenGlobals.has(name));
+
+    sawGlobals = true;
+  }, "bootstrap-seen-globals", false);
+
+  installAllFiles([do_get_addon("bootstrap_globals")], function() {
+    do_check_true(sawGlobals);
+    shutdownManager();
+    do_test_finished();
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -260,8 +260,9 @@ skip-if = os == "android"
 run-sequentially = Uses global XCurProcD dir.
 [test_upgrade_strictcompat.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 run-sequentially = Uses global XCurProcD dir.
 [test_overrideblocklist.js]
 run-sequentially = Uses global XCurProcD dir.
 [test_sourceURI.js]
+[test_bootstrap_globals.js]
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -75,17 +75,17 @@
 !endif
 
 !include x64.nsh
 
 ; NSIS provided macros that we have overridden.
 !include overrides.nsh
 
 !define SHORTCUTS_LOG "shortcuts_log.ini"
-!define TO_BE_DELETED "to_be_deleted"
+!define TO_BE_DELETED "tobedeleted"
 
 ; !define SHCNF_DWORD     0x0003
 ; !define SHCNF_FLUSH     0x1000
 !ifndef SHCNF_DWORDFLUSH
   !define SHCNF_DWORDFLUSH 0x1003
 !endif
 !ifndef SHCNE_ASSOCCHANGED
   !define SHCNE_ASSOCCHANGED 0x08000000
@@ -4334,20 +4334,22 @@
 /**
  * Parses the uninstall.log for the stub installer on install to first remove a
  * previous installation's files prior to installing.
  *
  * When modifying this macro be aware that LineFind uses all registers except
  * $R0-$R3 so be cautious. Callers of this macro are not affected.
  *
  * @param   _PROGRESSBAR
- *          The progress bar to update using PBM_STEPIT.
+ *          The progress bar to update using PBM_STEPIT. Can also be "false" if
+ *          updating a progressbar isn't needed.
  * @param   _INSTALL_STEP_COUNTER
  *          The install step counter to increment. The variable specified in
- *          this parameter is also updated.
+ *          this parameter is also updated. Can also be "false" if a counter
+ *          isn't needed.
  *
  * $R2 = _INSTALL_STEP_COUNTER
  * $R3 = _PROGRESSBAR
  */
 !macro OnStubInstallUninstall
 
   !ifndef OnStubInstallUninstall
     !insertmacro GetParent
@@ -4415,19 +4417,23 @@
 
       StrCmp "$R0" "\" +1 end  ; If this isn't a relative path goto end
       StrCmp "$R9" "\MapiProxy_InUse.dll" end +1 ; Skip the MapiProxy_InUse.dll
       StrCmp "$R9" "\mozMapi32_InUse.dll" end +1 ; Skip the mozMapi32_InUse.dll
 
       StrCpy $R1 "$INSTDIR$R9" ; Copy the install dir path and suffix it with the string
       IfFileExists "$R1" +1 end
 
-      IntOp $R2 $R2 + 2
-      SendMessage $R3 ${PBM_STEPIT} 0 0
-      SendMessage $R3 ${PBM_STEPIT} 0 0
+      ${Unless} "$R2" == "false"
+        IntOp $R2 $R2 + 2
+      ${EndIf}
+      ${Unless} "$R3" == "false"
+        SendMessage $R3 ${PBM_STEPIT} 0 0
+        SendMessage $R3 ${PBM_STEPIT} 0 0
+      ${EndIf}
 
       ClearErrors
       Delete "$R1"
       ${Unless} ${Errors}
         Goto end
       ${EndUnless}
 
       GetTempFileName $R0 "$INSTDIR\${TO_BE_DELETED}"
@@ -5245,18 +5251,18 @@
       ; Prevents breaking apps that don't use SetBrandNameVars
       !ifdef SetBrandNameVars
         ${SetBrandNameVars} "$INSTDIR\distribution\setup.ini"
       !endif
 
       ; Application update uses a directory named tobedeleted in the $INSTDIR to
       ; delete files on OS reboot when they are in use. Try to delete this
       ; directory if it exists.
-      ${If} ${FileExists} "$INSTDIR\tobedeleted"
-        RmDir /r "$INSTDIR\tobedeleted"
+      ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
+        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
       ${EndIf}
 
       ; Prevent all operations (e.g. set as default, postupdate, etc.) when a
       ; reboot is required and the executable launched is helper.exe
       IfFileExists "$INSTDIR\${FileMainEXE}.moz-upgrade" +1 +4
       MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UPGRADE)" IDNO +2
       Reboot
       Quit ; Nothing initialized so no need to call OnEndCommon
@@ -5683,18 +5689,18 @@
       ; Since we write to the uninstall.log in this directory during the
       ; installation create the directory if it doesn't already exist.
       IfFileExists "$INSTDIR\uninstall" +2 +1
       CreateDirectory "$INSTDIR\uninstall"
 
       ; Application update uses a directory named tobedeleted in the $INSTDIR to
       ; delete files on OS reboot when they are in use. Try to delete this
       ; directory if it exists.
-      ${If} ${FileExists} "$INSTDIR\tobedeleted"
-        RmDir /r "$INSTDIR\tobedeleted"
+      ${If} ${FileExists} "$INSTDIR\${TO_BE_DELETED}"
+        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
       ${EndIf}
 
       ; Remove files that may be left behind by the application in the
       ; VirtualStore directory.
       ${CleanVirtualStore}
     FunctionEnd
 
     !verbose pop
--- a/toolkit/themes/linux/global/findBar.css
+++ b/toolkit/themes/linux/global/findBar.css
@@ -145,17 +145,18 @@ findbar[hidden] {
   -moz-border-end-width: 1px;
 }
 
 .findbar-highlight,
 .findbar-case-sensitive {
   -moz-margin-start: 5px;
 }
 
-.findbar-find-status {
+.findbar-find-status,
+.findbar-matches {
   color: GrayText;
   margin: 0 !important;
   -moz-margin-start: 12px !important;
 }
 
 .find-status-icon[status="pending"] {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
--- a/toolkit/themes/linux/mozapps/downloads/downloads.css
+++ b/toolkit/themes/linux/mozapps/downloads/downloads.css
@@ -15,20 +15,16 @@
 }
 
 /* Download View Items */
 richlistitem[type="download"] {
   padding: 4px 8px 4px 4px;
   min-height: 46px;
 }
 
-richlistitem[type="download"][selected="true"] {
-  background-image: url(chrome://mozapps/skin/extensions/itemEnabledFader.png);
-}
-
 richlistitem[type="download"]:not([selected="true"]):nth-child(odd) {
   background-color: -moz-oddtreerow;
 }
 
 richlistitem[type="download"] .name {
   font-size: larger;
 }
 
--- a/toolkit/themes/osx/global/findBar.css
+++ b/toolkit/themes/osx/global/findBar.css
@@ -205,17 +205,18 @@ label.findbar-find-fast:-moz-lwtheme,
   display: none;
 }
 
 .find-status-icon[status="pending"] {
   display: block;
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
 
-.findbar-find-status {
+.findbar-find-status,
+.found-matches {
   color: rgba(0,0,0,.5);
   margin: 0 !important;
   -moz-margin-start: 12px !important;
   text-shadow: 0 1px rgba(255,255,255,.4);
 }
 
 /* Highlight and Case Sensitive toggles */
 
--- a/toolkit/themes/windows/global/findBar.css
+++ b/toolkit/themes/windows/global/findBar.css
@@ -138,17 +138,18 @@ findbar[hidden] {
   -moz-margin-start: 5px;
 }
 
 .findbar-highlight > .toolbarbutton-icon,
 .findbar-case-sensitive > .toolbarbutton-icon {
   display: none;
 }
 
-.findbar-find-status {
+.findbar-find-status,
+.found-matches {
   color: GrayText;
   margin: 0 !important;
   -moz-margin-start: 12px !important;
 }
 
 .find-status-icon[status="pending"] {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
--- a/toolkit/themes/windows/mozapps/downloads/downloads.css
+++ b/toolkit/themes/windows/mozapps/downloads/downloads.css
@@ -18,20 +18,16 @@
 
 /* Download View Items */
 richlistitem[type="download"] {
   padding: 4px 8px 4px 4px;
   min-height: 46px;
   border-bottom: 1px solid ThreeDLightShadow;
 }
 
-richlistitem[type="download"][selected="true"] {
-  background-image: url(chrome://mozapps/skin/extensions/itemEnabledFader.png);
-}
-
 richlistitem[type="download"] .name {
   font-size: larger;
 }
 
 richlistitem[type="download"] .dateTime {
   font-size: smaller;
 }