Merge fx-team to m-c
authorWes Kocher <wkocher@mozilla.com>
Fri, 27 Sep 2013 20:32:24 -0700
changeset 149099 2f4397db1830280edcd4e2f804a704756a9df67d
parent 149076 b3bb2171ff2b872427acc189bd8a2cf557cd0bfd (current diff)
parent 149098 021068e308c1929c0b24fffa76759e717452010d (diff)
child 149100 eb3435771bef86009783434a18970e409dbe8f5e
child 149102 6cb9c4e2777c2ad3c680b7979b1b1a6ddd402966
child 149113 d6b8ac4e8da73bb929f50adb3985ed455e3becf3
child 149128 caec8c0c4963b4d7f71633393feb9c3fb0cef126
child 155807 cc1da159c8110b669a6034abbe1e4ee9b830fcc4
push id25368
push userkwierso@gmail.com
push dateSat, 28 Sep 2013 03:32:42 +0000
treeherdermozilla-central@2f4397db1830 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
2f4397db1830 / 27.0a1 / 20130928030206 / files
nightly linux64
2f4397db1830 / 27.0a1 / 20130928030206 / files
nightly mac
2f4397db1830 / 27.0a1 / 20130928030206 / files
nightly win32
2f4397db1830 / 27.0a1 / 20130928030206 / files
nightly win64
2f4397db1830 / 27.0a1 / 20130928030206 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c
b2g/config/emulator/config.json
b2g/config/emulator/releng-emulator.tt
browser/devtools/shared/test/browser_telemetry_buttonsandsidebar.js
layout/base/nsDisplayList.cpp
mobile/android/base/Makefile.in
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/LayerView.java
mobile/android/base/gfx/NativePanZoomController.java
--- a/browser/devtools/shared/test/Makefile.in
+++ b/browser/devtools/shared/test/Makefile.in
@@ -4,17 +4,21 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOCHITEST_BROWSER_FILES = \
 		browser_css_color.js \
 		browser_eventemitter_basic.js \
 		browser_observableobject.js \
 		browser_layoutHelpers.js \
 		browser_require_basic.js \
-		browser_telemetry_buttonsandsidebar.js \
+		browser_telemetry_sidebar.js \
+		browser_telemetry_button_responsive.js \
+		browser_telemetry_button_scratchpad.js \
+		browser_telemetry_button_tilt.js \
+		browser_telemetry_button_paintflashing.js \
 		browser_telemetry_toolboxtabs_inspector.js \
 		browser_telemetry_toolboxtabs_jsdebugger.js \
 		browser_telemetry_toolboxtabs_jsprofiler.js \
 		browser_telemetry_toolboxtabs_netmonitor.js \
 		browser_telemetry_toolboxtabs_options.js \
 		browser_telemetry_toolboxtabs_styleeditor.js \
 		browser_telemetry_toolboxtabs_webconsole.js \
 		browser_templater_basic.js \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_button_paintflashing.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_paintflashing.js</p>";
+
+// Because we need to gather stats for the period of time that a tool has been
+// opened we make use of setTimeout() to create tool active times.
+const TOOL_DELAY = 200;
+
+let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let Telemetry = require("devtools/shared/telemetry");
+
+function init() {
+  Telemetry.prototype.telemetryInfo = {};
+  Telemetry.prototype._oldlog = Telemetry.prototype.log;
+  Telemetry.prototype.log = function(histogramId, value) {
+    if (histogramId) {
+      if (!this.telemetryInfo[histogramId]) {
+        this.telemetryInfo[histogramId] = [];
+      }
+
+      this.telemetryInfo[histogramId].push(value);
+    }
+  };
+
+  testButton("command-button-paintflashing");
+}
+
+function testButton(id) {
+  info("Testing " + id);
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    info("inspector opened");
+
+    let button = toolbox.doc.querySelector("#" + id);
+    ok(button, "Captain, we have the button");
+
+    delayedClicks(button, 4).then(function() {
+      checkResults("_PAINTFLASHING_");
+    });
+  }).then(null, console.error);
+}
+
+function delayedClicks(node, clicks) {
+  let deferred = promise.defer();
+  let clicked = 0;
+
+  // See TOOL_DELAY for why we need setTimeout here
+  setTimeout(function delayedClick() {
+    info("Clicking button " + node.id);
+    node.click();
+    clicked++;
+
+    if (clicked >= clicks) {
+      deferred.resolve(node);
+    } else {
+      setTimeout(delayedClick, TOOL_DELAY);
+    }
+  }, TOOL_DELAY);
+
+  return deferred.promise;
+}
+
+function checkResults(histIdFocus) {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
+        !histId.contains(histIdFocus)) {
+      // Inspector stats are tested in
+      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
+      // because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_BOOLEAN")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element === true;
+      });
+
+      ok(okay, "All " + histId + " entries are === true");
+    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element > 0;
+      });
+
+      ok(okay, "All " + histId + " entries have time > 0");
+    }
+  }
+
+  finishUp();
+}
+
+function finishUp() {
+  gBrowser.removeCurrentTab();
+
+  Telemetry.prototype.log = Telemetry.prototype._oldlog;
+  delete Telemetry.prototype._oldlog;
+  delete Telemetry.prototype.telemetryInfo;
+
+  TargetFactory = Services = promise = require = null;
+
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    waitForFocus(init, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_button_responsive.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_responsive.js</p>";
+
+// Because we need to gather stats for the period of time that a tool has been
+// opened we make use of setTimeout() to create tool active times.
+const TOOL_DELAY = 200;
+
+let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let Telemetry = require("devtools/shared/telemetry");
+
+function init() {
+  Telemetry.prototype.telemetryInfo = {};
+  Telemetry.prototype._oldlog = Telemetry.prototype.log;
+  Telemetry.prototype.log = function(histogramId, value) {
+    if (histogramId) {
+      if (!this.telemetryInfo[histogramId]) {
+        this.telemetryInfo[histogramId] = [];
+      }
+
+      this.telemetryInfo[histogramId].push(value);
+    }
+  };
+
+  testButton("command-button-responsive");
+}
+
+function testButton(id) {
+  info("Testing " + id);
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    info("inspector opened");
+
+    let button = toolbox.doc.querySelector("#" + id);
+    ok(button, "Captain, we have the button");
+
+    delayedClicks(button, 4).then(function() {
+      checkResults("_RESPONSIVE_");
+    });
+  }).then(null, console.error);
+}
+
+function delayedClicks(node, clicks) {
+  let deferred = promise.defer();
+  let clicked = 0;
+
+  // See TOOL_DELAY for why we need setTimeout here
+  setTimeout(function delayedClick() {
+    info("Clicking button " + node.id);
+    node.click();
+    clicked++;
+
+    if (clicked >= clicks) {
+      deferred.resolve(node);
+    } else {
+      setTimeout(delayedClick, TOOL_DELAY);
+    }
+  }, TOOL_DELAY);
+
+  return deferred.promise;
+}
+
+function checkResults(histIdFocus) {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
+        !histId.contains(histIdFocus)) {
+      // Inspector stats are tested in
+      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
+      // because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_BOOLEAN")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element === true;
+      });
+
+      ok(okay, "All " + histId + " entries are === true");
+    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element > 0;
+      });
+
+      ok(okay, "All " + histId + " entries have time > 0");
+    }
+  }
+
+  finishUp();
+}
+
+function finishUp() {
+  gBrowser.removeCurrentTab();
+
+  Telemetry.prototype.log = Telemetry.prototype._oldlog;
+  delete Telemetry.prototype._oldlog;
+  delete Telemetry.prototype.telemetryInfo;
+
+  TargetFactory = Services = promise = require = null;
+
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    waitForFocus(init, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_button_scratchpad.js
@@ -0,0 +1,155 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_scratchpad.js</p>";
+
+// Because we need to gather stats for the period of time that a tool has been
+// opened we make use of setTimeout() to create tool active times.
+const TOOL_DELAY = 200;
+
+let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let Telemetry = require("devtools/shared/telemetry");
+
+let numScratchpads = 0;
+
+function init() {
+  Telemetry.prototype.telemetryInfo = {};
+  Telemetry.prototype._oldlog = Telemetry.prototype.log;
+  Telemetry.prototype.log = function(histogramId, value) {
+    if (histogramId) {
+      if (!this.telemetryInfo[histogramId]) {
+        this.telemetryInfo[histogramId] = [];
+      }
+
+      this.telemetryInfo[histogramId].push(value);
+    }
+  };
+
+  Services.ww.registerNotification(windowObserver);
+  testButton("command-button-scratchpad");
+}
+
+function testButton(id) {
+  info("Testing " + id);
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    info("inspector opened");
+
+    let button = toolbox.doc.querySelector("#" + id);
+    ok(button, "Captain, we have the button");
+
+    delayedClicks(button, 4).then(null, console.error);
+  }).then(null, console.error);
+}
+
+function windowObserver(aSubject, aTopic, aData) {
+  if (aTopic == "domwindowopened") {
+    let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad, false);
+
+      if (win.Scratchpad) {
+        win.Scratchpad.addObserver({
+          onReady: function() {
+            win.Scratchpad.removeObserver(this);
+            numScratchpads++;
+            win.close();
+
+            info("another scratchpad was opened and closed, count is now " + numScratchpads);
+
+            if (numScratchpads === 4) {
+              Services.ww.unregisterNotification(windowObserver);
+              info("4 scratchpads have been opened and closed, checking results");
+              checkResults("_SCRATCHPAD_");
+            }
+          },
+        });
+      }
+    }, false);
+  }
+}
+
+function delayedClicks(node, clicks) {
+  let deferred = promise.defer();
+  let clicked = 0;
+
+  // See TOOL_DELAY for why we need setTimeout here
+  setTimeout(function delayedClick() {
+    info("Clicking button " + node.id);
+    node.click();
+    clicked++;
+
+    if (clicked >= clicks) {
+      deferred.resolve(node);
+    } else {
+      setTimeout(delayedClick, TOOL_DELAY);
+    }
+  }, TOOL_DELAY);
+
+  return deferred.promise;
+}
+
+function checkResults(histIdFocus) {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
+        !histId.contains(histIdFocus)) {
+      // Inspector stats are tested in
+      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
+      // because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_BOOLEAN")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element === true;
+      });
+
+      ok(okay, "All " + histId + " entries are === true");
+    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element > 0;
+      });
+
+      ok(okay, "All " + histId + " entries have time > 0");
+    }
+  }
+
+  finishUp();
+}
+
+function finishUp() {
+  gBrowser.removeCurrentTab();
+
+  Telemetry.prototype.log = Telemetry.prototype._oldlog;
+  delete Telemetry.prototype._oldlog;
+  delete Telemetry.prototype.telemetryInfo;
+
+  TargetFactory = Services = promise = require = numScratchpads = null;
+
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    waitForFocus(init, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_button_tilt.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_button_tilt.js</p>";
+
+// Because we need to gather stats for the period of time that a tool has been
+// opened we make use of setTimeout() to create tool active times.
+const TOOL_DELAY = 200;
+
+let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let Telemetry = require("devtools/shared/telemetry");
+
+function init() {
+  Telemetry.prototype.telemetryInfo = {};
+  Telemetry.prototype._oldlog = Telemetry.prototype.log;
+  Telemetry.prototype.log = function(histogramId, value) {
+    if (histogramId) {
+      if (!this.telemetryInfo[histogramId]) {
+        this.telemetryInfo[histogramId] = [];
+      }
+
+      this.telemetryInfo[histogramId].push(value);
+    }
+  };
+
+  testButton("command-button-tilt");
+}
+
+function testButton(id) {
+  info("Testing " + id);
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    info("inspector opened");
+
+    let button = toolbox.doc.querySelector("#" + id);
+    ok(button, "Captain, we have the button");
+
+    delayedClicks(button, 4).then(function() {
+      checkResults("_TILT_");
+    });
+  }).then(null, console.error);
+}
+
+function delayedClicks(node, clicks) {
+  let deferred = promise.defer();
+  let clicked = 0;
+
+  // See TOOL_DELAY for why we need setTimeout here
+  setTimeout(function delayedClick() {
+    info("Clicking button " + node.id);
+    node.click();
+    clicked++;
+
+    if (clicked >= clicks) {
+      deferred.resolve(node);
+    } else {
+      setTimeout(delayedClick, TOOL_DELAY);
+    }
+  }, TOOL_DELAY);
+
+  return deferred.promise;
+}
+
+function checkResults(histIdFocus) {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
+        !histId.contains(histIdFocus)) {
+      // Inspector stats are tested in
+      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
+      // because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_BOOLEAN")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element === true;
+      });
+
+      ok(okay, "All " + histId + " entries are === true");
+    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element > 0;
+      });
+
+      ok(okay, "All " + histId + " entries have time > 0");
+    }
+  }
+
+  finishUp();
+}
+
+function finishUp() {
+  gBrowser.removeCurrentTab();
+
+  Telemetry.prototype.log = Telemetry.prototype._oldlog;
+  delete Telemetry.prototype._oldlog;
+  delete Telemetry.prototype.telemetryInfo;
+
+  TargetFactory = Services = promise = require = null;
+
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    waitForFocus(init, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
deleted file mode 100644
--- a/browser/devtools/shared/test/browser_telemetry_buttonsandsidebar.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_buttonsandsidebar.js</p>";
-
-// Because we need to gather stats for the period of time that a tool has been
-// opened we make use of setTimeout() to create tool active times.
-const TOOL_DELAY = 200;
-
-let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
-let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
-
-let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
-let Telemetry = require("devtools/shared/telemetry");
-
-function init() {
-  Telemetry.prototype.telemetryInfo = {};
-  Telemetry.prototype._oldlog = Telemetry.prototype.log;
-  Telemetry.prototype.log = function(histogramId, value) {
-    if (histogramId) {
-      if (!this.telemetryInfo[histogramId]) {
-        this.telemetryInfo[histogramId] = [];
-      }
-
-      this.telemetryInfo[histogramId].push(value);
-    }
-  }
-
-  testButtons();
-}
-
-function testButtons() {
-  info("Testing buttons");
-
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-    let container = toolbox.doc.getElementById("toolbox-buttons");
-    let buttons = container.getElementsByTagName("toolbarbutton");
-
-    // Copy HTMLCollection to array.
-    buttons = Array.prototype.slice.call(buttons);
-
-    (function testButton() {
-      let button = buttons.pop();
-
-      if (button) {
-        info("Clicking button " + button.id);
-        button.click();
-        delayedClicks(button, 3).then(function(button) {
-          if (buttons.length == 0) {
-            // Remove scratchpads
-            let wins = Services.wm.getEnumerator("devtools:scratchpad");
-            while (wins.hasMoreElements()) {
-              let win = wins.getNext();
-              info("Closing scratchpad window");
-              win.close();
-            }
-
-            testSidebar();
-          } else {
-            setTimeout(testButton, TOOL_DELAY);
-          }
-        });
-      }
-    })();
-  }).then(null, reportError);
-}
-
-function delayedClicks(node, clicks) {
-  let deferred = promise.defer();
-  let clicked = 0;
-
-  setTimeout(function delayedClick() {
-    info("Clicking button " + node.id);
-    node.click();
-    clicked++;
-
-    if (clicked >= clicks) {
-      deferred.resolve(node);
-    } else {
-      setTimeout(delayedClick, TOOL_DELAY);
-    }
-  }, TOOL_DELAY);
-
-  return deferred.promise;
-}
-
-function testSidebar() {
-  info("Testing sidebar");
-
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-
-  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-    let inspector = toolbox.getCurrentPanel();
-    let sidebarTools = ["ruleview", "computedview", "fontinspector", "layoutview"];
-
-    // Concatenate the array with itself so that we can open each tool twice.
-    sidebarTools.push.apply(sidebarTools, sidebarTools);
-
-    setTimeout(function selectSidebarTab() {
-      let tool = sidebarTools.pop();
-      if (tool) {
-        inspector.sidebar.select(tool);
-        setTimeout(function() {
-          setTimeout(selectSidebarTab, TOOL_DELAY);
-        }, TOOL_DELAY);
-      } else {
-        checkResults();
-      }
-    }, TOOL_DELAY);
-  });
-}
-
-function checkResults() {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Iterator(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_")) {
-      // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we
-      // skip them here because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
-      ok(value.length === 1 && value[0] === true,
-         "Per user value " + histId + " has a single value of true");
-    } else if (histId.endsWith("OPENED_BOOLEAN")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
-
-  finishUp();
-}
-
-function reportError(error) {
-  let stack = "    " + error.stack.replace(/\n?.*?@/g, "\n    JS frame :: ");
-
-  ok(false, "ERROR: " + error + " at " + error.fileName + ":" +
-            error.lineNumber + "\n\nStack trace:" + stack);
-  finishUp();
-}
-
-function finishUp() {
-  gBrowser.removeCurrentTab();
-
-  Telemetry.prototype.log = Telemetry.prototype._oldlog;
-  delete Telemetry.prototype._oldlog;
-  delete Telemetry.prototype.telemetryInfo;
-
-  TargetFactory = Services = promise = require = null;
-
-  finish();
-}
-
-function test() {
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    waitForFocus(init, content);
-  }, true);
-
-  content.location = TEST_URI;
-}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_telemetry_sidebar.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_sidebar.js</p>";
+
+// Because we need to gather stats for the period of time that a tool has been
+// opened we make use of setTimeout() to create tool active times.
+const TOOL_DELAY = 200;
+
+let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+let Telemetry = require("devtools/shared/telemetry");
+
+function init() {
+  Telemetry.prototype.telemetryInfo = {};
+  Telemetry.prototype._oldlog = Telemetry.prototype.log;
+  Telemetry.prototype.log = function(histogramId, value) {
+    if (histogramId) {
+      if (!this.telemetryInfo[histogramId]) {
+        this.telemetryInfo[histogramId] = [];
+      }
+
+      this.telemetryInfo[histogramId].push(value);
+    }
+  };
+
+  testSidebar();
+}
+
+function testSidebar() {
+  info("Testing sidebar");
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+
+  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+    let inspector = toolbox.getCurrentPanel();
+    let sidebarTools = ["ruleview", "computedview", "fontinspector", "layoutview"];
+
+    // Concatenate the array with itself so that we can open each tool twice.
+    sidebarTools.push.apply(sidebarTools, sidebarTools);
+
+    // See TOOL_DELAY for why we need setTimeout here
+    setTimeout(function selectSidebarTab() {
+      let tool = sidebarTools.pop();
+      if (tool) {
+        inspector.sidebar.select(tool);
+        setTimeout(function() {
+          setTimeout(selectSidebarTab, TOOL_DELAY);
+        }, TOOL_DELAY);
+      } else {
+        checkResults();
+      }
+    }, TOOL_DELAY);
+  });
+}
+
+function checkResults() {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_")) {
+      // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we
+      // skip them here because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_BOOLEAN")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element === true;
+      });
+
+      ok(okay, "All " + histId + " entries are === true");
+    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
+      ok(value.length > 1, histId + " has more than one entry");
+
+      let okay = value.every(function(element) {
+        return element > 0;
+      });
+
+      ok(okay, "All " + histId + " entries have time > 0");
+    }
+  }
+
+  finishUp();
+}
+
+function finishUp() {
+  gBrowser.removeCurrentTab();
+
+  Telemetry.prototype.log = Telemetry.prototype._oldlog;
+  delete Telemetry.prototype._oldlog;
+  delete Telemetry.prototype.telemetryInfo;
+
+  TargetFactory = Services = promise = require = null;
+
+  finish();
+}
+
+function test() {
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function() {
+    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+    waitForFocus(init, content);
+  }, true);
+
+  content.location = TEST_URI;
+}
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -1,14 +1,15 @@
 /* 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/. */
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
+let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(aURL, aCallback)
 {
   waitForExplicitFinish();
 
--- a/docshell/base/nsDefaultURIFixup.cpp
+++ b/docshell/base/nsDefaultURIFixup.cpp
@@ -398,17 +398,19 @@ NS_IMETHODIMP nsDefaultURIFixup::Keyword
                 // can increment counts from the search engine. The assumption
                 // here is that this keyword/submission will eventually result
                 // in a search. Since we only generate a URI here, there is the
                 // possibility we'll increment the counter without actually
                 // incurring a search. A robust solution would involve currying
                 // the search engine's name through various function calls.
                 nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
                 if (obsSvc) {
-                    obsSvc->NotifyObservers(defaultEngine, "keyword-search", NS_ConvertUTF8toUTF16(keyword).get());
+                  // Note that "keyword-search" refers to a search via the url
+                  // bar, not a bookmarks keyword search.
+                  obsSvc->NotifyObservers(defaultEngine, "keyword-search", NS_ConvertUTF8toUTF16(keyword).get());
                 }
 
                 return submission->GetUri(aURI);
             }
         }
     }
 #endif
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -724,18 +724,18 @@ static void RecordFrameMetrics(nsIFrame*
                              * metrics.mCumulativeResolution
                              * layerToScreenScale);
 
   // For the root scroll frame of the root content document, clamp the
   // composition bounds to the widget bounds. This is necessary because, if
   // the page is zoomed in, the frame's size might be larger than the widget
   // bounds, but we don't want the composition bounds to be.
   bool useWidgetBounds = false;
-  bool isRootContentDocRootScrollFrame = aForFrame->GetParent() == nullptr
-                                      && presContext->IsRootContentDocument();
+  bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument()
+                                      && aScrollFrame == presShell->GetRootScrollFrame();
   if (isRootContentDocRootScrollFrame) {
     if (nsIWidget* widget = aForFrame->GetNearestWidget()) {
       nsIntRect bounds;
       widget->GetBounds(bounds);
       ScreenIntRect screenBounds = ScreenIntRect::FromUnknownRect(mozilla::gfx::IntRect(
           bounds.x, bounds.y, bounds.width, bounds.height));
       AdjustForScrollBars(screenBounds, scrollableFrame);
       metrics.mCompositionBounds = screenBounds.ClampRect(metrics.mCompositionBounds);
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1439,17 +1439,18 @@ abstract public class BrowserApp extends
                     keywordSearch = "";
                 } else {
                     keyword = url.substring(0, index);
                     keywordSearch = url.substring(index + 1);
                 }
 
                 final String keywordUrl = BrowserDB.getUrlForKeyword(getContentResolver(), keyword);
 
-                // If there isn't a bookmark keyword, just load the URL.
+                // If there isn't a bookmark keyword, load the url. This may result in a query
+                // using the default search engine.
                 if (TextUtils.isEmpty(keywordUrl)) {
                     Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
                     return;
                 }
 
                 recordSearch(null, "barkeyword");
 
                 // Otherwise, construct a search query from the bookmark keyword.
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -189,16 +189,17 @@ FENNEC_JAVA_FILES = \
   gfx/IntSize.java \
   gfx/JavaPanZoomController.java \
   gfx/Layer.java \
   gfx/LayerMarginsAnimator.java \
   gfx/LayerRenderer.java \
   gfx/LayerView.java \
   gfx/NativePanZoomController.java \
   gfx/NinePatchTileLayer.java \
+  gfx/Overscroll.java \
   gfx/PanningPerfAPI.java \
   gfx/PanZoomController.java \
   gfx/PanZoomTarget.java \
   gfx/PluginLayer.java \
   gfx/PointUtils.java \
   gfx/ProgressiveUpdateData.java \
   gfx/RectUtils.java \
   gfx/RenderTask.java \
--- a/mobile/android/base/gfx/Axis.java
+++ b/mobile/android/base/gfx/Axis.java
@@ -149,16 +149,20 @@ abstract class Axis {
     protected abstract boolean marginsHidden();
 
     Axis(SubdocumentScrollHelper subscroller) {
         mSubscroller = subscroller;
         mOverscrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
         mRecentVelocities = new float[FLING_VELOCITY_POINTS];
     }
 
+    // Implementors can override these to show effects when the axis overscrolls
+    protected void overscrollFling(float velocity) { }
+    protected void overscrollPan(float displacement) { }
+
     public void setOverScrollMode(int overscrollMode) {
         mOverscrollMode = overscrollMode;
     }
 
     public int getOverScrollMode() {
         return mOverscrollMode;
     }
 
@@ -374,22 +378,32 @@ abstract class Axis {
         else
             mDisplacement += mVelocity * getEdgeResistance(false);
 
         // if overscroll is disabled and we're trying to overscroll, reset the displacement
         // to remove any excess. Using getExcess alone isn't enough here since it relies on
         // getOverscroll which doesn't take into account any new displacment being applied.
         // If we using a subscroller, we don't want to alter the scrolling being done
         if (getOverScrollMode() == View.OVER_SCROLL_NEVER && !mSubscroller.scrolling()) {
+            float originalDisplacement = mDisplacement;
+
             if (mDisplacement + getOrigin() < getPageStart() - getMarginStart()) {
                 mDisplacement = getPageStart() - getMarginStart() - getOrigin();
-                stopFling();
             } else if (mDisplacement + getViewportEnd() > getPageEnd() + getMarginEnd()) {
                 mDisplacement = getPageEnd() - getMarginEnd() - getViewportEnd();
-                stopFling();
+            }
+
+            // Return the amount of overscroll so that the overscroll controller can draw it for us
+            if (originalDisplacement != mDisplacement) {
+                if (mFlingState == FlingStates.FLINGING) {
+                    overscrollFling(mVelocity / MS_PER_FRAME * 1000);
+                    stopFling();
+                } else if (mFlingState == FlingStates.PANNING) {
+                    overscrollPan(originalDisplacement - mDisplacement);
+                }
             }
         }
     }
 
     float resetDisplacement() {
         float d = mDisplacement;
         mDisplacement = 0.0f;
         return d;
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -127,16 +127,20 @@ public class GeckoLayerClient implements
 
         mPanZoomController = PanZoomController.Factory.create(this, view, eventDispatcher);
         mMarginsAnimator = new LayerMarginsAnimator(this, view);
         mView = view;
         mView.setListener(this);
         mContentDocumentIsDisplayed = true;
     }
 
+    public void setOverscrollHandler(final Overscroll listener) {
+        mPanZoomController.setOverscrollHandler(listener);
+    }
+
     /** Attaches to root layer so that Gecko appears. */
     public void notifyGeckoReady() {
         mGeckoIsReady = true;
 
         mRootLayer = new VirtualLayer(new IntSize(mView.getWidth(), mView.getHeight()));
         mLayerRenderer = mView.getRenderer();
 
         sendResizeEventIfNecessary(true);
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -124,16 +124,19 @@ class JavaPanZoomController
     private float mAutonavZoomDelta;
     /* The user selected panning mode */
     private AxisLockMode mMode;
     /* A medium-length tap/press is happening */
     private boolean mMediumPress;
     /* Used to change the scrollY direction */
     private boolean mNegateWheelScrollY;
 
+    // Handler to be notified when overscroll occurs
+    private Overscroll mOverscroll;
+
     public JavaPanZoomController(PanZoomTarget target, View view, EventDispatcher eventDispatcher) {
         mTarget = target;
         mSubscroller = new SubdocumentScrollHelper(eventDispatcher);
         mX = new AxisX(mSubscroller);
         mY = new AxisY(mSubscroller);
         mTouchEventHandler = new TouchEventHandler(view.getContext(), view, this);
 
         checkMainThread();
@@ -1108,16 +1111,28 @@ class JavaPanZoomController
         @Override
         protected float getPageLength() { return getMetrics().getPageWidthWithMargins(); }
         @Override
         protected boolean marginsHidden() {
             ImmutableViewportMetrics metrics = getMetrics();
             RectF maxMargins = mTarget.getMaxMargins();
             return (metrics.marginLeft < maxMargins.left || metrics.marginRight < maxMargins.right);
         }
+        @Override
+        protected void overscrollFling(final float velocity) {
+            if (mOverscroll != null) {
+                mOverscroll.setVelocity(velocity, Overscroll.Axis.X);
+            }
+        }
+        @Override
+        protected void overscrollPan(final float distance) {
+            if (mOverscroll != null) {
+                mOverscroll.setDistance(distance, Overscroll.Axis.X);
+            }
+        }
     }
 
     private class AxisY extends Axis {
         AxisY(SubdocumentScrollHelper subscroller) { super(subscroller); }
         @Override
         public float getOrigin() { return getMetrics().viewportRectTop; }
         @Override
         protected float getViewportLength() { return getMetrics().getHeight(); }
@@ -1130,16 +1145,28 @@ class JavaPanZoomController
         @Override
         protected float getMarginEnd() { return mTarget.getMaxMargins().bottom - getMetrics().marginBottom; }
         @Override
         protected boolean marginsHidden() {
             ImmutableViewportMetrics metrics = getMetrics();
             RectF maxMargins = mTarget.getMaxMargins();
             return (metrics.marginTop < maxMargins.top || metrics.marginBottom < maxMargins.bottom);
         }
+        @Override
+        protected void overscrollFling(final float velocity) {
+            if (mOverscroll != null) {
+                mOverscroll.setVelocity(velocity, Overscroll.Axis.Y);
+            }
+        }
+        @Override
+        protected void overscrollPan(final float distance) {
+            if (mOverscroll != null) {
+                mOverscroll.setDistance(distance, Overscroll.Axis.Y);
+            }
+        }
     }
 
     /*
      * Zooming
      */
     @Override
     public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
         if (mState == PanZoomState.ANIMATED_ZOOM)
@@ -1429,9 +1456,14 @@ class JavaPanZoomController
     public int getOverScrollMode() {
         return mX.getOverScrollMode();
     }
 
     @Override
     public void updateScrollOffset(float cssX, float cssY) {
         // Nothing to update, this class doesn't store the scroll offset locally.
     }
+
+    @Override
+    public void setOverscrollHandler(final Overscroll handler) {
+        mOverscroll = handler;
+    }
 }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -13,16 +13,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.TouchEventInterceptor;
 import org.mozilla.gecko.ZoomConstraints;
 import org.mozilla.gecko.mozglue.GeneratableAndroidBridgeTarget;
 import org.mozilla.gecko.util.EventDispatcher;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.os.Build;
 import android.os.Handler;
 import android.util.AttributeSet;
@@ -62,16 +63,17 @@ public class LayerView extends FrameLayo
 
     private SurfaceView mSurfaceView;
     private TextureView mTextureView;
 
     private Listener mListener;
 
     /* This should only be modified on the Java UI thread. */
     private final ArrayList<TouchEventInterceptor> mTouchInterceptors;
+    private final Overscroll mOverscroll;
 
     /* Flags used to determine when to show the painted surface. */
     public static final int PAINT_START = 0;
     public static final int PAINT_BEFORE_FIRST = 1;
     public static final int PAINT_AFTER_FIRST = 2;
 
     public boolean shouldUseTextureView() {
         // Disable TextureView support for now as it causes panning/zooming
@@ -99,20 +101,23 @@ public class LayerView extends FrameLayo
     public LayerView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         mGLController = GLController.getInstance(this);
         mPaintState = PAINT_START;
         mBackgroundColor = Color.WHITE;
 
         mTouchInterceptors = new ArrayList<TouchEventInterceptor>();
+        mOverscroll = new Overscroll(this);
     }
 
     public void initializeView(EventDispatcher eventDispatcher) {
         mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher);
+        mLayerClient.setOverscrollHandler(mOverscroll);
+
         mPanZoomController = mLayerClient.getPanZoomController();
         mMarginsAnimator = mLayerClient.getLayerMarginsAnimator();
 
         mRenderer = new LayerRenderer(this);
         mInputConnectionHandler = null;
 
         setFocusable(true);
         setFocusableInTouchMode(true);
@@ -214,16 +219,26 @@ public class LayerView extends FrameLayo
                 result |= i.onInterceptTouchEvent(this, event);
             }
         }
 
         return result;
     }
 
     @Override
+    public void dispatchDraw(final Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        // We must have a layer client to get valid viewport metrics
+        if (mLayerClient != null) {
+            mOverscroll.draw(canvas, getViewportMetrics());
+        }
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             requestFocus();
         }
 
         if (runTouchInterceptors(event, false)) {
             return true;
         }
@@ -482,24 +497,28 @@ public class LayerView extends FrameLayo
         if (!mGLController.hasValidSurface() || mSurfaceView == null) {
             surfaceChanged(width, height);
             return;
         }
 
         if (mListener != null) {
             mListener.sizeChanged(width, height);
         }
+
+        mOverscroll.setSize(width, height);
     }
 
     private void surfaceChanged(int width, int height) {
         mGLController.surfaceChanged(width, height);
 
         if (mListener != null) {
             mListener.surfaceChanged(width, height);
         }
+
+        mOverscroll.setSize(width, height);
     }
 
     private void onDestroyed() {
         mGLController.surfaceDestroyed();
     }
 
     public Object getNativeWindow() {
         if (mSurfaceView != null)
--- a/mobile/android/base/gfx/NativePanZoomController.java
+++ b/mobile/android/base/gfx/NativePanZoomController.java
@@ -99,9 +99,12 @@ class NativePanZoomController implements
             long nextDelay = runDelayedCallback();
             if (nextDelay >= 0) {
                 mTarget.postDelayed(this, nextDelay);
             }
         }
     }
 
     public native void updateScrollOffset(float cssX, float cssY);
+
+    public void setOverscrollHandler(final Overscroll listener) {
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/gfx/Overscroll.java
@@ -0,0 +1,127 @@
+/* -*- 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.gfx;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.support.v4.widget.EdgeEffectCompat;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+
+public class Overscroll {
+    // Used to index particular edges in the edges array
+    private static final int TOP = 0;
+    private static final int BOTTOM = 1;
+    private static final int LEFT = 2;
+    private static final int RIGHT = 3;
+
+    // All four edges of the screen
+    private final EdgeEffectCompat[] mEdges = new EdgeEffectCompat[4];
+
+    // The view we're showing this overscroll on.
+    private final View mView;
+
+    // The axis to show overscroll on.
+    public enum Axis {
+        X,
+        Y,
+    };
+
+    public Overscroll(final View v) {
+        mView = v;
+        Context context = v.getContext();
+        for (int i = 0; i < 4; i++) {
+            mEdges[i] = new EdgeEffectCompat(context);
+        }
+    }
+
+    public void setSize(final int width, final int height) {
+        mEdges[LEFT].setSize(height, width);
+        mEdges[RIGHT].setSize(height, width);
+        mEdges[TOP].setSize(width, height);
+        mEdges[BOTTOM].setSize(width, height);
+    }
+
+    private EdgeEffectCompat getEdgeForAxisAndSide(final Axis axis, final float side) {
+        if (axis == Axis.Y) {
+            if (side < 0) {
+                return mEdges[TOP];
+            } else {
+                return mEdges[BOTTOM];
+            }
+        } else {
+            if (side < 0) {
+                return mEdges[LEFT];
+            } else {
+                return mEdges[RIGHT];
+            }
+        }
+    }
+
+    public void setVelocity(final float velocity, final Axis axis) {
+        final EdgeEffectCompat edge = getEdgeForAxisAndSide(axis, velocity);
+
+        // If we're showing overscroll already, start fading it out.
+        if (!edge.isFinished()) {
+            edge.onRelease();
+        } else {
+            // Otherwise, show an absorb effect
+            edge.onAbsorb((int)velocity);
+        }
+
+        ViewCompat.postInvalidateOnAnimation(mView);
+    }
+
+    public void setDistance(final float distance, final Axis axis) {
+        // The first overscroll event often has zero distance. Throw it out
+        if (distance == 0.0f) {
+            return;
+        }
+
+        final EdgeEffectCompat edge = getEdgeForAxisAndSide(axis, (int)distance);
+        edge.onPull(distance / (axis == Axis.X ? mView.getWidth() : mView.getHeight()));
+        ViewCompat.postInvalidateOnAnimation(mView);
+    }
+
+    public void draw(final Canvas canvas, final ImmutableViewportMetrics metrics) {
+        if (metrics == null) {
+            return;
+        }
+
+        // If we're pulling an edge, or fading it out, draw!
+        boolean invalidate = false;
+        if (!mEdges[TOP].isFinished()) {
+            invalidate |= draw(mEdges[TOP], canvas, metrics.marginLeft, metrics.marginTop, 0);
+        }
+
+        if (!mEdges[BOTTOM].isFinished()) {
+            invalidate |= draw(mEdges[BOTTOM], canvas, mView.getWidth(), mView.getHeight(), 180);
+        }
+
+        if (!mEdges[LEFT].isFinished()) {
+            invalidate |= draw(mEdges[LEFT], canvas, metrics.marginLeft, mView.getHeight(), 270);
+        }
+
+        if (!mEdges[RIGHT].isFinished()) {
+            invalidate |= draw(mEdges[RIGHT], canvas, mView.getWidth(), metrics.marginTop, 90);
+        }
+
+        // If the edge effect is animating off screen, invalidate.
+        if (invalidate) {
+            ViewCompat.postInvalidateOnAnimation(mView);
+        }
+    }
+
+    public boolean draw(final EdgeEffectCompat edge, final Canvas canvas, final float translateX, final float translateY, final float rotation) {
+        final int state = canvas.save();
+        canvas.translate(translateX, translateY);
+        canvas.rotate(rotation);
+        boolean invalidate = edge.draw(canvas);
+        canvas.restoreToCount(state);
+
+        return invalidate;
+    }
+}
--- a/mobile/android/base/gfx/PanZoomController.java
+++ b/mobile/android/base/gfx/PanZoomController.java
@@ -37,9 +37,11 @@ public interface PanZoomController {
     public void pageRectUpdated();
     public void abortPanning();
     public void abortAnimation();
 
     public void setOverScrollMode(int overscrollMode);
     public int getOverScrollMode();
 
     public void updateScrollOffset(float cssX, float cssY);
+
+    public void setOverscrollHandler(final Overscroll controller);
 }
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -60,19 +60,20 @@ import java.util.concurrent.atomic.Atomi
  */
 public class BrowserHealthRecorder implements GeckoEventListener {
     private static final String LOG_TAG = "GeckoHealthRec";
     private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
     private static final String EVENT_ADDONS_ALL = "Addons:All";
     private static final String EVENT_ADDONS_CHANGE = "Addons:Change";
     private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling";
     private static final String EVENT_PREF_CHANGE = "Pref:Change";
- 
-    // This is raised from Gecko. It avoids browser.js having to know about the
-    // location that invoked it (the URL bar).
+
+    // This is raised from Gecko and signifies a search via the URL bar (not a bookmarks keyword
+    // search). Using this event (rather than passing the invocation location as an arg) avoids
+    // browser.js having to know about the invocation location.
     public static final String EVENT_KEYWORD_SEARCH = "Search:Keyword";
 
     // This is raised from Java. We include the location in the message.
     public static final String EVENT_SEARCH = "Search:Event";
 
     public enum State {
         NOT_INITIALIZED,
         INITIALIZING,
@@ -676,16 +677,19 @@ public class BrowserHealthRecorder imple
                 Log.d(LOG_TAG, "Pref changed: " + pref);
                 handlePrefValue(pref, message.getBoolean("value"));
                 this.onEnvironmentChanged();
                 return;
             }
 
             // Searches.
             if (EVENT_KEYWORD_SEARCH.equals(event)) {
+                // A search via the URL bar. Since we eliminate all other search possibilities
+                // (e.g. bookmarks keyword, search suggestion) when we initially process the
+                // search URL, this is considered a default search.
                 recordSearch(message.getString("identifier"), "bartext");
                 return;
             }
             if (EVENT_SEARCH.equals(event)) {
                 if (!message.has("location")) {
                     Log.d(LOG_TAG, "Ignoring search without location.");
                     return;
                 }
--- a/mobile/android/base/resources/layout/home_top_sites_page.xml
+++ b/mobile/android/base/resources/layout/home_top_sites_page.xml
@@ -1,29 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="fill_parent"
-              android:layout_height="fill_parent"
-              android:orientation="vertical">
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:orientation="vertical">
 
     <org.mozilla.gecko.home.HomeListView
             android:id="@+id/list"
             style="@style/Widget.TopSitesListView"
             android:layout_width="fill_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"/>
+            android:layout_height="fill_parent"/>
 
     <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
                                        style="@style/Widget.HomeBanner"
                                        android:layout_width="fill_parent"
                                        android:layout_height="@dimen/home_banner_height"
                                        android:background="@drawable/home_banner"
                                        android:layout_gravity="bottom"
                                        android:gravity="center_vertical"
                                        android:visibility="gone"
                                        android:clickable="true"
                                        android:focusable="true"/>
 
-</LinearLayout>
+</FrameLayout>
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -34,17 +34,17 @@
 [testClearPrivateData]
 [testSettingsMenuItems]
 [testSystemPages]
 # [testPermissions] # see bug 757475
 [testJarReader]
 [testDistribution]
 [testFindInPage]
 [testInputUrlBar]
-# [testAddSearchEngine] # disabled on fig - bug 880060
+[testAddSearchEngine]
 [testImportFromAndroid]
 [testMasterPassword]
 [testDeviceSearchEngine]
 
 # Used for Talos, please don't use in mochitest
 #[testPan]
 #[testCheck]
 #[testCheck2]
--- a/mobile/android/base/tests/robocop_search.html
+++ b/mobile/android/base/tests/robocop_search.html
@@ -1,11 +1,11 @@
 <html>
     <header> 
         <title> Robocop Search Engine </title>
     </header>
     <body>
         <form method="get" action="http://www.google.com/search">
-            <input type="text" name="q" size="50" maxlength="255" value="" />
+            <input type="text" name="q" style="width:300px; height:500px;" maxlength="255" value="" />
             <input type="submit" value="Google Search" />
         </form>
     </body>
 </html>
--- a/mobile/android/base/tests/testAddSearchEngine.java.in
+++ b/mobile/android/base/tests/testAddSearchEngine.java.in
@@ -1,108 +1,112 @@
 #filter substitution
 package @ANDROID_PACKAGE_NAME@.tests;
 
 import @ANDROID_PACKAGE_NAME@.*;
 import android.view.View;
 import android.widget.ListAdapter;
 import android.widget.ListView;
-import android.util.Log;
 import java.util.ArrayList;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 /**
  * Test adding a search engine from an input field context menu.
- * 1. Get the number of existing search engines (As shown in the AwesomeScreen).
+ * 1. Get the number of existing search engines from the SearchEngine:Data event and as displayed in about:home.
  * 2. Load a page with a text field, open the context menu and add a search engine from the page.
  * 3. Get the number of search engines after adding the new one and verify it has increased by 1.
  */
-public class testAddSearchEngine extends PixelTest {
+public class testAddSearchEngine extends AboutHomeTest {
     private final int MAX_WAIT_TEST_MS = 5000;
+    private final String SEARCH_TEXT = "Firefox for Android";
+    private final String ADD_SEARCHENGINE_OPTION_TEXT = "Add Search Engine";
+
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
 
     public void testAddSearchEngine() {
-        String blankPageURL = getAbsoluteUrl("/robocop/robocop_blank_01.html");
-        String searchEngineURL = getAbsoluteUrl("/robocop/robocop_search.html");
+        String blankPageURL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
+        String searchEngineURL = getAbsoluteUrl(StringHelper.ROBOCOP_SEARCH_URL);
 
         blockForGeckoReady();
+        int height = mDriver.getGeckoTop() + 150;
+        int width = mDriver.getGeckoLeft() + 150;
+
         inputAndLoadUrl(blankPageURL);
-        waitForText("Browser Blank Page 01");
+        waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
 
         // Get the searchengine data by clicking the awesomebar - this causes Gecko to send Java the list
         // of search engines.
         Actions.EventExpecter searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data");
         focusUrlBar();
+        mActions.sendKeys(SEARCH_TEXT);
         String eventData = searchEngineDataEventExpector.blockForEventData();
         searchEngineDataEventExpector.unregisterListener();
 
         ArrayList<String> searchEngines;
         try {
             // Parse the data to get the number of searchengines.
             searchEngines = getSearchEnginesNames(eventData);
         } catch (JSONException e) {
             mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko prior to addition of new engine.", e.toString());
             return;
         }
         final int initialNumSearchEngines = searchEngines.size();
         mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
 
         // Verify that the number of displayed search engines is the same as the one received through the SearchEngines:Data event.
-        verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines);
+        verifyDisplayedSearchEnginesCount(initialNumSearchEngines);
 
         // Load the page for the search engine to add.
         inputAndLoadUrl(searchEngineURL);
-        waitForText("Robocop Search Engine");
+        waitForText(StringHelper.ROBOCOP_SEARCH_TITLE);
+        verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE);
 
         // Used to long-tap on the search input box for the search engine to add.
-        int height = mDriver.getGeckoTop() + 10;
-        int width = mDriver.getGeckoLeft() + 20;
+        getInstrumentation().waitForIdleSync();
         mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
         mSolo.clickLongOnScreen(width,height);
-        if (!waitForText("Add Search Engine")) {
-            // TODO: clickLongOnScreen does not always work - known Robotium issue - . Clicking a second time seems to work.
-            mAsserter.dumpLog("Something went wrong and the context menu was not opened. Trying again");
-            mSolo.clickLongOnScreen(width,height);
-        }
-        mAsserter.ok(waitForText("Add Search Engine"), "Waiting for the context menu to be opened", "The context menu was opened");
+
+        mAsserter.ok(waitForText(ADD_SEARCHENGINE_OPTION_TEXT), "Waiting for the context menu to be opened", "The context menu was opened");
 
         // Add the search engine
-        mSolo.clickOnText("Add Search Engine");
+        mSolo.clickOnText(ADD_SEARCHENGINE_OPTION_TEXT);
         waitForText("Cancel");
         clickOnButton("OK");
-        mAsserter.ok(!mSolo.searchText("Add Search Engine"), "Adding the Search Engine", "The add Search Engine pop-up has been closed");
+        mAsserter.ok(!mSolo.searchText(ADD_SEARCHENGINE_OPTION_TEXT), "Adding the Search Engine", "The add Search Engine pop-up has been closed");
+        waitForText(StringHelper.ROBOCOP_SEARCH_TITLE); // Make sure the pop-up is closed and we are back at the searchengine page
 
         // Load Robocop Blank 1 again to give the time for the searchengine to be added
         // TODO: This is a potential source of intermittent oranges - it's a race condition!
-        loadAndPaint(blankPageURL);
-        waitForText("Browser Blank Page 01");
+        loadUrl(blankPageURL);
+        waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
 
         // Load search engines again and check that the quantity of engines has increased by 1.
         searchEngineDataEventExpector = mActions.expectGeckoEvent("SearchEngines:Data");
         focusUrlBar();
+        mActions.sendKeys(SEARCH_TEXT);
         eventData = searchEngineDataEventExpector.blockForEventData();
 
         try {
             // Parse the data to get the number of searchengines
             searchEngines = getSearchEnginesNames(eventData);
         } catch (JSONException e) {
             mAsserter.ok(false, "Fatal exception in testAddSearchEngine while decoding JSON search engine string from Gecko after adding of new engine.", e.toString());
             return;
         }
 
         mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
         mAsserter.is(searchEngines.size(), initialNumSearchEngines + 1, "Checking the number of Search Engines has increased");
         
         // Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event.
-        verifyDisplayedSearchEnginesCount("Browser Blank Page 01", initialNumSearchEngines + 1);
+        verifyDisplayedSearchEnginesCount(initialNumSearchEngines + 1);
         searchEngineDataEventExpector.unregisterListener();
     }
 
     /**
      * Helper method to decode a list of search engine names from the provided search engine information
      * JSON string sent from Gecko.
      * @param searchEngineData The JSON string representing the search engine array to process
      * @return An ArrayList<String> containing the names of all the search engines represented in
@@ -118,56 +122,26 @@ public class testAddSearchEngine extends
             JSONObject engineJSON = engines.getJSONObject(i);
             searchEngineNames.add(engineJSON.getString("name"));
         }
         return searchEngineNames;
     }
 
     /**
      * Method to verify that the displayed number of search engines matches the expected number.
-     * Uses a BooleanTest which counts how many SearchEngineRow instances are being displayed
-     * in the Awesomescreen.
-     * @param waitText Text from the loaded page to expect. Used to detect when the Awesomescreen
-     *                 close animation has completed.
-     * @param expectedCountParam The expected number of search engines.
+     * @param expectedCount The expected number of search engines.
      */
-    public void verifyDisplayedSearchEnginesCount(String waitText, int expectedCountParam) {
-        final int expectedCount = expectedCountParam;
-        mActions.sendKeys("Firefox for Android");
+    public void verifyDisplayedSearchEnginesCount(final int expectedCount) {
+        mSolo.clearEditText(0);
+        mActions.sendKeys(SEARCH_TEXT);
         boolean correctNumSearchEnginesDisplayed = waitForTest(new BooleanTest() {
             @Override
             public boolean test() {
-                ArrayList<ListView> views;
-                int searchEngineCount = 0;
-                views = mSolo.getCurrentViews(ListView.class);
-                for (ListView view : views) {
-                    ListAdapter adapter = view.getAdapter();
-                    if (adapter != null) {
-                        // Only count SearchEngineRow views - other views are not relavent to this test.
-                        try {
-                            ClassLoader classLoader = getActivity().getClassLoader();
-                            Class searchEngineRow = classLoader.loadClass("org.mozilla.gecko.SearchEngineRow");
-                            for (int i = 0; i < adapter.getCount(); i++ ) {
-                                View item = view.getChildAt(i);
-                                if (searchEngineRow.isInstance(item)) {
-                                    searchEngineCount++;
-                                }
-                            }
-                        } catch (Exception e) {
-                             mAsserter.dumpLog("Exception in verifyDisplayedSearchEnginesCount", e);
-                        }
-                    } 
-                }
-                if (searchEngineCount == expectedCount) {
-                    return true;
-                } else {
-                    mAsserter.dumpLog("The wrong number of search engines was found. Found " + searchEngineCount + " search engines");
-                    return false;
-                }
+                return (findListViewWithTag("browser_search").getAdapter().getCount() == expectedCount);
             }
         }, MAX_WAIT_TEST_MS);
         
-        // Close the Awesomescreen
+        // Exit about:home
         mActions.sendSpecialKey(Actions.SpecialKey.BACK);
-        waitForText(waitText);
+        waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
         mAsserter.ok(correctNumSearchEnginesDisplayed, expectedCount + " Search Engines should be displayed" , "The correct number of Search Engines has been displayed");
    }
 }
--- a/mobile/android/base/widget/FaviconView.java
+++ b/mobile/android/base/widget/FaviconView.java
@@ -57,27 +57,19 @@ public class FaviconView extends ImageVi
     }
 
     /**
      * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to
      * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space
      * in this view with the coloured background.
      */
     private void formatImage() {
-        // If we're called before bitmap is set, just show the default.
-        if (mIconBitmap == null) {
-            setImageResource(R.drawable.favicon);
-            hideBackground();
-            return;
-        }
-
-        // If we're called before size set, abort.
-        if (mActualWidth == 0 || mActualHeight == 0) {
-            hideBackground();
-            setImageResource(R.drawable.favicon);
+        // If we're called before bitmap is set, or before size is set, show blank.
+        if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) {
+            clearImage();
             return;
         }
 
         if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) {
             scaleBitmap();
             // Don't scale the image every time something changes.
             mScalingExpected = false;
         }
@@ -137,35 +129,49 @@ public class FaviconView extends ImageVi
      *
      * @param bitmap favicon image
      * @param key string used as a key to cache the dominant color of this image
      * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView.
      *                     Typically, you should prefer using Favicons obtained via the caching system
      *                     (Favicons class), so as to exploit caching.
      */
     private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) {
+        if (bitmap == null) {
+            showDefaultFavicon();
+            return;
+        }
+
         // Reassigning the same bitmap? Don't bother.
         if (mUnscaledBitmap == bitmap) {
             return;
         }
         mUnscaledBitmap = bitmap;
         mIconBitmap = bitmap;
         mIconKey = key;
         mScalingExpected = allowScaling;
 
         // Possibly update the display.
         formatImage();
     }
 
+    private void showDefaultFavicon() {
+        setImageResource(R.drawable.favicon);
+        hideBackground();
+    }
+
     /**
      * Clear image and background shown by this view.
      */
     public void clearImage() {
         setImageResource(0);
         hideBackground();
+        mUnscaledBitmap = null;
+        mIconBitmap = null;
+        mIconKey = null;
+        mScalingExpected = false;
     }
 
     /**
      * Update the displayed image and apply the scaling logic.
      * The scaling logic will attempt to resize the image to fit correctly inside the view in a way
      * that avoids unreasonable levels of loss of quality.
      * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache
      * introduced in Bug 914296.
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -1397,17 +1397,19 @@ var BrowserApp = {
         this._handleTabSelected(this.getTabForId(parseInt(aData)));
         break;
 
       case "Tab:Closed":
         this._handleTabClosed(this.getTabForId(parseInt(aData)));
         break;
 
       case "keyword-search":
-        // This assumes that the user can only perform a keyword search on the selected tab.
+        // This event refers to a search via the URL bar, not a bookmarks
+        // keyword search. Note that this code assumes that the user can only
+        // perform a keyword search on the selected tab.
         this.selectedTab.userSearch = aData;
 
         let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
         sendMessageToJava({
           type: "Search:Keyword",
           identifier: engine.identifier,
           name: engine.name,
         });
--- a/mobile/android/components/HelperAppDialog.js
+++ b/mobile/android/components/HelperAppDialog.js
@@ -8,16 +8,21 @@ const Cu = Components.utils;
 const Cr = Components.results;
 
 const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
 // -----------------------------------------------------------------------
 // HelperApp Launcher Dialog
 // -----------------------------------------------------------------------
 
 function HelperAppLauncherDialog() { }
 
 HelperAppLauncherDialog.prototype = {
   classID: Components.ID("{e9d277a0-268a-4ec2-bb8c-10fdf3e44611}"),
@@ -25,25 +30,26 @@ HelperAppLauncherDialog.prototype = {
 
   show: function hald_show(aLauncher, aContext, aReason) {
     // Save everything by default
     aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;
     aLauncher.saveToDisk(null, false);
   },
 
   promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
-    // Retrieve the user's default download directory
-    let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
-    let defaultFolder = dnldMgr.userDownloadsDirectory;
+    return Task.spawn(function() {
+      // Retrieve the user's default download directory
+      let defaultFolder = yield Downloads.getPreferredDownloadsDirectory();
 
-    try {
-      file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
-    } catch (e) { }
+      try {
+        file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
+      } catch (e) { }
 
-    return file;
+      throw new Task.Result(file);
+    }.bind(this));
   },
 
   validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) {
     if (!(aLocalFile && this.isUsableDirectory(aLocalFile)))
       return null;
 
     // Remove any leading periods, since we don't want to save hidden files
     // automatically.
--- a/mobile/android/components/NSSDialogService.js
+++ b/mobile/android/components/NSSDialogService.js
@@ -177,17 +177,17 @@ NSSDialogs.prototype = {
     while (true) {
       let prompt = this.getPrompt(this.getString("clientAuthAsk.title"),
                                      this.getString("clientAuthAsk.message1"),
                                      [ this.getString("nssdialogs.ok.label"),
                                        this.getString("clientAuthAsk.viewCert.label"),
                                        this.getString("nssdialogs.cancel.label")
                                      ])
       .addLabel({ id: "requestedDetails", label: serverRequestedDetails } )
-      .addMenuList({
+      .addMenulist({
         id: "nicknames",
         label: this.getString("clientAuthAsk.message2"),
         values: certNickList, selected: selectedIndex
       }).addCheckbox({
         id: "rememberBox",
         label: this.getString("clientAuthAsk.remember.label"),
         checked: rememberSetting
       });
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -699,17 +699,19 @@ Connection::initializeInternal(nsIFile* 
 
 nsresult
 Connection::databaseElementExists(enum DatabaseElementType aElementType,
                                   const nsACString &aElementName,
                                   bool *_exists)
 {
   if (!mDBConn) return NS_ERROR_NOT_INITIALIZED;
 
-  nsAutoCString query("SELECT name FROM sqlite_master WHERE type = '");
+  nsCString query("SELECT name FROM (SELECT * FROM sqlite_master UNION ALL "
+                                    "SELECT * FROM sqlite_temp_master) "
+                  "WHERE type = '");
   switch (aElementType) {
     case INDEX:
       query.Append("index");
       break;
     case TABLE:
       query.Append("table");
       break;
   }
--- a/storage/test/unit/test_storage_connection.js
+++ b/storage/test/unit/test_storage_connection.js
@@ -103,16 +103,29 @@ add_task(function test_tableExists_not_c
 });
 
 add_task(function test_indexExists_not_created()
 {
   var msc = getOpenedDatabase();
   do_check_false(msc.indexExists("foo"));
 });
 
+add_task(function test_temp_tableExists_and_indexExists()
+{
+  var msc = getOpenedDatabase();
+  msc.executeSimpleSQL("CREATE TEMP TABLE test_temp(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");
+  do_check_true(msc.tableExists("test_temp"));
+
+  msc.executeSimpleSQL("CREATE INDEX test_temp_ind ON test_temp (name)");
+  do_check_true(msc.indexExists("test_temp_ind"));
+
+  msc.executeSimpleSQL("DROP INDEX test_temp_ind");
+  msc.executeSimpleSQL("DROP TABLE test_temp");
+});
+
 add_task(function test_createTable_not_created()
 {
   var msc = getOpenedDatabase();
   msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT");
   do_check_true(msc.tableExists("test"));
 });
 
 add_task(function test_indexExists_created()
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -459,25 +459,36 @@ class MochiRemote(Mochitest):
         print '\n'.join(logFile)
         with open(self.localLog, 'w') as localLog:
             localLog.write('\n'.join(logFile))
 
         if failed > 0:
             return 1
         return 0
 
-    def printScreenshot(self):
-        try:
-            image = self._dm.pullFile("/mnt/sdcard/Robotium-Screenshots/robocop-screenshot.jpg")
-            encoded = base64.b64encode(image)
-            log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded)
-        except:
-            # If the test passes, no screenshot will be generated and
-            # pullFile will fail -- continue silently.
-            pass
+    def printScreenshots(self, screenShotDir):
+        # TODO: This can be re-written after completion of bug 749421
+        if not self._dm.dirExists(screenShotDir):
+            log.info("SCREENSHOT: No ScreenShots directory available: " + screenShotDir)
+            return
+
+        printed = 0
+        for name in self._dm.listFiles(screenShotDir):
+            fullName = screenShotDir + "/" + name
+            log.info("SCREENSHOT: FOUND: [%s]", fullName)
+            try:
+                image = self._dm.pullFile(fullName)
+                encoded = base64.b64encode(image)
+                log.info("SCREENSHOT: data:image/jpg;base64,%s", encoded)
+                printed += 1
+            except:
+                log.info("SCREENSHOT: Could not be parsed")
+                pass
+
+        log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed)
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 log.info('\n'+(''.join(logcat)))
             log.info("Device info: %s", self._dm.getInfo())
             log.info("Test root: %s", self._dm.getDeviceRoot())
@@ -649,25 +660,26 @@ def main():
                 for i in range(20):
                    if ("pandaboard" in devOS):
                        cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser2.db 'insert or replace into bookmarks(_id,title,url,folder,parent,position) values (" + str(30 + i) + ",\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",0,1," + str(100 + i) + ");'"]
                    else:
                        cmd = ['execsu', 'sqlite3', "/data/data/com.android.browser/databases/browser.db 'insert into bookmarks(title,url,bookmark) values (\"Bookmark"+ str(i) + "\",\"http://www.bookmark" + str(i) + ".com\",1);'"]
                    if (options.dm_trans == "sut"):
                        dm._runCmds([{"cmd": " ".join(cmd)}])
             try:
-                dm.removeDir("/mnt/sdcard/Robotium-Screenshots")
+                screenShotDir = "/mnt/sdcard/Robotium-Screenshots"
+                dm.removeDir(screenShotDir)
                 dm.recordLogcat()
                 result = mochitest.runTests(options)
                 if result != 0:
                     log.error("runTests() exited with code %s", result)
                 log_result = mochitest.addLogData()
                 if result != 0 or log_result != 0:
                     mochitest.printDeviceInfo(printLogcat=True)
-                    mochitest.printScreenshot()
+                    mochitest.printScreenshots(screenShotDir)
                 # Ensure earlier failures aren't overwritten by success on this run
                 if retVal is None or retVal == 0:
                     retVal = result
             except:
                 log.error("Automation Error: Exception caught while running tests")
                 traceback.print_exc()
                 mochitest.stopWebServer(options)
                 mochitest.stopWebSocketServer(options)
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -710,18 +710,25 @@ var StyleRuleActor = protocol.ActorClass
    *
    * @returns the rule with updated properties
    */
   modifyProperties: method(function(modifications) {
     let validProps = new Map();
 
     // Use a fresh element for each call to this function to prevent side effects
     // that pop up based on property values that were already set on the element.
-    let tempElement = Services.appShell.hiddenDOMWindow.
-      document.createElement("div");
+
+    let document;
+    if (this.rawNode) {
+      document = this.rawNode.ownerDocument;
+    } else {
+      document = this.rawRule.parentStyleSheet.ownerNode.ownerDocument;
+    }
+
+    let tempElement = document.createElement("div");
 
     for (let mod of modifications) {
       if (mod.type === "set") {
         tempElement.style.setProperty(mod.name, mod.value, mod.priority || "");
         this.rawStyle.setProperty(mod.name,
           tempElement.style.getPropertyValue(mod.name), mod.priority || "");
       } else if (mod.type === "remove") {
         this.rawStyle.removeProperty(mod.name);
--- a/toolkit/modules/Sqlite.jsm
+++ b/toolkit/modules/Sqlite.jsm
@@ -595,48 +595,48 @@ OpenedConnection.prototype = Object.free
         deferred.reject(error);
       }.bind(this)
     );
 
     return deferred.promise;
   },
 
   /**
-   * Whether a table exists in the database.
-   *
-   * IMPROVEMENT: Look for temporary tables.
+   * Whether a table exists in the database (both persistent and temporary tables).
    *
    * @param name
    *        (string) Name of the table.
    *
    * @return Promise<bool>
    */
   tableExists: function (name) {
     return this.execute(
-      "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
+      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
+                        "SELECT * FROM sqlite_temp_master) " +
+      "WHERE type = 'table' AND name=?",
       [name])
       .then(function onResult(rows) {
         return Promise.resolve(rows.length > 0);
       }
     );
   },
 
   /**
-   * Whether a named index exists.
-   *
-   * IMPROVEMENT: Look for indexes in temporary tables.
+   * Whether a named index exists (both persistent and temporary tables).
    *
    * @param name
    *        (string) Name of the index.
    *
    * @return Promise<bool>
    */
   indexExists: function (name) {
     return this.execute(
-      "SELECT name FROM sqlite_master WHERE type='index' AND name=?",
+      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
+                        "SELECT * FROM sqlite_temp_master) " +
+      "WHERE type = 'index' AND name=?",
       [name])
       .then(function onResult(rows) {
         return Promise.resolve(rows.length > 0);
       }
     );
   },
 
   /**
--- a/toolkit/modules/tests/xpcshell/test_sqlite.js
+++ b/toolkit/modules/tests/xpcshell/test_sqlite.js
@@ -55,16 +55,32 @@ function getDummyDatabase(name, extraOpt
   for (let [k, v] in Iterator(TABLES)) {
     yield c.execute("CREATE TABLE " + k + "(" + v + ")");
     c._initialStatementCount++;
   }
 
   throw new Task.Result(c);
 }
 
+function getDummyTempDatabase(name, extraOptions={}) {
+  const TABLES = {
+    dirs: "id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT",
+    files: "id INTEGER PRIMARY KEY AUTOINCREMENT, dir_id INTEGER, path TEXT",
+  };
+
+  let c = yield getConnection(name, extraOptions);
+  c._initialStatementCount = 0;
+
+  for (let [k, v] in Iterator(TABLES)) {
+    yield c.execute("CREATE TEMP TABLE " + k + "(" + v + ")");
+    c._initialStatementCount++;
+  }
+
+  throw new Task.Result(c);
+}
 
 function run_test() {
   Cu.import("resource://testing-common/services-common/logging.js");
   initTestLogging("Trace");
 
   run_next_test();
 }
 
@@ -198,16 +214,37 @@ add_task(function test_index_exists() {
   do_check_false(yield c.indexExists("does_not_exist"));
 
   yield c.execute("CREATE INDEX my_index ON dirs (path)");
   do_check_true(yield c.indexExists("my_index"));
 
   yield c.close();
 });
 
+add_task(function test_temp_table_exists() {
+  let c = yield getDummyTempDatabase("temp_table_exists");
+
+  do_check_false(yield c.tableExists("temp_does_not_exist"));
+  do_check_true(yield c.tableExists("dirs"));
+  do_check_true(yield c.tableExists("files"));
+
+  yield c.close();
+});
+
+add_task(function test_temp_index_exists() {
+  let c = yield getDummyTempDatabase("temp_index_exists");
+
+  do_check_false(yield c.indexExists("temp_does_not_exist"));
+
+  yield c.execute("CREATE INDEX my_index ON dirs (path)");
+  do_check_true(yield c.indexExists("my_index"));
+
+  yield c.close();
+});
+
 add_task(function test_close_cached() {
   let c = yield getDummyDatabase("close_cached");
 
   yield c.executeCached("SELECT * FROM dirs");
   yield c.executeCached("SELECT * FROM files");
 
   yield c.close();
 });
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -21,16 +21,17 @@ const PREF_APP_UPDATE_ENABLED         = 
 const PREF_APP_UPDATE_AUTO            = "app.update.auto";
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_HOTFIX_LASTVERSION      = "extensions.hotfix.lastVersion";
 const PREF_EM_HOTFIX_URL              = "extensions.hotfix.url";
 const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
 const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";
 const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
 const PREF_SELECTED_LOCALE            = "general.useragent.locale";
+const UNKNOWN_XPCOM_ABI               = "unknownABI";
 
 const UPDATE_REQUEST_VERSION          = 2;
 const CATEGORY_UPDATE_PARAMS          = "extension-update-params";
 
 const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
 const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
 #ifdef MOZ_COMPATIBILITY_NIGHTLY
 var PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly";