merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 25 Apr 2014 13:18:39 +0200
changeset 180545 09a19b25b9cf0fd8fba893bfa7ebc28aa99fe32a
parent 180520 2b02d933c39a96e44be37e955ed49dd4d8505a3d (current diff)
parent 180544 70fdf06731b8c25729527cc09ad05910b64de0a3 (diff)
child 180546 7711e1bc8d4b5f1d8f9ccea14d8e4c18e5a079e7
child 180611 3d8e8213e08ed4986b4e9d37bedee2cdf3826375
child 180643 b917e6f9dbb769bb5361d185538c827eff9abc8b
child 180663 ab40a6469c74e8c03011f34d2ec9271b95c449fe
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone31.0a1
merge fx-team to mozilla-central
b2g/components/B2GComponents.manifest
b2g/components/moz.build
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
browser/themes/shared/devtools/images/eyedropper-black.png
content/xul/document/test/test_documentnotification.xul
content/xul/document/test/window_documentnotification.xul
mobile/android/base/tests/robocop.ini
mobile/android/chrome/content/browser.js
mobile/android/installer/package-manifest.in
modules/libpref/src/init/all.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1223,27 +1223,28 @@ pref("devtools.appmanager.enabled", true
 pref("devtools.appmanager.lastTab", "help");
 pref("devtools.appmanager.manifestEditor.enabled", true);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
-pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
+pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 
 // Toolbox Button preferences
 pref("devtools.command-button-pick.enabled", true);
 pref("devtools.command-button-splitconsole.enabled", true);
 pref("devtools.command-button-paintflashing.enabled", false);
 pref("devtools.command-button-tilt.enabled", false);
 pref("devtools.command-button-scratchpad.enabled", false);
 pref("devtools.command-button-responsive.enabled", true);
+pref("devtools.command-button-eyedropper.enabled", false);
 
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 // What was the last active sidebar in the inspector
 pref("devtools.inspector.activeSidebar", "ruleview");
 // Enable the markup preview
 pref("devtools.inspector.markupPreview", false);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -186,16 +186,70 @@ let AboutHomeListener = {
       case "settings":
         sendAsyncMessage("AboutHome:Settings");
         break;
     }
   },
 };
 AboutHomeListener.init(this);
 
+
+let ContentSearchMediator = {
+
+  whitelist: new Set([
+    "about:newtab",
+  ]),
+
+  init: function (chromeGlobal) {
+    chromeGlobal.addEventListener("ContentSearchClient", this, true, true);
+    addMessageListener("ContentSearch", this);
+  },
+
+  handleEvent: function (event) {
+    if (this._contentWhitelisted) {
+      this._sendMsg(event.detail.type, event.detail.data);
+    }
+  },
+
+  receiveMessage: function (msg) {
+    if (msg.data.type == "AddToWhitelist") {
+      for (let uri of msg.data.data) {
+        this.whitelist.add(uri);
+      }
+      this._sendMsg("AddToWhitelistAck");
+      return;
+    }
+    if (this._contentWhitelisted) {
+      this._fireEvent(msg.data.type, msg.data.data);
+    }
+  },
+
+  get _contentWhitelisted() {
+    return this.whitelist.has(content.document.documentURI.toLowerCase());
+  },
+
+  _sendMsg: function (type, data=null) {
+    sendAsyncMessage("ContentSearch", {
+      type: type,
+      data: data,
+    });
+  },
+
+  _fireEvent: function (type, data=null) {
+    content.dispatchEvent(new content.CustomEvent("ContentSearchService", {
+      detail: {
+        type: type,
+        data: data,
+      },
+    }));
+  },
+};
+ContentSearchMediator.init(this);
+
+
 var global = this;
 
 // Lazily load the finder code
 addMessageListener("Finder:Initialize", function () {
   let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
   new RemoteFinderListener(global);
 });
 
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -192,21 +192,27 @@ let gGrid = {
     if (this._cellMargin === undefined) {
       let refCell = document.querySelector(".newtab-cell");
       this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) * 2;
       this._cellHeight = refCell.offsetHeight + this._cellMargin;
       this._cellWidth = refCell.offsetWidth + this._cellMargin;
     }
 
     let availSpace = document.documentElement.clientHeight - this._cellMargin -
-                     document.querySelector("#newtab-margin-undo-container").offsetHeight;
+                     document.querySelector("#newtab-margin-undo-container").offsetHeight -
+                     document.querySelector("#newtab-search-form").offsetHeight;
     let visibleRows = Math.floor(availSpace / this._cellHeight);
     this._node.style.height = this._computeHeight() + "px";
     this._node.style.maxHeight = this._computeHeight(visibleRows) + "px";
     this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
                                 GRID_WIDTH_EXTRA + "px";
+
+    // Resize the search bar.
+    let width = parseFloat(window.getComputedStyle(this._node).width);
+    let visibleCols = Math.floor(width / this._cellWidth);
+    gSearch.setWidth(visibleCols * this._cellWidth - this._cellMargin);
   },
 
   _shouldRenderGrid : function Grid_shouldRenderGrid() {
     let cellsLength = this._node.querySelectorAll(".newtab-cell").length;
     return cellsLength != (gGridPrefs.gridRows * gGridPrefs.gridColumns);
   }
 };
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -1,22 +1,28 @@
 /* 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/. */
 
+input {
+  font: message-box !important;
+  font-size: 16px !important;
+}
+
 input[type=button] {
   cursor: pointer;
 }
 
 /* SCROLLBOX */
 #newtab-scrollbox {
   display: -moz-box;
   position: relative;
   -moz-box-flex: 1;
   -moz-user-focus: normal;
+  -moz-box-orient: vertical;
 }
 
 #newtab-scrollbox:not([page-disabled]) {
   overflow: auto;
 }
 
 /* UNDO */
 #newtab-undo-container {
@@ -49,28 +55,36 @@ input[type=button] {
   position: relative;
   -moz-box-flex: 1;
   -moz-box-orient: vertical;
 }
 
 #newtab-margin-undo-container {
   display: -moz-box;
   -moz-box-pack: center;
+  margin-bottom: 26px; /* 32 - 6 search form top "padding" */
 }
 
 #newtab-horizontal-margin {
   display: -moz-box;
   -moz-box-flex: 1;
 }
 
 #newtab-margin-top,
 #newtab-margin-bottom {
   display: -moz-box;
+  position: relative;
+}
+
+#newtab-margin-top {
   -moz-box-flex: 1;
-  position: relative;
+}
+
+#newtab-margin-bottom {
+  -moz-box-flex: 2;
 }
 
 .newtab-side-margin {
   min-width: 16px;
   -moz-box-flex: 1;
 }
 
 /* GRID */
@@ -208,20 +222,173 @@ input[type=button] {
  */
 .newtab-drag {
   width: 1px;
   height: 1px;
   background-color: #fff;
   opacity: 0.01;
 }
 
-/* PANEL */
+/* SPONSORED PANEL */
 #sponsored-panel {
   width: 330px;
 }
 
 #sponsored-panel description {
   margin: 0;
 }
 
 #sponsored-panel .text-link {
   margin: 12px 0 0;
 }
+
+/* SEARCH */
+#newtab-search-container {
+  display: -moz-box;
+  position: relative;
+  -moz-box-align: center;
+  -moz-box-pack: center;
+}
+
+#newtab-search-container[page-disabled] {
+  opacity: 0;
+  pointer-events: none;
+}
+
+#newtab-search-form {
+  display: -moz-box;
+  -moz-box-orient: horizontal;
+  -moz-box-align: center;
+  height: 44px; /* 32 + 6 logo top "padding" + 6 logo bottom "padding" */
+  margin-bottom: 10px; /* 32 - 16 tiles top margin - 6 logo bottom "padding" */
+}
+
+#newtab-search-logo {
+  display: -moz-box;
+  width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
+  height: 38px; /* 26 image height + 6 top "padding" + 6 bottom "padding" */
+  border: 1px solid transparent;
+  -moz-margin-end: 8px;
+  background-repeat: no-repeat;
+  background-position: center;
+  background-size: 65px 26px;
+}
+
+#newtab-search-logo[hidden] {
+  display: none;
+}
+
+#newtab-search-logo[active],
+#newtab-search-logo:hover {
+  background-color: #e9e9e9;
+  border: 1px solid rgb(226, 227, 229);
+  border-radius: 2.5px;
+}
+
+#newtab-search-text {
+  height: 32px;
+  -moz-box-flex: 1;
+
+  padding: 0 8px;
+  background: hsla(0,0%,100%,.9) padding-box;
+  border: 1px solid;
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+  box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+              0 0 2px hsla(210,65%,9%,.1) inset,
+              0 1px 0 hsla(0,0%,100%,.2);
+  border-radius: 2.5px 0 0 2.5px;
+}
+
+#newtab-search-text:-moz-dir(rtl) {
+  border-radius: 0 2.5px 2.5px 0;
+}
+
+#newtab-search-text:focus,
+#newtab-search-text[autofocus] {
+  border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#newtab-search-submit {
+  height: 32px;
+
+  -moz-margin-start: -1px;
+  background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+  padding: 0 9px;
+  border: 1px solid;
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+  -moz-border-start: 1px solid transparent;
+  border-radius: 0 2.5px 2.5px 0;
+  box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+              0 1px 0 hsla(0,0%,100%,.2);
+  cursor: pointer;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+}
+
+#newtab-search-submit:-moz-dir(rtl) {
+  border-radius: 2.5px 0 0 2.5px;
+}
+
+#newtab-search-text:focus + #newtab-search-submit,
+#newtab-search-text + #newtab-search-submit:hover,
+#newtab-search-text[autofocus] + #newtab-search-submit {
+  border-color: #59b5fc #45a3e7 #3294d5;
+  color: white;
+}
+
+#newtab-search-text:focus + #newtab-search-submit,
+#newtab-search-text[autofocus] + #newtab-search-submit {
+  background-image: linear-gradient(#4cb1ff, #1793e5);
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+              0 0 0 1px hsla(0,0%,100%,.1) inset,
+              0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#newtab-search-text + #newtab-search-submit:hover {
+  background-image: linear-gradient(#66bdff, #0d9eff);
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+              0 0 0 1px hsla(0,0%,100%,.1) inset,
+              0 1px 0 hsla(210,54%,20%,.03),
+              0 0 4px hsla(206,100%,20%,.2);
+}
+
+#newtab-search-text + #newtab-search-submit:hover:active {
+  box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+              0 0 1px hsla(211,79%,6%,.2) inset;
+  transition-duration: 0ms;
+}
+
+#newtab-search-panel .panel-arrowcontent {
+  -moz-padding-start: 0;
+  -moz-padding-end: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+  background: rgb(248, 250, 251);
+}
+
+.newtab-search-panel-engine {
+  -moz-box-align: center;
+  padding-top: 4px;
+  padding-bottom: 4px;
+  -moz-padding-start: 24px;
+  -moz-padding-end: 24px;
+}
+
+.newtab-search-panel-engine:not(:last-child) {
+  border-bottom: 1px solid #ccc;
+}
+
+.newtab-search-panel-engine > image {
+  -moz-margin-end: 8px;
+  width: 16px;
+  height: 16px;
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.newtab-search-panel-engine > label {
+  -moz-padding-start: 0;
+  -moz-margin-start: 0;
+  color: rgb(130, 132, 133);
+}
+
+.newtab-search-panel-engine[selected] {
+  background: url("chrome://global/skin/menu/shared-menu-check.png") center left 4px no-repeat transparent;
+}
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -36,24 +36,26 @@ XPCOMUtils.defineLazyGetter(this, "gStri
 
 function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
 
 function inPrivateBrowsingMode() {
   return PrivateBrowsingUtils.isWindowPrivate(window);
 }
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 #include transformations.js
 #include page.js
 #include grid.js
 #include cells.js
 #include sites.js
 #include drag.js
 #include dragDataHelper.js
 #include drop.js
 #include dropTargetShim.js
 #include dropPreview.js
 #include updater.js
 #include undo.js
+#include search.js
 
 // Everything is loaded. Initialize the New Tab Page.
 gPage.init();
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -6,29 +6,38 @@
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
   %newTabDTD;
+  <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
+  %searchBarDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&newtab.pageTitle;">
 
   <xul:panel id="sponsored-panel" orient="vertical" type="arrow">
     <xul:description>&newtab.panel.message;</xul:description>
     <xul:label class="text-link"
                href="https://support.mozilla.org/kb/how-do-sponsored-tiles-work"
                value="&newtab.panel.link.text;" />
   </xul:panel>
 
+  <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
+             noautohide="true">
+    <xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine">
+      <xul:label>&cmd_engineManager.label;</xul:label>
+    </xul:hbox>
+  </xul:panel>
+
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
 
       <div id="newtab-margin-top"/>
 
       <div id="newtab-margin-undo-container">
         <div id="newtab-undo-container" undo-disabled="true">
@@ -41,16 +50,26 @@
                       label="&newtab.undo.restoreButton;"
                       class="newtab-undo-button" />
           <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
                              class="close-icon tabbable"
                              tooltiptext="&newtab.undo.closeTooltip;" />
         </div>
       </div>
 
+      <div id="newtab-search-container">
+        <form id="newtab-search-form" name="searchForm">
+          <div id="newtab-search-logo"/>
+          <input type="text" name="q" value="" id="newtab-search-text"
+                 maxlength="256" dir="auto"/>
+          <input id="newtab-search-submit" type="submit"
+                 value="&searchEndCap.label;"/>
+        </form>
+      </div>
+
       <div id="newtab-horizontal-margin">
         <div class="newtab-side-margin"/>
 
         <div id="newtab-grid">
         </div>
 
         <div class="newtab-side-margin"/>
       </div>
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -106,16 +106,18 @@ let gPage = {
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
       return;
 
     this._initialized = true;
 
+    gSearch.init();
+
     this._mutationObserver = new MutationObserver(() => {
       if (this.allowBackgroundCaptures) {
         Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
 
         // Initialize type counting with the types we want to count
         let directoryCount = {};
         for (let type of DirectoryLinksProvider.linkTypes) {
           directoryCount[type] = 0;
@@ -133,16 +135,20 @@ let gPage = {
 
         // Record how many directory sites were shown, but place counts over the
         // default 9 in the same bucket
         for (let [type, count] of Iterator(directoryCount)) {
           let shownId = "NEWTAB_PAGE_DIRECTORY_" + type.toUpperCase() + "_SHOWN";
           let shownCount = Math.min(10, count);
           Services.telemetry.getHistogramById(shownId).add(shownCount);
         }
+
+        // content.js isn't loaded for the page while it's in the preloader,
+        // which is why this is necessary.
+        gSearch.setUpInitialState();
       }
     });
     this._mutationObserver.observe(document.documentElement, {
       attributes: true,
       attributeFilter: ["allow-background-captures"],
     });
 
     // Initialize and render the grid.
@@ -159,17 +165,17 @@ let gPage = {
   },
 
   /**
    * Updates the 'page-disabled' attributes of the respective DOM nodes.
    * @param aValue Whether the New Tab Page is enabled or not.
    */
   _updateAttributes: function Page_updateAttributes(aValue) {
     // Set the nodes' states.
-    let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid";
+    let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid, #newtab-search-container";
     for (let node of document.querySelectorAll(nodeSelector)) {
       if (aValue)
         node.removeAttribute("page-disabled");
       else
         node.setAttribute("page-disabled", "true");
     }
 
     // Enables/disables the control and link elements.
new file mode 100644
--- /dev/null
+++ b/browser/base/content/newtab/search.js
@@ -0,0 +1,170 @@
+#ifdef 0
+/* 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/. */
+#endif
+
+let gSearch = {
+
+  currentEngineName: null,
+
+  init: function () {
+    for (let idSuffix of this._nodeIDSuffixes) {
+      this._nodes[idSuffix] =
+        document.getElementById("newtab-search-" + idSuffix);
+    }
+
+    window.addEventListener("ContentSearchService", this);
+    this.setUpInitialState();
+  },
+
+  setUpInitialState: function () {
+    this._send("GetState");
+  },
+
+  showPanel: function () {
+    let panel = this._nodes.panel;
+    let logo = this._nodes.logo;
+    panel.openPopup(logo);
+    logo.setAttribute("active", "true");
+    panel.addEventListener("popuphidden", function onHidden() {
+      panel.removeEventListener("popuphidden", onHidden);
+      logo.removeAttribute("active");
+    });
+  },
+
+  search: function (event) {
+    event.preventDefault();
+    let searchStr = this._nodes.text.value;
+    if (this.currentEngineName && searchStr.length) {
+      this._send("Search", {
+        engineName: this.currentEngineName,
+        searchString: searchStr,
+        whence: "newtab",
+      });
+    }
+  },
+
+  manageEngines: function () {
+    this._nodes.panel.hidePopup();
+    this._send("ManageEngines");
+  },
+
+  setWidth: function (width) {
+    this._nodes.form.style.width = width + "px";
+    this._nodes.form.style.maxWidth = width + "px";
+  },
+
+  handleEvent: function (event) {
+    this["on" + event.detail.type](event.detail.data);
+  },
+
+  onState: function (data) {
+    this._makePanel(data.engines);
+    this._setCurrentEngine(data.currentEngine);
+    this._initWhenInitalStateReceived();
+  },
+
+  onCurrentEngine: function (engineName) {
+    this._setCurrentEngine(engineName);
+  },
+
+  _nodeIDSuffixes: [
+    "form",
+    "logo",
+    "manage",
+    "panel",
+    "text",
+  ],
+
+  _nodes: {},
+
+  _initWhenInitalStateReceived: function () {
+    this._nodes.form.addEventListener("submit", e => this.search(e));
+    this._nodes.logo.addEventListener("click", e => this.showPanel());
+    this._nodes.manage.addEventListener("click", e => this.manageEngines());
+    this._initWhenInitalStateReceived = function () {};
+  },
+
+  _send: function (type, data=null) {
+    window.dispatchEvent(new CustomEvent("ContentSearchClient", {
+      detail: {
+        type: type,
+        data: data,
+      },
+    }));
+  },
+
+  _makePanel: function (engines) {
+    let panel = this._nodes.panel;
+
+    // Empty the panel except for the Manage Engines row.
+    let i = 0;
+    while (i < panel.childNodes.length) {
+      let node = panel.childNodes[i];
+      if (node != this._nodes.manage) {
+        panel.removeChild(node);
+      }
+      else {
+        i++;
+      }
+    }
+
+    // Add all the engines.
+    for (let engine of engines) {
+      panel.insertBefore(this._makePanelEngine(panel, engine),
+                         this._nodes.manage);
+    }
+  },
+
+  _makePanelEngine: function (panel, engine) {
+    let box = document.createElementNS(XUL_NAMESPACE, "hbox");
+    box.className = "newtab-search-panel-engine";
+    box.setAttribute("engine", engine.name);
+
+    box.addEventListener("click", () => {
+      this._send("SetCurrentEngine", engine.name);
+      panel.hidePopup();
+      this._nodes.text.focus();
+    });
+
+    let image = document.createElementNS(XUL_NAMESPACE, "image");
+    if (engine.iconURI) {
+      image.setAttribute("src", engine.iconURI);
+    }
+    box.appendChild(image);
+
+    let label = document.createElementNS(XUL_NAMESPACE, "label");
+    label.setAttribute("value", engine.name);
+    box.appendChild(label);
+
+    return box;
+  },
+
+  _setCurrentEngine: function (engine) {
+    this.currentEngineName = engine.name;
+
+    // Set the logo.
+    let logoURI = window.devicePixelRatio == 2 ? engine.logo2xURI :
+                  engine.logoURI;
+    if (logoURI) {
+      this._nodes.logo.hidden = false;
+      this._nodes.logo.style.backgroundImage = "url(" + logoURI + ")";
+      this._nodes.text.placeholder = "";
+    }
+    else {
+      this._nodes.logo.hidden = true;
+      this._nodes.text.placeholder = engine.name;
+    }
+
+    // Set the selected state of all the engines in the panel.
+    for (let box of this._nodes.panel.childNodes) {
+      if (box.getAttribute("engine") == engine.name) {
+        box.setAttribute("selected", "true");
+      }
+      else {
+        box.removeAttribute("selected");
+      }
+    }
+  },
+};
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -394,17 +394,16 @@ skip-if = e10s # Bug ?????? - obscure no
 [browser_urlbarRevert.js]
 skip-if = e10s # Bug ?????? - ESC reverted the location bar value - Got foobar, expected example.com
 [browser_urlbarStop.js]
 skip-if = e10s # Bug ????? - test calls gBrowser.contentWindow.stop
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_search_healthreport.js]
 skip-if = e10s # Bug ?????? - FHR tests failing (either with "no data for today" or "2 records for today")
 [browser_utilityOverlay.js]
-skip-if = e10s # Bug 921947 - openNewTabWith failed with window.content.document being null
 [browser_visibleFindSelection.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 [browser_visibleLabel.js]
 [browser_visibleTabs.js]
 skip-if = e10s # Bug 921905 - pinTab/unpinTab fail in e10s
 [browser_visibleTabs_bookmarkAllPages.js]
 skip-if = e10s # Bug ?????? - bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
 [browser_visibleTabs_bookmarkAllTabs.js]
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -1,11 +1,14 @@
 [DEFAULT]
-support-files = head.js
 skip-if = e10s # Bug ?????? - about:newtab tests don't work in e10s
+support-files =
+  head.js
+  searchEngineLogo.xml
+  searchEngineNoLogo.xml
 
 [browser_newtab_background_captures.js]
 [browser_newtab_block.js]
 [browser_newtab_bug721442.js]
 [browser_newtab_bug722273.js]
 [browser_newtab_bug723102.js]
 [browser_newtab_bug723121.js]
 [browser_newtab_bug725996.js]
@@ -20,13 +23,14 @@ skip-if = os == "mac" # Intermittent fai
 [browser_newtab_bug998387.js]
 [browser_newtab_disable.js]
 [browser_newtab_drag_drop.js]
 [browser_newtab_drag_drop_ext.js]
 [browser_newtab_drop_preview.js]
 [browser_newtab_focus.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reset.js]
+[browser_newtab_search.js]
 [browser_newtab_sponsored_icon_click.js]
 [browser_newtab_tabsync.js]
 [browser_newtab_undo.js]
 [browser_newtab_unpin.js]
 [browser_newtab_update.js]
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -4,19 +4,19 @@
 /*
  * These tests make sure that focusing the 'New Tage Page' works as expected.
  */
 function runTests() {
   // Handle the OSX full keyboard access setting
   Services.prefs.setIntPref("accessibility.tabfocus", 7);
 
   // Focus count in new tab page.
-  // 28 = 9 * 3 + 1 = 9 sites and 1 toggle button, each site has a link, a pin
-  // and a remove button.
-  let FOCUS_COUNT = 28;
+  // 30 = 9 * 3 + 3 = 9 sites, each with link, pin and remove buttons; search
+  // bar; search button; and toggle button.
+  let FOCUS_COUNT = 30;
 
   // Create a new tab page.
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   gURLBar.focus();
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -0,0 +1,295 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// See browser/components/search/test/browser_*_behavior.js for tests of actual
+// searches.
+
+const ENGINE_LOGO = "searchEngineLogo.xml";
+const ENGINE_NO_LOGO = "searchEngineNoLogo.xml";
+
+const SERVICE_EVENT_NAME = "ContentSearchService";
+
+const LOGO_LOW_DPI_SIZE = [65, 26];
+const LOGO_HIGH_DPI_SIZE = [130, 52];
+
+// The test has an expected search event queue and a search event listener.
+// Search events that are expected to happen are added to the queue, and the
+// listener consumes the queue and ensures that each event it receives is at
+// the head of the queue.
+//
+// Each item in the queue is an object { type, deferred }.  type is the
+// expected search event type.  deferred is a Promise.defer() value that is
+// resolved when the event is consumed.
+var gExpectedSearchEventQueue = [];
+
+var gNewEngines = [];
+
+function runTests() {
+  let oldCurrentEngine = Services.search.currentEngine;
+
+  yield addNewTabPageTab();
+  yield whenSearchInitDone();
+
+  // The tab is removed at the end of the test, so there's no need to remove
+  // this listener at the end of the test.
+  info("Adding search event listener");
+  getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener);
+
+  let panel = searchPanel();
+  is(panel.state, "closed", "Search panel should be closed initially");
+
+  // The panel's animation often is not finished when the test clicks on panel
+  // children, which makes the test click the wrong children, so disable it.
+  panel.setAttribute("animate", "false");
+
+  // Add the two test engines.
+  let logoEngine = null;
+  yield promiseNewSearchEngine(true).then(engine => {
+    logoEngine = engine;
+    TestRunner.next();
+  });
+  ok(!!logoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE),
+     "Sanity check: engine should have 1x logo");
+  ok(!!logoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE),
+     "Sanity check: engine should have 2x logo");
+
+  let noLogoEngine = null;
+  yield promiseNewSearchEngine(false).then(engine => {
+    noLogoEngine = engine;
+    TestRunner.next();
+  });
+  ok(!noLogoEngine.getIconURLBySize(...LOGO_LOW_DPI_SIZE),
+     "Sanity check: engine should not have 1x logo");
+  ok(!noLogoEngine.getIconURLBySize(...LOGO_HIGH_DPI_SIZE),
+     "Sanity check: engine should not have 2x logo");
+
+  // Use the search service to change the current engine to the logo engine.
+  Services.search.currentEngine = logoEngine;
+  yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
+  checkCurrentEngine(ENGINE_LOGO);
+
+  // Click the logo to open the search panel.
+  yield Promise.all([
+    promisePanelShown(panel),
+    promiseClick(logoImg()),
+  ]).then(TestRunner.next);
+
+  // In the search panel, click the no-logo engine.  It should become the
+  // current engine.
+  let noLogoBox = null;
+  for (let box of panel.childNodes) {
+    if (box.getAttribute("engine") == noLogoEngine.name) {
+      noLogoBox = box;
+      break;
+    }
+  }
+  ok(noLogoBox, "Search panel should contain the no-logo engine");
+  yield Promise.all([
+    promiseSearchEvents(["CurrentEngine"]),
+    promiseClick(noLogoBox),
+  ]).then(TestRunner.next);
+
+  checkCurrentEngine(ENGINE_NO_LOGO);
+
+  // Switch back to the logo engine.
+  Services.search.currentEngine = logoEngine;
+  yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
+  checkCurrentEngine(ENGINE_LOGO);
+
+  // Open the panel again.
+  yield Promise.all([
+    promisePanelShown(panel),
+    promiseClick(logoImg()),
+  ]).then(TestRunner.next);
+
+  // In the search panel, click the Manage Engines box.
+  let manageBox = $("manage");
+  ok(!!manageBox, "The Manage Engines box should be present in the document");
+  yield Promise.all([
+    promiseManagerOpen(),
+    promiseClick(manageBox),
+  ]).then(TestRunner.next);
+
+  // Done.  Revert the current engine and remove the new engines.
+  Services.search.currentEngine = oldCurrentEngine;
+  yield promiseSearchEvents(["CurrentEngine"]).then(TestRunner.next);
+
+  let events = [];
+  for (let engine of gNewEngines) {
+    Services.search.removeEngine(engine);
+    events.push("State");
+  }
+  yield promiseSearchEvents(events).then(TestRunner.next);
+}
+
+function searchEventListener(event) {
+  info("Got search event " + event.detail.type);
+  let passed = false;
+  let nonempty = gExpectedSearchEventQueue.length > 0;
+  ok(nonempty, "Expected search event queue should be nonempty");
+  if (nonempty) {
+    let { type, deferred } = gExpectedSearchEventQueue.shift();
+    is(event.detail.type, type, "Got expected search event " + type);
+    if (event.detail.type == type) {
+      passed = true;
+      // Let gSearch respond to the event before continuing.
+      executeSoon(() => deferred.resolve());
+    }
+  }
+  if (!passed) {
+    info("Didn't get expected event, stopping the test");
+    getContentWindow().removeEventListener(SERVICE_EVENT_NAME,
+                                           searchEventListener);
+    // Set next() to a no-op so the test really does stop.
+    TestRunner.next = function () {};
+    TestRunner.finish();
+  }
+}
+
+function $(idSuffix) {
+  return getContentDocument().getElementById("newtab-search-" + idSuffix);
+}
+
+function promiseSearchEvents(events) {
+  info("Expecting search events: " + events);
+  events = events.map(e => ({ type: e, deferred: Promise.defer() }));
+  gExpectedSearchEventQueue.push(...events);
+  return Promise.all(events.map(e => e.deferred.promise));
+}
+
+function promiseNewSearchEngine(withLogo) {
+  let basename = withLogo ? ENGINE_LOGO : ENGINE_NO_LOGO;
+  info("Waiting for engine to be added: " + basename);
+
+  // Wait for the search events triggered by adding the new engine.
+  // engine-added engine-loaded
+  let expectedSearchEvents = ["State", "State"];
+  if (withLogo) {
+    // an engine-changed for each of the two logos
+    expectedSearchEvents.push("State", "State");
+  }
+  let eventPromise = promiseSearchEvents(expectedSearchEvents);
+
+  // Wait for addEngine().
+  let addDeferred = Promise.defer();
+  let url = getRootDirectory(gTestPath) + basename;
+  Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+    onSuccess: function (engine) {
+      info("Search engine added: " + basename);
+      gNewEngines.push(engine);
+      addDeferred.resolve(engine);
+    },
+    onError: function (errCode) {
+      ok(false, "addEngine failed with error code " + errCode);
+      addDeferred.reject();
+    },
+  });
+
+  // Make a new promise that wraps the previous promises.  The only point of
+  // this is to pass the new engine to the yielder via deferred.resolve(),
+  // which is a little nicer than passing an array whose first element is the
+  // new engine.
+  let deferred = Promise.defer();
+  Promise.all([addDeferred.promise, eventPromise]).then(values => {
+    let newEngine = values[0];
+    deferred.resolve(newEngine);
+  }, () => deferred.reject());
+  return deferred.promise;
+}
+
+function checkCurrentEngine(basename) {
+  let engine = Services.search.currentEngine;
+  ok(engine.name.contains(basename),
+     "Sanity check: current engine: engine.name=" + engine.name +
+     " basename=" + basename);
+
+  // gSearch.currentEngineName
+  is(gSearch().currentEngineName, engine.name,
+     "currentEngineName: " + engine.name);
+
+  // search bar logo
+  let logoSize = [px * window.devicePixelRatio for (px of LOGO_LOW_DPI_SIZE)];
+  let logoURI = engine.getIconURLBySize(...logoSize);
+  let logo = logoImg();
+  is(logo.hidden, !logoURI,
+     "Logo should be visible iff engine has a logo: " + engine.name);
+  if (logoURI) {
+    is(logo.style.backgroundImage, 'url("' + logoURI + '")', "Logo URI");
+  }
+
+  // "selected" attributes of engines in the panel
+  let panel = searchPanel();
+  for (let engineBox of panel.childNodes) {
+    let engineName = engineBox.getAttribute("engine");
+    if (engineName == engine.name) {
+      is(engineBox.getAttribute("selected"), "true",
+         "Engine box's selected attribute should be true for " +
+         "selected engine: " + engineName);
+    }
+    else {
+      ok(!engineBox.hasAttribute("selected"),
+         "Engine box's selected attribute should be absent for " +
+         "non-selected engine: " + engineName);
+    }
+  }
+}
+
+function promisePanelShown(panel) {
+  let deferred = Promise.defer();
+  info("Waiting for popupshown");
+  panel.addEventListener("popupshown", function onEvent() {
+    panel.removeEventListener("popupshown", onEvent);
+    is(panel.state, "open", "Panel state");
+    executeSoon(() => deferred.resolve());
+  });
+  return deferred.promise;
+}
+
+function promiseClick(node) {
+  let deferred = Promise.defer();
+  let win = getContentWindow();
+  SimpleTest.waitForFocus(() => {
+    EventUtils.synthesizeMouseAtCenter(node, {}, win);
+    deferred.resolve();
+  }, win);
+  return deferred.promise;
+}
+
+function promiseManagerOpen() {
+  info("Waiting for the search manager window to open...");
+  let deferred = Promise.defer();
+  let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                   getService(Ci.nsIWindowWatcher);
+  winWatcher.registerNotification(function onWin(subj, topic, data) {
+    if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+      subj.addEventListener("load", function onLoad() {
+        subj.removeEventListener("load", onLoad);
+        if (subj.document.documentURI ==
+            "chrome://browser/content/search/engineManager.xul") {
+          winWatcher.unregisterNotification(onWin);
+          ok(true, "Observed search manager window opened");
+          is(subj.opener, gWindow,
+             "Search engine manager opener should be the chrome browser " +
+             "window containing the newtab page");
+          executeSoon(() => {
+            subj.close();
+            deferred.resolve();
+          });
+        }
+      });
+    }
+  });
+  return deferred.promise;
+}
+
+function searchPanel() {
+  return $("panel");
+}
+
+function logoImg() {
+  return $("logo");
+}
+
+function gSearch() {
+  return getContentWindow().gSearch;
+}
--- a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -1,15 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
   yield setLinks("0");
   yield addNewTabPageTab();
 
+  // When gSearch modifies the DOM as it sets itself up, it can prevent the
+  // popup from opening, depending on the timing.  Wait until that's done.
+  yield whenSearchInitDone();
+
   let site = getCell(0).node.querySelector(".newtab-site");
   site.setAttribute("type", "sponsored");
 
   let sponsoredPanel = getContentDocument().getElementById("sponsored-panel");
   is(sponsoredPanel.state, "closed", "Sponsored panel must be closed");
 
   function continueOnceOn(event) {
     sponsoredPanel.addEventListener(event, function listener() {
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -20,20 +20,51 @@ let {Promise, NewTabUtils, Sanitizer, cl
 let uri = Services.io.newURI("about:newtab", null, null);
 let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
 
 let isMac = ("nsILocalFileMac" in Ci);
 let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
 let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 let gWindow = window;
 
+// The tests assume all three rows of sites are shown, but the window may be too
+// short to actually show three rows.  Resize it if necessary.
+let requiredInnerHeight =
+  40 + 32 + // undo container + bottom margin
+  44 + 32 + // search bar + bottom margin
+  (3 * (150 + 32)) + // 3 rows * (tile height + title and bottom margin)
+  100; // breathing room
+
+let oldInnerHeight = null;
+if (gBrowser.contentWindow.innerHeight < requiredInnerHeight) {
+  oldInnerHeight = gBrowser.contentWindow.innerHeight;
+  info("Changing browser inner height from " + oldInnerHeight + " to " +
+       requiredInnerHeight);
+  gBrowser.contentWindow.innerHeight = requiredInnerHeight;
+  let screenHeight = {};
+  Cc["@mozilla.org/gfx/screenmanager;1"].
+    getService(Ci.nsIScreenManager).
+    primaryScreen.
+    GetAvailRectDisplayPix({}, {}, {}, screenHeight);
+  screenHeight = screenHeight.value;
+  if (screenHeight < gBrowser.contentWindow.outerHeight) {
+    info("Warning: Browser outer height is now " +
+         gBrowser.contentWindow.outerHeight + ", which is larger than the " +
+         "available screen height, " + screenHeight +
+         ". That may cause problems.");
+  }
+}
+
 registerCleanupFunction(function () {
   while (gWindow.gBrowser.tabs.length > 1)
     gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
 
+  if (oldInnerHeight)
+    gBrowser.contentWindow.innerHeight = oldInnerHeight;
+
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
   Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
 
   // Stop any update timers to prevent unexpected updates in later tests
   let timer = NewTabUtils.allPages._scheduleUpdateTimeout;
   if (timer) {
     clearTimeout(timer);
     delete NewTabUtils.allPages._scheduleUpdateTimeout;
@@ -544,8 +575,40 @@ function whenPagesUpdated(aCallback, aOn
     }
   };
 
   NewTabUtils.allPages.register(page);
   registerCleanupFunction(function () {
     NewTabUtils.allPages.unregister(page);
   });
 }
+
+/**
+ * Waits a small amount of time for search events to stop occurring in the
+ * newtab page.
+ *
+ * newtab pages receive some search events around load time that are difficult
+ * to predict.  There are two categories of such events: (1) "State" events
+ * triggered by engine notifications like engine-changed, due to the search
+ * service initializing itself on app startup.  This can happen when a test is
+ * the first test to run.  (2) "State" events triggered by the newtab page
+ * itself when gSearch first sets itself up.  newtab preloading makes these a
+ * pain to predict.
+ */
+function whenSearchInitDone() {
+  info("Waiting for initial search events...");
+  let numTicks = 0;
+  function reset(event) {
+    info("Got initial search event " + event.detail.type +
+         ", waiting for more...");
+    numTicks = 0;
+  }
+  let eventName = "ContentSearchService";
+  getContentWindow().addEventListener(eventName, reset);
+  let interval = window.setInterval(() => {
+    if (++numTicks >= 100) {
+      info("Done waiting for initial search events");
+      window.clearInterval(interval);
+      getContentWindow().removeEventListener(eventName, reset);
+      TestRunner.next();
+    }
+  }, 0);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineLogo.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/logo" rel="searchform"/>
+<Image width="65" height="26">data:image/png;base64,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</Image>
+<Image width="130" height="52">data:image/png;base64,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</Image>
+</SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineNoLogo.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_newtab_search searchEngineNoLogo.xml</ShortName>
+<Url type="text/html" method="GET" template="http://browser-newtab-search.com/nologo" rel="searchform"/>
+</SearchPlugin>
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -594,77 +594,68 @@ function isElementVisible(aElement)
 }
 
 function makeURLAbsolute(aBase, aUrl)
 {
   // Note:  makeURI() will throw if aUri is not a valid URI
   return makeURI(aUrl, null, makeURI(aBase)).spec;
 }
 
-
 /**
  * openNewTabWith: opens a new tab with the given URL.
  *
  * @param aURL
  *        The URL to open (as a string).
  * @param aDocument
- *        The document from which the URL came, or null. This is used to set the
- *        referrer header and to do a security check of whether the document is
- *        allowed to reference the URL. If null, there will be no referrer
- *        header and no security check.
+ *        Note this parameter is now ignored. There is no security check & no
+ *        referrer header derived from aDocument (null case).
  * @param aPostData
  *        Form POST data, or null.
  * @param aEvent
  *        The triggering event (for the purpose of determining whether to open
  *        in the background), or null.
  * @param aAllowThirdPartyFixup
  *        If true, then we allow the URL text to be sent to third party services
  *        (e.g., Google's I Feel Lucky) for interpretation. This parameter may
  *        be undefined in which case it is treated as false.
  * @param [optional] aReferrer
- *        If aDocument is null, then this will be used as the referrer.
- *        There will be no security check.
+ *        This will be used as the referrer. There will be no security check.
  */ 
 function openNewTabWith(aURL, aDocument, aPostData, aEvent,
                         aAllowThirdPartyFixup, aReferrer) {
-  if (aDocument)
-    urlSecurityCheck(aURL, aDocument.nodePrincipal);
 
   // As in openNewWindowWith(), we want to pass the charset of the
   // current document over to a new tab.
-  var originCharset = aDocument && aDocument.characterSet;
-  if (!originCharset &&
-      document.documentElement.getAttribute("windowtype") == "navigator:browser")
-    originCharset = window.content.document.characterSet;
+  let originCharset = null;
+  if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+    originCharset = gBrowser.selectedBrowser.characterSet;
 
   openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
              { charset: originCharset,
                postData: aPostData,
                allowThirdPartyFixup: aAllowThirdPartyFixup,
-               referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+               referrerURI: aReferrer });
 }
 
+/**
+ * @param aDocument
+ *        Note this parameter is ignored. See openNewTabWith()
+ */
 function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) {
-  if (aDocument)
-    urlSecurityCheck(aURL, aDocument.nodePrincipal);
-
-  // if and only if the current window is a browser window and it has a
-  // document with a character set, then extract the current charset menu
-  // setting from the current document and use it to initialize the new browser
-  // window...
-  var originCharset = aDocument && aDocument.characterSet;
-  if (!originCharset &&
-      document.documentElement.getAttribute("windowtype") == "navigator:browser")
-    originCharset = window.content.document.characterSet;
+  // Extract the current charset menu setting from the current document and
+  // use it to initialize the new browser window...
+  let originCharset = null;
+  if (document.documentElement.getAttribute("windowtype") == "navigator:browser")
+    originCharset = gBrowser.selectedBrowser.characterSet;
 
   openLinkIn(aURL, "window",
              { charset: originCharset,
                postData: aPostData,
                allowThirdPartyFixup: aAllowThirdPartyFixup,
-               referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+               referrerURI: aReferrer });
 }
 
 // aCalledFromModal is optional
 function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
   var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                       .getService(Components.interfaces.nsIURLFormatter)
                       .formatURLPref("app.support.baseURL");
   url += aHelpTopic;
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -93,16 +93,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 
 #ifdef NIGHTLY_BUILD
 XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX",
                                   "resource:///modules/SignInToWebsite.jsm");
 #endif
 
+XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
+                                  "resource:///modules/ContentSearch.jsm");
+
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // Seconds of idle before trying to create a bookmarks backup.
 const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 10 * 60;
 // Minimum interval between backups.  We try to not create more than one backup
 // per interval.
 const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -492,16 +495,17 @@ BrowserGlue.prototype = {
     PdfJs.init();
 #ifdef NIGHTLY_BUILD
     ShumwayUtils.init();
 #endif
     webrtcUI.init();
     AboutHome.init();
     SessionStore.init();
     BrowserUITelemetry.init();
+    ContentSearch.init();
 
     if (Services.appinfo.browserTabsRemote) {
       ContentClick.init();
       RemotePrompt.init();
     }
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
   },
--- a/browser/components/search/test/browser_bing_behavior.js
+++ b/browser/components/search/test/browser_bing_behavior.js
@@ -61,16 +61,60 @@ function test() {
         sb.value = "foo";
         registerCleanupFunction(function () {
           sb.value = "";
         });
         EventUtils.synthesizeKey("VK_RETURN", {});
       }
     },
     {
+      name: "new tab search",
+      searchURL: base,
+      run: function () {
+        function doSearch(doc) {
+          // Re-add the listener, and perform a search
+          gBrowser.addProgressListener(listener);
+          doc.getElementById("newtab-search-text").value = "foo";
+          doc.getElementById("newtab-search-submit").click();
+        }
+
+        // load about:newtab, but remove the listener first so it doesn't
+        // get in the way
+        gBrowser.removeProgressListener(listener);
+        gBrowser.loadURI("about:newtab");
+        info("Waiting for about:newtab load");
+        tab.linkedBrowser.addEventListener("load", function load(event) {
+          if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+              event.target.location.href == "about:blank") {
+            info("skipping spurious load event");
+            return;
+          }
+          tab.linkedBrowser.removeEventListener("load", load, true);
+
+          // Observe page setup
+          let win = gBrowser.contentWindow;
+          if (win.gSearch.currentEngineName ==
+              Services.search.currentEngine.name) {
+            doSearch(win.document);
+          }
+          else {
+            info("Waiting for newtab search init");
+            win.addEventListener("ContentSearchService", function done(event) {
+              info("Got newtab search event " + event.detail.type);
+              if (event.detail.type == "State") {
+                win.removeEventListener("ContentSearchService", done);
+                // Let gSearch respond to the event before continuing.
+                executeSoon(() => doSearch(win.document));
+              }
+            });
+          }
+        }, true);
+      }
+    },
+    {
       name: "home page search",
       searchURL: base + "&form=MOZSPG",
       run: function () {
         // Bug 992270: Ignore uncaught about:home exceptions (related to snippets from IndexedDB)
         ignoreAllUncaughtExceptions(true);
 
         // load about:home, but remove the listener first so it doesn't
         // get in the way
--- a/browser/components/search/test/browser_google_behavior.js
+++ b/browser/components/search/test/browser_google_behavior.js
@@ -91,16 +91,60 @@ function test() {
         sb.value = "foo";
         registerCleanupFunction(function () {
           sb.value = "";
         });
         EventUtils.synthesizeKey("VK_RETURN", {});
       }
     },
     {
+      name: "new tab search",
+      searchURL: base,
+      run: function () {
+        function doSearch(doc) {
+          // Re-add the listener, and perform a search
+          gBrowser.addProgressListener(listener);
+          doc.getElementById("newtab-search-text").value = "foo";
+          doc.getElementById("newtab-search-submit").click();
+        }
+
+        // load about:newtab, but remove the listener first so it doesn't
+        // get in the way
+        gBrowser.removeProgressListener(listener);
+        gBrowser.loadURI("about:newtab");
+        info("Waiting for about:newtab load");
+        tab.linkedBrowser.addEventListener("load", function load(event) {
+          if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+              event.target.location.href == "about:blank") {
+            info("skipping spurious load event");
+            return;
+          }
+          tab.linkedBrowser.removeEventListener("load", load, true);
+
+          // Observe page setup
+          let win = gBrowser.contentWindow;
+          if (win.gSearch.currentEngineName ==
+              Services.search.currentEngine.name) {
+            doSearch(win.document);
+          }
+          else {
+            info("Waiting for newtab search init");
+            win.addEventListener("ContentSearchService", function done(event) {
+              info("Got newtab search event " + event.detail.type);
+              if (event.detail.type == "State") {
+                win.removeEventListener("ContentSearchService", done);
+                // Let gSearch respond to the event before continuing.
+                executeSoon(() => doSearch(win.document));
+              }
+            });
+          }
+        }, true);
+      }
+    },
+    {
       name: "home page search",
       searchURL: base + "&channel=np&source=hp",
       run: function () {
         // Bug 992270: Ignore uncaught about:home exceptions (related to snippets from IndexedDB)
         ignoreAllUncaughtExceptions(true);
 
         // load about:home, but remove the listener first so it doesn't
         // get in the way
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -4,18 +4,16 @@
 /**
  * Bug 978019: Setting a breakpoint on the last line of a Debugger.Script and
  * reloading should still hit the breakpoint.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_breakpoints-break-on-last-line-of-script-on-reload.html";
 const CODE_URL = EXAMPLE_URL + "code_breakpoints-break-on-last-line-of-script-on-reload.js";
 
-const { promiseInvoke } = require("devtools/async-utils");
-
 function test() {
   let gPanel, gDebugger, gThreadClient, gEvents;
 
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
@@ -54,25 +52,25 @@ function test() {
         // Refresh and hit the debugger statement again.
         yield promise.all([
           reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
           waitForCaretAndScopes(gPanel, 1)
         ]);
 
         // And we should hit the breakpoints as we resume.
         yield promise.all([
-          doResume(),
+          doResume(gPanel),
           waitForCaretAndScopes(gPanel, 3)
         ]);
         yield promise.all([
-          doResume(),
+          doResume(gPanel),
           waitForCaretAndScopes(gPanel, 4)
         ]);
         yield promise.all([
-          doResume(),
+          doResume(gPanel),
           waitForCaretAndScopes(gPanel, 5)
         ]);
 
         // Clean up the breakpoints.
         yield promise.all([
           rdpInvoke(bp1, bp1.remove),
           rdpInvoke(bp2, bp1.remove),
           rdpInvoke(bp3, bp1.remove),
@@ -85,33 +83,16 @@ function test() {
           "browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js",
           e
         );
         ok(false);
       }
     });
   });
 
-  function rdpInvoke(obj, method) {
-    return promiseInvoke(obj, method)
-      .then(({error, message }) => {
-        if (error) {
-          throw new Error(error + ": " + message);
-        }
-      });
-  }
-
-  function doResume() {
-    return rdpInvoke(gThreadClient, gThreadClient.resume);
-  }
-
-  function doInterrupt() {
-    return rdpInvoke(gThreadClient, gThreadClient.interrupt);
-  }
-
   function setBreakpoint(location) {
     let deferred = promise.defer();
     gThreadClient.setBreakpoint(location, ({ error, message }, bpClient) => {
       if (error) {
         deferred.reject(error + ": " + message);
       }
       deferred.resolve(bpClient);
     });
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
@@ -1,81 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Test that pretty printing when the debugger is paused
- * does not switch away from the selected source.
+ * Test that pretty printing when the debugger is paused does not switch away
+ * from the selected source.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_pretty-print-on-paused.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
-let gSources;
+let gTab, gDebuggee, gPanel, gDebugger, gThreadClient, gSources;
 
-let gSecondSourceLabel = "code_ugly-2.js";
+const SECOND_SOURCE_VALUE = EXAMPLE_URL + "code_ugly-2.js";
 
 function test(){
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
+    gThreadClient = gDebugger.gThreadClient;
     gSources = gDebugger.DebuggerView.Sources;
 
-    gPanel.addBreakpoint({ url: gSources.values[0], line: 6 });
+    Task.spawn(function* () {
+      try {
+        yield ensureSourceIs(gPanel, "code_script-switching-02.js", true);
+
+        yield doInterrupt(gPanel);
+        yield rdpInvoke(gThreadClient, gThreadClient.setBreakpoint, {
+          url: gSources.selectedValue,
+          line: 6
+        });
+        yield doResume(gPanel);
+
+        const bpHit = waitForCaretAndScopes(gPanel, 6);
+        // Get the debuggee call off this tick so that we aren't accidentally
+        // blocking the yielding of bpHit which causes a deadlock.
+        executeSoon(() => gDebuggee.secondCall());
+        yield bpHit;
 
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
-      .then(testPaused)
-      .then(() => {
-        // Switch to the second source.
-        let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
-        gSources.selectedIndex = 1;
-        return finished;
-      })
-      .then(testSecondSourceIsSelected)
-      .then(() => {
-        const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
-        clickPrettyPrintButton();
-        testProgressBarShown();
-        return finished;
-      })
-      .then(testSecondSourceIsStillSelected)
-      .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
-      })
+        info("Switch to the second source.");
+        const sourceShown = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
+        gSources.selectedValue = SECOND_SOURCE_VALUE;
+        yield sourceShown;
 
-    gDebuggee.secondCall();
+        info("Pretty print the source.");
+        const prettyPrinted = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
+        gDebugger.document.getElementById("pretty-print").click();
+        yield prettyPrinted;
+
+        yield resumeDebuggerThenCloseAndFinish(gPanel);
+      } catch (e) {
+        DevToolsUtils.reportException("browser_dbg_pretty-print-on-paused.js", e);
+        ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+      }
+    });
   });
 }
 
-function testPaused() {
-  is(gDebugger.gThreadClient.paused, true,
-    "The thread should be paused");
-}
-
-function testSecondSourceIsSelected() {
-  ok(gSources.containsValue(EXAMPLE_URL + gSecondSourceLabel),
-    "The second source should be selected.");
-}
-
-function clickPrettyPrintButton() {
-  gDebugger.document.getElementById("pretty-print").click();
-}
-
-function testProgressBarShown() {
-  const deck = gDebugger.document.getElementById("editor-deck");
-  is(deck.selectedIndex, 2, "The progress bar should be shown");
-}
-
-function testSecondSourceIsStillSelected() {
-  ok(gSources.containsValue(EXAMPLE_URL + gSecondSourceLabel),
-    "The second source should still be selected.");
-}
-
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
+  gThreadClient = null;
   gSources = null;
 });
--- 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", {});
+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(() => {
   gDevTools.testing = false;
@@ -851,8 +852,28 @@ function attachAddonActorForUrl(aClient,
   getAddonActorForUrl(aClient, aUrl).then(aGrip => {
     aClient.attachAddon(aGrip.actor, aResponse => {
       deferred.resolve([aGrip, aResponse]);
     });
   });
 
   return deferred.promise;
 }
+
+function rdpInvoke(aClient, aMethod, ...args) {
+  return promiseInvoke(aClient, aMethod, ...args)
+    .then(({error, message }) => {
+      if (error) {
+        throw new Error(error + ": " + message);
+      }
+    });
+}
+
+function doResume(aPanel) {
+  const threadClient = aPanel.panelWin.gThreadClient;
+  return rdpInvoke(threadClient, threadClient.resume);
+}
+
+function doInterrupt(aPanel) {
+  const threadClient = aPanel.panelWin.gThreadClient;
+  return rdpInvoke(threadClient, threadClient.interrupt);
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/eyedropper/commands.js
@@ -0,0 +1,50 @@
+/* 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/. */
+
+const gcli = require("gcli/index");
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const eventEmitter = new EventEmitter();
+
+let { Eyedropper, EyedropperManager } = require("devtools/eyedropper/eyedropper");
+
+/**
+ * 'eyedropper' command
+ */
+exports.items = [{
+  name: "eyedropper",
+  description: gcli.lookup("eyedropperDesc"),
+  manual: gcli.lookup("eyedropperManual"),
+  buttonId: "command-button-eyedropper",
+  buttonClass: "command-button command-button-invertable",
+  tooltipText: gcli.lookup("eyedropperTooltip"),
+  state: {
+    isChecked: function(target) {
+      let chromeWindow = target.tab.ownerDocument.defaultView;
+      let dropper = EyedropperManager.getInstance(chromeWindow);
+      if (dropper) {
+        return true;
+      }
+      return false;
+    },
+    onChange: function(target, changeHandler) {
+      eventEmitter.on("changed", changeHandler);
+    },
+    offChange: function(target, changeHandler) {
+      eventEmitter.off("changed", changeHandler);
+    },
+  },
+  exec: function(args, context) {
+    let chromeWindow = context.environment.chromeWindow;
+    let target = context.environment.target;
+
+    let dropper = EyedropperManager.createInstance(chromeWindow);
+    dropper.open();
+
+    eventEmitter.emit("changed", target.tab);
+
+    dropper.once("destroy", () => {
+      eventEmitter.emit("changed", target.tab);
+    });
+  }
+}];
--- a/browser/devtools/eyedropper/eyedropper.js
+++ b/browser/devtools/eyedropper/eyedropper.js
@@ -44,16 +44,50 @@ const FORMAT_PREF = "devtools.defaultCol
 const CANVAS_WIDTH = 96;
 const CANVAS_OFFSET = 3; // equals the border width of the canvas.
 const CLOSE_DELAY = 750;
 
 const HEX_BOX_WIDTH = CANVAS_WIDTH + CANVAS_OFFSET * 2;
 const HSL_BOX_WIDTH = 158;
 
 /**
+ * Manage instances of eyedroppers for windows. Registering here isn't
+ * necessary for creating an eyedropper, but can be used for testing.
+ */
+let EyedropperManager = {
+  _instances: new WeakMap(),
+
+  getInstance: function(chromeWindow) {
+    return this._instances.get(chromeWindow);
+  },
+
+  createInstance: function(chromeWindow) {
+    let dropper = this.getInstance(chromeWindow);
+    if (dropper) {
+      return dropper;
+    }
+
+    dropper = new Eyedropper(chromeWindow);
+    this._instances.set(chromeWindow, dropper);
+
+    dropper.on("destroy", () => {
+      this.deleteInstance(chromeWindow);
+    });
+
+    return dropper;
+  },
+
+  deleteInstance: function(chromeWindow) {
+    this._instances.delete(chromeWindow);
+  }
+}
+
+exports.EyedropperManager = EyedropperManager;
+
+/**
  * Eyedropper widget. Once opened, shows zoomed area above current pixel and
  * displays the color value of the center pixel. Clicking on the window will
  * close the widget and fire a 'select' event. If 'copyOnSelect' is true, the color
  * will also be copied to the clipboard.
  *
  * let eyedropper = new Eyedropper(window);
  * eyedropper.open();
  *
--- a/browser/devtools/eyedropper/moz.build
+++ b/browser/devtools/eyedropper/moz.build
@@ -2,12 +2,13 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JS_MODULES_PATH = 'modules/devtools/eyedropper'
 
 EXTRA_JS_MODULES += [
+    'commands.js',
     'eyedropper.js'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/eyedropper/test/browser.ini
+++ b/browser/devtools/eyedropper/test/browser.ini
@@ -2,8 +2,9 @@
 skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   color-block.html
   head.js
 
 [browser_eyedropper_basic.js]
 skip-if = os == "win" && debug # bug 963492
+[browser_eyedropper_cmd.js]
--- a/browser/devtools/eyedropper/test/browser_eyedropper_basic.js
+++ b/browser/devtools/eyedropper/test/browser_eyedropper_basic.js
@@ -57,19 +57,8 @@ function inspectPage(dropper, click=true
       EventUtils.synthesizeMouse(target, 30, 30, {}, win);
     }
   });
 }
 
 function pressESC() {
   EventUtils.synthesizeKey("VK_ESCAPE", { });
 }
-
-function dropperLoaded(dropper) {
-  if (dropper.loaded) {
-    return promise.resolve();
-  }
-
-  let deferred = promise.defer();
-  dropper.once("load", deferred.resolve);
-
-  return deferred.promise;
-}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/eyedropper/test/browser_eyedropper_cmd.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the eyedropper command works
+
+const TESTCASE_URI = TEST_BASE + "color-block.html";
+const DIV_COLOR = "#0000FF";
+
+function test() {
+  return Task.spawn(spawnTest).then(finish, helpers.handleError);
+}
+
+function spawnTest() {
+  let options = yield helpers.openTab(TESTCASE_URI);
+  yield helpers.openToolbar(options);
+
+  yield helpers.audit(options, [
+    {
+      setup: "eyedropper",
+      check: {
+        input: "eyedropper"
+      },
+      exec: { output: "" }
+    },
+  ]);
+
+  yield inspectAndWaitForCopy();
+
+  yield helpers.closeToolbar(options);
+  yield helpers.closeTab(options);
+}
+
+function inspectAndWaitForCopy() {
+  let deferred = promise.defer();
+
+  waitForClipboard(DIV_COLOR, () => {
+    inspectPage(); // setup: inspect the page
+  }, deferred.resolve, deferred.reject);
+
+  return deferred.promise;
+}
+
+function inspectPage() {
+  let target = content.document.getElementById("test");
+  let win = content.window;
+
+  EventUtils.synthesizeMouse(target, 20, 20, { type: "mousemove" }, win);
+
+  let dropper = EyedropperManager.getInstance(window);
+
+  return dropperLoaded(dropper).then(() => {
+    EventUtils.synthesizeMouse(target, 30, 30, { type: "mousemove" }, win);
+
+    EventUtils.synthesizeMouse(target, 30, 30, {}, win);
+  });
+}
--- a/browser/devtools/eyedropper/test/head.js
+++ b/browser/devtools/eyedropper/test/head.js
@@ -1,18 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/eyedropper/test/";
 const TEST_HOST = 'mochi.test:8888';
 
-const promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
-const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
-const { Eyedropper } = require("devtools/eyedropper/eyedropper");
+let { devtools } = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
+const { Eyedropper, EyedropperManager } = devtools.require("devtools/eyedropper/eyedropper");
 
+let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
 waitForExplicitFinish();
 
 function cleanup()
 {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
@@ -30,8 +31,15 @@ function addTab(uri) {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     deferred.resolve(tab);
   }, true);
 
   content.location = uri;
 
   return deferred.promise;
 }
+
+function dropperLoaded(dropper) {
+  if (dropper.loaded) {
+    return promise.resolve();
+  }
+  return dropper.once("load");
+}
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -568,17 +568,18 @@ Toolbox.prototype = {
     // White-list buttons that can be toggled to prevent adding prefs for
     // addons that have manually inserted toolbarbuttons into DOM.
     return [
       "command-button-pick",
       "command-button-splitconsole",
       "command-button-responsive",
       "command-button-paintflashing",
       "command-button-tilt",
-      "command-button-scratchpad"
+      "command-button-scratchpad",
+      "command-button-eyedropper"
     ].map(id => {
       let button = this.doc.getElementById(id);
       // Some buttons may not exist inside of Browser Toolbox
       if (!button) {
         return false;
       }
       return {
         id: id,
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -123,16 +123,17 @@ Tools.inspector = {
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/inspector/inspector.xul",
   label: l10n("inspector.label", inspectorStrings),
   tooltip: l10n("inspector.tooltip", inspectorStrings),
   inMenu: true,
   commands: [
     "devtools/resize-commands",
     "devtools/inspector/inspector-commands",
+    "devtools/eyedropper/commands.js"
   ],
 
   preventClosingOnKey: true,
   onkey: function(panel) {
     panel.toolbox.highlighterUtils.togglePicker();
   },
 
   isTargetSupported: function(target) {
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -688,17 +688,17 @@ Tooltip.prototype = {
    */
   setColorPickerContent: function(color) {
     let def = promise.defer();
 
     // Create an iframe to contain spectrum
     let iframe = this.doc.createElementNS(XHTML_NS, "iframe");
     iframe.setAttribute("transparent", true);
     iframe.setAttribute("width", "210");
-    iframe.setAttribute("height", "220");
+    iframe.setAttribute("height", "216");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("class", "devtools-tooltip-iframe");
 
     let panel = this.panel;
     let xulWin = this.doc.ownerGlobal;
 
     // Wait for the load to initialize spectrum
     function onLoad() {
--- a/browser/devtools/shared/widgets/spectrum-frame.xhtml
+++ b/browser/devtools/shared/widgets/spectrum-frame.xhtml
@@ -14,11 +14,11 @@
     body {
       margin: 0;
       padding: 0;
     }
   </style>
 </head>
 <body role="application">
   <div id="spectrum"></div>
-  <button id="eyedropper-button"></button>
+  <div id="eyedropper-button"></div>
 </body>
 </html>
\ No newline at end of file
--- a/browser/devtools/shared/widgets/spectrum.css
+++ b/browser/devtools/shared/widgets/spectrum.css
@@ -1,24 +1,47 @@
 /* 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/. */
 
 #eyedropper-button {
-  background: url("chrome://browser/skin/devtools/eyedropper-black.png") no-repeat center;
-  width: 20px;
-  height: 20px;
-  -moz-margin-start: 6px;
-  border: 1px solid #ccc;
+  background-image: url("chrome://browser/skin/devtools/command-eyedropper.png");
+  width: 16px;
+  height: 16px;
+  background-size: 64px 16px;
+  background-position: 0 center;
+  background-repeat: no-repeat;
+  -moz-margin-start: 5px;
   border-radius: 2px;
   cursor: pointer;
 }
 
+.theme-light #eyedropper-button {
+  filter: url(chrome://browser/skin/devtools/filters.svg#invert);
+  border: 1px solid #AAA;
+}
+
 .theme-dark #eyedropper-button {
-  filter: url(chrome://browser/skin/devtools/filters.svg#colorpicker-invert);
+  border: 1px solid #444;
+}
+
+#eyedropper-button:hover {
+  background-position: -16px center;
+}
+#eyedropper-button:hover:active {
+  background-position: -32px center;
+}
+#eyedropper-button[checked=true] {
+  background-position: -48px center;
+}
+
+@media (min-resolution: 2dppx) {
+  #eyedropper-button {
+    background-image: url("chrome://browser/skin/devtools/command-eyedropper@2x.png");
+  }
 }
 
 /* Mix-in classes */
 
 .spectrum-checker {
   background-color: #eee;
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -425,16 +425,40 @@ FunctionEnd
     WriteRegStr SHCTX "$0\.xht"   "" "FirefoxHTML"
   ${EndIf}
 
   ReadRegStr $6 SHCTX "$0\.xhtml" ""
   ${If} "$6" != "FirefoxHTML"
     WriteRegStr SHCTX "$0\.xhtml" "" "FirefoxHTML"
   ${EndIf}
 
+  ; Only add .oga if it's not present
+  ${CheckIfRegistryKeyExists} "$0" ".oga" $7
+  ${If} $7 == "false"
+    WriteRegStr SHCTX "$0\.oga"  "" "FirefoxHTML"
+  ${EndIf}
+
+  ; Only add .ogg if it's not present
+  ${CheckIfRegistryKeyExists} "$0" ".ogg" $7
+  ${If} $7 == "false"
+    WriteRegStr SHCTX "$0\.ogg"  "" "FirefoxHTML"
+  ${EndIf}
+
+  ; Only add .ogv if it's not present
+  ${CheckIfRegistryKeyExists} "$0" ".ogv" $7
+  ${If} $7 == "false"
+    WriteRegStr SHCTX "$0\.ogv"  "" "FirefoxHTML"
+  ${EndIf}
+
+  ; Only add .pdf if it's not present
+  ${CheckIfRegistryKeyExists} "$0" ".pdf" $7
+  ${If} $7 == "false"
+    WriteRegStr SHCTX "$0\.pdf"  "" "FirefoxHTML"
+  ${EndIf}
+
   ; Only add webm if it's not present
   ${CheckIfRegistryKeyExists} "$0" ".webm" $7
   ${If} $7 == "false"
     WriteRegStr SHCTX "$0\.webm"  "" "FirefoxHTML"
   ${EndIf}
 
   ; An empty string is used for the 5th param because FirefoxHTML is not a
   ; protocol handler
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -319,16 +319,20 @@ Section "Uninstall"
   ; Don't clean up the file handlers if the FirefoxHTML key still exists since
   ; there should be a second installation that may be the default file handler
   ${If} ${Errors}
     ${un.RegCleanFileHandler}  ".htm"   "FirefoxHTML"
     ${un.RegCleanFileHandler}  ".html"  "FirefoxHTML"
     ${un.RegCleanFileHandler}  ".shtml" "FirefoxHTML"
     ${un.RegCleanFileHandler}  ".xht"   "FirefoxHTML"
     ${un.RegCleanFileHandler}  ".xhtml" "FirefoxHTML"
+    ${un.RegCleanFileHandler}  ".oga"  "FirefoxHTML"
+    ${un.RegCleanFileHandler}  ".ogg"  "FirefoxHTML"
+    ${un.RegCleanFileHandler}  ".ogv"  "FirefoxHTML"
+    ${un.RegCleanFileHandler}  ".pdf"  "FirefoxHTML"
     ${un.RegCleanFileHandler}  ".webm"  "FirefoxHTML"
   ${EndIf}
 
   SetShellVarContext all  ; Set SHCTX to HKLM
   ${un.GetSecondInstallPath} "Software\Mozilla" $R9
   ${If} $R9 == "false"
     SetShellVarContext current  ; Set SHCTX to HKCU
     ${un.GetSecondInstallPath} "Software\Mozilla" $R9
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -160,16 +160,30 @@ inspectManual=Investigate the dimensions
 # when the user is using this command.
 inspectNodeDesc=CSS selector
 
 # LOCALIZATION NOTE (inspectNodeManual) A fuller description of the 'node'
 # parameter to the 'inspect' command, displayed when the user asks for help
 # on what it does.
 inspectNodeManual=A CSS selector for use with document.querySelector which identifies a single element
 
+# LOCALIZATION NOTE (eyedropperDesc) A very short description of the 'eyedropper'
+# command. See eyedropperManual for a fuller description of what it does. This
+# string is designed to be shown in a menu alongside the command name, which
+# is why it should be as short as possible.
+eyedropperDesc=Grab a color from the page
+
+# LOCALIZATION NOTE (eyedropperManual) A fuller description of the 'eyedropper'
+# command, displayed when the user asks for help on what it does.
+eyedropperManual=Open a panel that magnifies an area of page to inspect pixels and copy color values
+
+# LOCALIZATION NOTE (eyedropperTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles the Eyedropper tool.
+eyedropperTooltip=Grab a color from the page
+
 # LOCALIZATION NOTE (tiltDesc) A very short description of the 'tilt'
 # command. See tiltManual for a fuller description of what it does. This
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 tiltDesc=Visualize the webpage in 3D
 
 # LOCALIZATION NOTE (tiltManual) A fuller description of the 'tilt'
 # command, displayed when the user asks for help on what it does.
new file mode 100644
--- /dev/null
+++ b/browser/modules/ContentSearch.jsm
@@ -0,0 +1,149 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+  "ContentSearch",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const INBOUND_MESSAGE = "ContentSearch";
+const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
+
+/**
+ * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
+ * named OUTBOUND_MESSAGE.  The data of each message is expected to look like
+ * { type, data }.  type is the message's type (or subtype if you consider the
+ * type of the message itself to be INBOUND_MESSAGE), and data is data that is
+ * specific to the type.
+ *
+ * Inbound messages have the following types:
+ *
+ *   GetState
+ *      Retrieves the current search engine state.
+ *      data: null
+ *   ManageEngines
+ *      Opens the search engine management window.
+ *      data: null
+ *   Search
+ *      Performs a search.
+ *      data: an object { engineName, searchString, whence }
+ *   SetCurrentEngine
+ *      Sets the current engine.
+ *      data: the name of the engine
+ *
+ * Outbound messages have the following types:
+ *
+ *   CurrentEngine
+ *     Sent when the current engine changes.
+ *     data: see _currentEngineObj
+ *   State
+ *     Sent in reply to GetState and when the state changes.
+ *     data: see _currentStateObj
+ */
+
+this.ContentSearch = {
+
+  init: function () {
+    Cc["@mozilla.org/globalmessagemanager;1"].
+      getService(Ci.nsIMessageListenerManager).
+      addMessageListener(INBOUND_MESSAGE, this);
+    Services.obs.addObserver(this, "browser-search-engine-modified", false);
+  },
+
+  receiveMessage: function (msg) {
+    let methodName = "on" + msg.data.type;
+    if (methodName in this) {
+      this[methodName](msg, msg.data.data);
+    }
+  },
+
+  onGetState: function (msg, data) {
+    this._reply(msg, "State", this._currentStateObj());
+  },
+
+  onSearch: function (msg, data) {
+    let expectedDataProps = [
+      "engineName",
+      "searchString",
+      "whence",
+    ];
+    for (let prop of expectedDataProps) {
+      if (!(prop in data)) {
+        Cu.reportError("Message data missing required property: " + prop);
+        return;
+      }
+    }
+    let browserWin = msg.target.ownerDocument.defaultView;
+    let engine = Services.search.getEngineByName(data.engineName);
+    browserWin.BrowserSearch.recordSearchInHealthReport(engine, data.whence);
+    let submission = engine.getSubmission(data.searchString, "", data.whence);
+    browserWin.loadURI(submission.uri.spec, null, submission.postData);
+  },
+
+  onSetCurrentEngine: function (msg, data) {
+    Services.search.currentEngine = Services.search.getEngineByName(data);
+  },
+
+  onManageEngines: function (msg, data) {
+    let browserWin = msg.target.ownerDocument.defaultView;
+    browserWin.BrowserSearch.searchBar.openManager(null);
+  },
+
+  observe: function (subj, topic, data) {
+    switch (topic) {
+    case "browser-search-engine-modified":
+      if (data == "engine-current") {
+        this._broadcast("CurrentEngine", this._currentEngineObj());
+      }
+      else if (data != "engine-default") {
+        // engine-default is always sent with engine-current and isn't otherwise
+        // relevant to content searches.
+        this._broadcast("State", this._currentStateObj());
+      }
+      break;
+    }
+  },
+
+  _reply: function (msg, type, data) {
+    msg.target.messageManager.sendAsyncMessage(...this._msgArgs(type, data));
+  },
+
+  _broadcast: function (type, data) {
+    Cc["@mozilla.org/globalmessagemanager;1"].
+      getService(Ci.nsIMessageListenerManager).
+      broadcastAsyncMessage(...this._msgArgs(type, data));
+  },
+
+  _msgArgs: function (type, data) {
+    return [OUTBOUND_MESSAGE, {
+      type: type,
+      data: data,
+    }];
+  },
+
+  _currentStateObj: function () {
+    return {
+      engines: Services.search.getVisibleEngines().map(engine => {
+        return {
+          name: engine.name,
+          iconURI: engine.getIconURLBySize(16, 16),
+        };
+      }),
+      currentEngine: this._currentEngineObj(),
+    };
+  },
+
+  _currentEngineObj: function () {
+    return {
+      name: Services.search.currentEngine.name,
+      logoURI: Services.search.currentEngine.getIconURLBySize(65, 26),
+      logo2xURI: Services.search.currentEngine.getIconURLBySize(130, 52),
+    };
+  },
+};
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -6,16 +6,17 @@
 
 TEST_DIRS += ['test']
 
 EXTRA_JS_MODULES += [
     'BrowserNewTabPreloader.jsm',
     'BrowserUITelemetry.jsm',
     'ContentClick.jsm',
     'ContentLinkHandler.jsm',
+    'ContentSearch.jsm',
     'CustomizationTabPreloader.jsm',
     'Feeds.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'RemotePrompt.jsm',
     'SharedFrame.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -1,15 +1,17 @@
 [DEFAULT]
 support-files =
   head.js
+  contentSearch.js
+  image.png
   uitour.*
-  image.png
 
 [browser_BrowserUITelemetry_buckets.js]
+[browser_ContentSearch.js]
 [browser_NetworkPrioritizer.js]
 skip-if = e10s # Bug 666804 - Support NetworkPrioritizer in e10s
 [browser_SignInToWebsite.js]
 skip-if = e10s # Bug 941426 - SignIntoWebsite.jsm not e10s friendly
 [browser_UITour.js]
 skip-if = os == "linux" || e10s # Intermittent failures, bug 951965
 [browser_UITour2.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -0,0 +1,240 @@
+/* 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/. */
+
+const TEST_MSG = "ContentSearchTest";
+const CONTENT_SEARCH_MSG = "ContentSearch";
+const TEST_CONTENT_SCRIPT_BASENAME = "contentSearch.js";
+
+function generatorTest() {
+  // nextStep() drives the iterator returned by this function.  This function's
+  // iterator in turn drives the iterator of each test below.
+  let currentTestIter = yield startNextTest();
+  let arg = undefined;
+  while (currentTestIter) {
+    try {
+      currentTestIter.send(arg);
+      arg = yield null;
+    }
+    catch (err if err instanceof StopIteration) {
+      currentTestIter = yield startNextTest();
+      arg = undefined;
+    }
+  }
+}
+
+function startNextTest() {
+  if (!gTests.length) {
+    setTimeout(() => nextStep(null), 0);
+    return;
+  }
+  let nextTestGen = gTests.shift();
+  let nextTestIter = nextTestGen();
+  addTab(() => {
+    info("Starting test " + nextTestGen.name);
+    nextStep(nextTestIter);
+  });
+}
+
+function addTest(testGen) {
+  gTests.push(testGen);
+}
+
+var gTests = [];
+var gMsgMan;
+
+addTest(function GetState() {
+  gMsgMan.sendAsyncMessage(TEST_MSG, {
+    type: "GetState",
+  });
+  let msg = yield waitForTestMsg("State");
+  checkMsg(msg, {
+    type: "State",
+    data: currentStateObj(),
+  });
+});
+
+addTest(function SetCurrentEngine() {
+  let newCurrentEngine = null;
+  let oldCurrentEngine = Services.search.currentEngine;
+  let engines = Services.search.getVisibleEngines();
+  for (let engine of engines) {
+    if (engine != oldCurrentEngine) {
+      newCurrentEngine = engine;
+      break;
+    }
+  }
+  if (!newCurrentEngine) {
+    info("Couldn't find a non-selected search engine, " +
+         "skipping this part of the test");
+    return;
+  }
+  gMsgMan.sendAsyncMessage(TEST_MSG, {
+    type: "SetCurrentEngine",
+    data: newCurrentEngine.name,
+  });
+  Services.obs.addObserver(function obs(subj, topic, data) {
+    info("Test observed " + data);
+    if (data == "engine-current") {
+      ok(true, "Test observed engine-current");
+      Services.obs.removeObserver(obs, "browser-search-engine-modified", false);
+      nextStep();
+    }
+  }, "browser-search-engine-modified", false);
+  info("Waiting for test to observe engine-current...");
+  waitForTestMsg("CurrentEngine");
+  let maybeMsg1 = yield null;
+  let maybeMsg2 = yield null;
+  let msg = maybeMsg1 || maybeMsg2;
+  ok(!!msg,
+     "Sanity check: One of the yields is for waitForTestMsg and should have " +
+     "therefore produced a message object");
+  checkMsg(msg, {
+    type: "CurrentEngine",
+    data: currentEngineObj(newCurrentEngine),
+  });
+
+  Services.search.currentEngine = oldCurrentEngine;
+  let msg = yield waitForTestMsg("CurrentEngine");
+  checkMsg(msg, {
+    type: "CurrentEngine",
+    data: currentEngineObj(oldCurrentEngine),
+  });
+});
+
+addTest(function ManageEngines() {
+  gMsgMan.sendAsyncMessage(TEST_MSG, {
+    type: "ManageEngines",
+  });
+  let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                   getService(Ci.nsIWindowWatcher);
+  winWatcher.registerNotification(function onOpen(subj, topic, data) {
+    if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+      subj.addEventListener("load", function onLoad() {
+        subj.removeEventListener("load", onLoad);
+        if (subj.document.documentURI ==
+            "chrome://browser/content/search/engineManager.xul") {
+          winWatcher.unregisterNotification(onOpen);
+          ok(true, "Observed search manager window open");
+          is(subj.opener, window,
+             "Search engine manager opener should be this chrome window");
+          subj.close();
+          nextStep();
+        }
+      });
+    }
+  });
+  info("Waiting for search engine manager window to open...");
+  yield null;
+});
+
+addTest(function modifyEngine() {
+  let engine = Services.search.currentEngine;
+  let oldAlias = engine.alias;
+  engine.alias = "ContentSearchTest";
+  let msg = yield waitForTestMsg("State");
+  checkMsg(msg, {
+    type: "State",
+    data: currentStateObj(),
+  });
+  engine.alias = oldAlias;
+  msg = yield waitForTestMsg("State");
+  checkMsg(msg, {
+    type: "State",
+    data: currentStateObj(),
+  });
+});
+
+addTest(function search() {
+  let engine = Services.search.currentEngine;
+  let data = {
+    engineName: engine.name,
+    searchString: "ContentSearchTest",
+    whence: "ContentSearchTest",
+  };
+  gMsgMan.sendAsyncMessage(TEST_MSG, {
+    type: "Search",
+    data: data,
+  });
+  let submissionURL =
+    engine.getSubmission(data.searchString, "", data.whence).uri.spec;
+  let listener = {
+    onStateChange: function (webProg, req, flags, status) {
+      let url = req.originalURI.spec;
+      info("onStateChange " + url);
+      let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+                     Ci.nsIWebProgressListener.STATE_START;
+      if ((flags & docStart) && webProg.isTopLevel && url == submissionURL) {
+        gBrowser.removeProgressListener(listener);
+        ok(true, "Search URL loaded");
+        req.cancel(Components.results.NS_ERROR_FAILURE);
+        nextStep();
+      }
+    }
+  };
+  gBrowser.addProgressListener(listener);
+  info("Waiting for search URL to load: " + submissionURL);
+  yield null;
+});
+
+function checkMsg(actualMsg, expectedMsgData) {
+  SimpleTest.isDeeply(actualMsg.data, expectedMsgData, "Checking message");
+}
+
+function waitForMsg(name, type, callback) {
+  info("Waiting for " + name + " message " + type + "...");
+  gMsgMan.addMessageListener(name, function onMsg(msg) {
+    info("Received " + name + " message " + msg.data.type + "\n");
+    if (msg.data.type == type) {
+      gMsgMan.removeMessageListener(name, onMsg);
+      (callback || nextStep)(msg);
+    }
+  });
+}
+
+function waitForTestMsg(type, callback) {
+  waitForMsg(TEST_MSG, type, callback);
+}
+
+function addTab(onLoad) {
+  let tab = gBrowser.addTab();
+  gBrowser.selectedTab = tab;
+  tab.linkedBrowser.addEventListener("load", function load() {
+    tab.removeEventListener("load", load, true);
+    let url = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
+    gMsgMan = tab.linkedBrowser.messageManager;
+    gMsgMan.sendAsyncMessage(CONTENT_SEARCH_MSG, {
+      type: "AddToWhitelist",
+      data: ["about:blank"],
+    });
+    waitForMsg(CONTENT_SEARCH_MSG, "AddToWhitelistAck", () => {
+      gMsgMan.loadFrameScript(url, false);
+      onLoad();
+    });
+  }, true);
+  registerCleanupFunction(() => gBrowser.removeTab(tab));
+}
+
+function currentStateObj() {
+  return {
+    engines: Services.search.getVisibleEngines().map(engine => {
+      return {
+        name: engine.name,
+        iconURI: engine.getIconURLBySize(16, 16),
+      };
+    }),
+    currentEngine: currentEngineObj(),
+  };
+}
+
+function currentEngineObj(expectedCurrentEngine) {
+  if (expectedCurrentEngine) {
+    is(Services.search.currentEngine.name, expectedCurrentEngine.name,
+       "Sanity check: expected current engine");
+  }
+  return {
+    name: Services.search.currentEngine.name,
+    logoURI: Services.search.currentEngine.getIconURLBySize(65, 26),
+    logo2xURI: Services.search.currentEngine.getIconURLBySize(130, 52),
+  };
+}
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/contentSearch.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+const TEST_MSG = "ContentSearchTest";
+const SERVICE_EVENT_TYPE = "ContentSearchService";
+const CLIENT_EVENT_TYPE = "ContentSearchClient";
+
+// Forward events from the in-content service to the test.
+content.addEventListener(SERVICE_EVENT_TYPE, event => {
+  sendAsyncMessage(TEST_MSG, event.detail);
+});
+
+// Forward messages from the test to the in-content service.
+addMessageListener(TEST_MSG, msg => {
+  content.dispatchEvent(
+    new content.CustomEvent(CLIENT_EVENT_TYPE, {
+      detail: msg.data,
+    })
+  );
+});
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -194,16 +194,18 @@ browser.jar:
   skin/classic/browser/devtools/command-scratchpad.png        (../shared/devtools/images/command-scratchpad.png)
   skin/classic/browser/devtools/command-scratchpad@2x.png     (../shared/devtools/images/command-scratchpad@2x.png)
   skin/classic/browser/devtools/command-tilt.png              (../shared/devtools/images/command-tilt.png)
   skin/classic/browser/devtools/command-tilt@2x.png           (../shared/devtools/images/command-tilt@2x.png)
   skin/classic/browser/devtools/command-pick.png              (../shared/devtools/images/command-pick.png)
   skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
   skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
   skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
+  skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
+  skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
 * skin/classic/browser/devtools/ruleview.css          (../shared/devtools/ruleview.css)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (devtools/editor-error.png)
@@ -292,17 +294,16 @@ browser.jar:
   skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
   skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
   skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
   skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
   skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
   skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
   skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
   skin/classic/browser/devtools/app-manager/default-app-icon.png      (../shared/devtools/app-manager/images/default-app-icon.png)
-  skin/classic/browser/devtools/eyedropper-black.png        (../shared/devtools/images/eyedropper-black.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-horizontalbar.png
   skin/classic/browser/sync-mobileIcon.png
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -315,16 +315,18 @@ browser.jar:
   skin/classic/browser/devtools/command-scratchpad.png        (../shared/devtools/images/command-scratchpad.png)
   skin/classic/browser/devtools/command-scratchpad@2x.png     (../shared/devtools/images/command-scratchpad@2x.png)
   skin/classic/browser/devtools/command-tilt.png              (../shared/devtools/images/command-tilt.png)
   skin/classic/browser/devtools/command-tilt@2x.png           (../shared/devtools/images/command-tilt@2x.png)
   skin/classic/browser/devtools/command-pick.png              (../shared/devtools/images/command-pick.png)
   skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
   skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
   skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
+  skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
+  skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
   skin/classic/browser/devtools/alerticon-warning.png       (devtools/alerticon-warning.png)
 * skin/classic/browser/devtools/ruleview.css                (../shared/devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (devtools/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (devtools/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png    (devtools/editor-debug-location.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
@@ -413,17 +415,16 @@ browser.jar:
   skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
   skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
   skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
   skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
   skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
   skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
   skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
   skin/classic/browser/devtools/app-manager/default-app-icon.png      (../shared/devtools/app-manager/images/default-app-icon.png)
-  skin/classic/browser/devtools/eyedropper-black.png        (../shared/devtools/images/eyedropper-black.png)
 
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-horizontalbar.png
--- a/browser/themes/shared/devtools/filters.svg
+++ b/browser/themes/shared/devtools/filters.svg
@@ -1,16 +1,9 @@
 <svg height="0" xmlns="http://www.w3.org/2000/svg">
 <filter id="invert" x="0%" y="0%" width="100%" height="100%" >
   <feComponentTransfer>
     <feFuncR type="table" tableValues=".1 0"/>
     <feFuncG type="table" tableValues=".1 0"/>
     <feFuncB type="table" tableValues=".1 0"/>
   </feComponentTransfer>
 </filter>
-<filter id="colorpicker-invert" x="0%" y="0%" width="100%" height="100%" >
-  <feComponentTransfer>
-    <feFuncR type="table" tableValues=".6 0"/>
-    <feFuncG type="table" tableValues=".6 0"/>
-    <feFuncB type="table" tableValues=".6 0"/>
-  </feComponentTransfer>
-</filter>
 </svg>
\ No newline at end of file
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a96b8eb8070221d28536098888cd3e0092a544d3
GIT binary patch
literal 1049
zc$@(l1m^pRP)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn000BvNkl<ZSPAWw
z&udgy6vywm^JeBvCNTt)+9sx;T^0WT2O=eP)udE6siYKJ3Ki-f;HJ_af^pSlid_hb
zGz*h7LM@aM5f?4#(v3(rjs+8SQf%nt{eJJBe(#KL#)-*$FSFGPGw<E|&gY){J?Gqe
zg?QCo?g&s8`OHl9dVQ}n%&!D-Z`mxIA)=>QSZ$+u%d+;7QM`!veh%}~eSK8h4gb^;
zBN3~gG1~x%+}Kc=*0S<g03BXh9g$4n#emF5V}Bffv%#kZz_$Ivio_QH3&gyq;S}P_
zrNXCK7T`F+Q5NEB7Hr}<pcyz_2Y)0Bzix%cz|F$8gx}yZ=Fw|qV>k<6`Dyi-Wbh<P
z%rQ@oR;Gp<ypc2svSr$i|0S`ITkO0o{cXG8du%V5h2va@0g>I&-=R}u?qxe#!skw2
z2x|wVV|aYN*6VSwgaGCQ{@RUO{8nLYoA`}rRY=6}S!058mH#I0r$4C*CoLY?|Ig>}
zR65tRK;`^}w-%504Ux-DWaVM^BCk}7-!T9>`<4@&SAyo5)Y*0r$m{75Ptq2kv+uaU
zWfc4mi2o<g1;JguVLnXbb@qiFEo0waAjL;v9|yr6DvD|vue0yUU>V#00<&uKEbRSa
ze~aHX0I_%``lYXwzfB>JvSJNkNM6GMsFD^Ci)WHQmQCY^VEGondy<5bf-jypK~WMP
zi^tZR9qKjal$38Hkaxf+7BmC@iF_;`tDXJx!{IqW1YR_FRN%})l7B6J)AqlsJHfbK
zq-D92sDzojce&75m>x`MT0G8;YnB^+pv`PA{AknhuVTE2U-J-6;I)sM6|Ssrh97IQ
zCsuU2W{0<`Sl7k|HxAJRTxju?#nqL{!dio`EIvG4SzNm{ez|HMnfv<%zT+0a0vly-
zNcyc<G=8_e=v`bPV-M~d=MEhrKgqHF2EJMLQZAGVxnEt^KOPBsuWaT|A#14zXPkLV
zb&2fUyy{UWiFnE?<t7ghxoQ66%oB-A(kRTc@1ImeQ)b<py#3jMZTVIJw0Or0&vV38
zmi(_I8pPP^5AGMf%`Dyvui(VLheiLRB7Or`&nT|D&$EgrJGzOzg;M3mBxn@;L5QEo
zD&CXfO)z(GW<17WJ&J8}uvqsdb`q}x+PVg9H$00S)|GfYk?jWG!(mtAH4pLEyA`i_
zPX^bq*LNjebL~TrcyBkE*ON3G?dvA<F^%hPq?^pgJf0)F$$ZSU0)WyjqwL<nwXr8_
z*~gPKpWKHn^FB!$$XfQDJoRJmPUEf{%v$yxJd1vm{WNZl!L+jP;9hlJ@(BD7yK5*4
T>WA9`00000NkvXXu0mjfj^_vF
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..edfe4c175398676e59bd27738f80e059a33db185
GIT binary patch
literal 2066
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
deleted file mode 100644
index 907a135e4d1f4580e893347478dc205d3aeaffde..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -575,16 +575,19 @@
 #command-button-pick > image {
   background-image: url("chrome://browser/skin/devtools/command-pick.png");
 }
 
 #command-button-splitconsole > image {
   background-image: url("chrome://browser/skin/devtools/command-console.png");
 }
 
+#command-button-eyedropper > image {
+  background-image: url("chrome://browser/skin/devtools/command-eyedropper.png");
+}
 
 @media (min-resolution: 2dppx) {
   #command-button-paintflashing > image {
     background-image: url("chrome://browser/skin/devtools/command-paintflashing@2x.png");
   }
 
   #command-button-responsive > image {
     background-image: url("chrome://browser/skin/devtools/command-responsivemode@2x.png");
@@ -600,16 +603,20 @@
 
   #command-button-pick > image {
     background-image: url("chrome://browser/skin/devtools/command-pick@2x.png");
   }
 
   #command-button-splitconsole > image {
     background-image: url("chrome://browser/skin/devtools/command-console@2x.png");
   }
+
+  #command-button-eyedropper > image {
+    background-image: url("chrome://browser/skin/devtools/command-eyedropper@2x.png");
+  }
 }
 
 /* Tabs */
 
 .devtools-tabbar {
   -moz-appearance: none;
   min-height: 28px;
   border: 0px solid;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -234,16 +234,18 @@ browser.jar:
         skin/classic/browser/devtools/command-scratchpad.png        (../shared/devtools/images/command-scratchpad.png)
         skin/classic/browser/devtools/command-scratchpad@2x.png     (../shared/devtools/images/command-scratchpad@2x.png)
         skin/classic/browser/devtools/command-tilt.png              (../shared/devtools/images/command-tilt.png)
         skin/classic/browser/devtools/command-tilt@2x.png           (../shared/devtools/images/command-tilt@2x.png)
         skin/classic/browser/devtools/command-pick.png              (../shared/devtools/images/command-pick.png)
         skin/classic/browser/devtools/command-pick@2x.png           (../shared/devtools/images/command-pick@2x.png)
         skin/classic/browser/devtools/command-console.png           (../shared/devtools/images/command-console.png)
         skin/classic/browser/devtools/command-console@2x.png        (../shared/devtools/images/command-console@2x.png)
+        skin/classic/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
+        skin/classic/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/editor-error.png              (devtools/editor-error.png)
         skin/classic/browser/devtools/editor-breakpoint.png         (devtools/editor-breakpoint.png)
         skin/classic/browser/devtools/editor-debug-location.png     (devtools/editor-debug-location.png)
 *       skin/classic/browser/devtools/webconsole.css                (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css   (devtools/webconsole_networkpanel.css)
         skin/classic/browser/devtools/webconsole.png                (devtools/webconsole.png)
         skin/classic/browser/devtools/breadcrumbs-divider@2x.png    (../shared/devtools/images/breadcrumbs-divider@2x.png)
@@ -328,17 +330,16 @@ browser.jar:
         skin/classic/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
         skin/classic/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
         skin/classic/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
         skin/classic/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
         skin/classic/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
         skin/classic/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
         skin/classic/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
         skin/classic/browser/devtools/app-manager/default-app-icon.png      (../shared/devtools/app-manager/images/default-app-icon.png)
-        skin/classic/browser/devtools/eyedropper-black.png                  (../shared/devtools/images/eyedropper-black.png)
 
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-horizontalbar.png
@@ -594,16 +595,18 @@ browser.jar:
         skin/classic/aero/browser/devtools/command-scratchpad.png    (../shared/devtools/images/command-scratchpad.png)
         skin/classic/aero/browser/devtools/command-scratchpad@2x.png (../shared/devtools/images/command-scratchpad@2x.png)
         skin/classic/aero/browser/devtools/command-tilt.png          (../shared/devtools/images/command-tilt.png)
         skin/classic/aero/browser/devtools/command-tilt@2x.png       (../shared/devtools/images/command-tilt@2x.png)
         skin/classic/aero/browser/devtools/command-pick.png          (../shared/devtools/images/command-pick.png)
         skin/classic/aero/browser/devtools/command-pick@2x.png       (../shared/devtools/images/command-pick@2x.png)
         skin/classic/aero/browser/devtools/command-console.png       (../shared/devtools/images/command-console.png)
         skin/classic/aero/browser/devtools/command-console@2x.png    (../shared/devtools/images/command-console@2x.png)
+        skin/classic/aero/browser/devtools/command-eyedropper.png        (../shared/devtools/images/command-eyedropper.png)
+        skin/classic/aero/browser/devtools/command-eyedropper@2x.png     (../shared/devtools/images/command-eyedropper@2x.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (devtools/alerticon-warning.png)
 *       skin/classic/aero/browser/devtools/ruleview.css              (../shared/devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/editor-error.png           (devtools/editor-error.png)
         skin/classic/aero/browser/devtools/editor-breakpoint.png      (devtools/editor-breakpoint.png)
         skin/classic/aero/browser/devtools/editor-debug-location.png  (devtools/editor-debug-location.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
@@ -690,18 +693,16 @@ browser.jar:
         skin/classic/aero/browser/devtools/app-manager/error.svg                 (../shared/devtools/app-manager/images/error.svg)
         skin/classic/aero/browser/devtools/app-manager/plus.svg                  (../shared/devtools/app-manager/images/plus.svg)
         skin/classic/aero/browser/devtools/app-manager/remove.svg                (../shared/devtools/app-manager/images/remove.svg)
         skin/classic/aero/browser/devtools/app-manager/add.svg                   (../shared/devtools/app-manager/images/add.svg)
         skin/classic/aero/browser/devtools/app-manager/index-icons.svg           (../shared/devtools/app-manager/images/index-icons.svg)
         skin/classic/aero/browser/devtools/app-manager/rocket.svg                (../shared/devtools/app-manager/images/rocket.svg)
         skin/classic/aero/browser/devtools/app-manager/noise.png                 (../shared/devtools/app-manager/images/noise.png)
         skin/classic/aero/browser/devtools/app-manager/default-app-icon.png      (../shared/devtools/app-manager/images/default-app-icon.png)
-        skin/classic/aero/browser/devtools/eyedropper-black.png        (../shared/devtools/images/eyedropper-black.png)
-
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-horizontalbar.png
         skin/classic/aero/browser/sync-horizontalbar-XPVista7.png
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -2480,18 +2480,16 @@ XULDocument::PrepareToWalk()
         if (NS_FAILED(rv)) return rv;
         
         rv = AddElementToRefMap(root);
         if (NS_FAILED(rv)) return rv;
 
         // Block onload until we've finished building the complete
         // document content model.
         BlockOnload();
-
-        nsContentSink::NotifyDocElementCreated(this);
     }
 
     // There'd better not be anything on the context stack at this
     // point! This is the basis case for our "induction" in
     // ResumeWalk(), below, which'll assume that there's always a
     // content element on the context stack if either 1) we're in the
     // "master" document, or 2) we're in an overlay, and we've got
     // more than one prototype element (the single, root "overlay"
--- a/content/xul/document/test/chrome.ini
+++ b/content/xul/document/test/chrome.ini
@@ -1,24 +1,22 @@
 [DEFAULT]
 support-files =
   bug497875-iframe.xul
   overlay1_bug335375.xul
   overlay2_bug335375.xul
   window_bug583948.xul
   window_bug757137.xul
-  window_documentnotification.xul
 
 [test_bug199692.xul]
 [test_bug311681.xul]
 [test_bug335375.xul]
 [test_bug391002.xul]
 [test_bug403868.xul]
 [test_bug414907.xul]
 [test_bug418216.xul]
 [test_bug445177.xul]
 [test_bug449457.xul]
 [test_bug468176.xul]
 [test_bug497875.xul]
 [test_bug583948.xul]
 [test_bug640158_overlay_persist.xul]
 [test_bug757137.xul]
-[test_documentnotification.xul]
deleted file mode 100644
--- a/content/xul/document/test/test_documentnotification.xul
+++ /dev/null
@@ -1,40 +0,0 @@
-<?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"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-
-<body xmlns="http://www.w3.org/1999/xhtml">
-  <div id="content" style="display: none"/>
-</body>
-
-<script>
-SimpleTest.waitForExplicitFinish();
-
-var seenNotification = false;
-function notify(subject, topic, data) {
-  seenNotification = true;
-  is(topic, "document-element-inserted", "Should be the right notification");
-  is(subject, otherWindow.document, "Should have been notified about the right window");
-  ok(subject.documentElement, "documentElement should be defined");
-}
-
-var obs = Components.classes["@mozilla.org/observer-service;1"].
-          getService(Components.interfaces.nsIObserverService)
-obs.addObserver(notify, "document-element-inserted", false);
-
-var otherWindow = window.open("window_documentnotification.xul", "_new", "chrome");
-otherWindow.addEventListener("load", function() {
-  ok(seenNotification, "Should have seen the document-element-inserted")
-  obs.removeObserver(notify, "document-element-inserted");
-  window.close();
-  SimpleTest.waitForFocus(function() {
-    SimpleTest.finish();
-  });
-});
-</script>
-
-</window>
deleted file mode 100644
--- a/content/xul/document/test/window_documentnotification.xul
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <label value="window_documentnotification.xul"/>
-</window>
--- a/mobile/android/base/CrashReporter.java
+++ b/mobile/android/base/CrashReporter.java
@@ -131,16 +131,17 @@ public class CrashReporter extends Activ
         mExtrasStringMap = new HashMap<String, String>();
         readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
 
         // Set the flag that indicates we were stopped as expected, as
         // we will send a crash report, so it is not a silent OOM crash.
         SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
         SharedPreferences.Editor editor = prefs.edit();
         editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
+        editor.putBoolean(GeckoApp.PREFS_CRASHED, true);
         editor.commit();
 
         final CheckBox allowContactCheckBox = (CheckBox) findViewById(R.id.allow_contact);
         final CheckBox includeUrlCheckBox = (CheckBox) findViewById(R.id.include_url);
         final CheckBox sendReportCheckBox = (CheckBox) findViewById(R.id.send_report);
         final EditText commentsEditText = (EditText) findViewById(R.id.comment);
         final EditText emailEditText = (EditText) findViewById(R.id.email);
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -152,16 +152,17 @@ public abstract class GeckoApp
     public static final String ACTION_WEBAPP_PREFIX        = "org.mozilla.gecko.WEBAPP";
 
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
     public static final String PREFS_OOM_EXCEPTION         = "OOMException";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
+    public static final String PREFS_CRASHED               = "crashed";
     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
 
     public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
 
     static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
 
     // Delay before running one-time "cleanup" tasks that may be needed
@@ -1767,20 +1768,27 @@ public abstract class GeckoApp
                          .putInt(PREFS_VERSION_CODE, versionCode)
                          .commit();
                 }
             });
 
             shouldRestore = true;
         } else if (savedInstanceState != null ||
                    getSessionRestorePreference().equals("always") ||
-                   getRestartFromIntent() ||
-                   prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, false)) {
+                   getRestartFromIntent()) {
             // We're coming back from a background kill by the OS, the user
-            // has chosen to always restore, we restarted, or we crashed.
+            // has chosen to always restore, or we restarted.
+            shouldRestore = true;
+        } else if (prefs.getBoolean(GeckoApp.PREFS_CRASHED, false)) {
+            ThreadUtils.postToBackgroundThread(new Runnable() {
+                @Override
+                public void run() {
+                    prefs.edit().putBoolean(PREFS_CRASHED, false).commit();
+                }
+            });
             shouldRestore = true;
         }
 
         return shouldRestore;
     }
 
     private String getSessionRestorePreference() {
         return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
--- a/mobile/android/base/TabsAccessor.java
+++ b/mobile/android/base/TabsAccessor.java
@@ -16,16 +16,17 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 public final class TabsAccessor {
     private static final String LOGTAG = "GeckoTabsAccessor";
 
     private static final String[] CLIENTS_AVAILABILITY_PROJECTION = new String[] {
                                                                         BrowserContract.Clients.GUID
                                                                     };
 
@@ -44,16 +45,17 @@ public final class TabsAccessor {
         NAME
     };
 
     private static final String CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL";
     private static final String TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
 
     private static final String LOCAL_CLIENT_SELECTION = BrowserContract.Clients.GUID + " IS NULL";
     private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
+    private static final Pattern FILTERED_URL_PATTERN = Pattern.compile("^(about|chrome|wyciwyg|file):.*$");
 
     public static class RemoteTab {
         public String title;
         public String url;
         public String guid;
         public String name;
     }
 
@@ -144,19 +146,19 @@ public final class TabsAccessor {
      */
     private static void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
         // Reuse this for serializing individual history URLs as JSON.
         JSONArray history = new JSONArray();
         ArrayList<ContentValues> valuesToInsert = new ArrayList<ContentValues>();
 
         int position = 0;
         for (Tab tab : tabs) {
-            // Skip this tab if it has a null URL or is in private browsing mode
+            // Skip this tab if it has a null URL or is in private browsing mode, or is a filtered URL.
             String url = tab.getURL();
-            if (url == null || tab.isPrivate())
+            if (url == null || tab.isPrivate() || isFilteredURL(url))
                 continue;
 
             ContentValues values = new ContentValues();
             values.put(BrowserContract.Tabs.URL, url);
             values.put(BrowserContract.Tabs.TITLE, tab.getTitle());
             values.put(BrowserContract.Tabs.LAST_USED, tab.getLastUsed());
 
             String favicon = tab.getFaviconURL();
@@ -187,9 +189,18 @@ public final class TabsAccessor {
     }
 
     // Deletes all local tabs and replaces them with a new list of tabs.
     public static synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
         deleteLocalTabs(cr);
         insertLocalTabs(cr, tabs);
         updateLocalClient(cr);
     }
+
+    /**
+     * Matches the supplied URL string against the set of URLs to filter.
+     *
+     * @return true if the supplied URL should be skipped; false otherwise.
+     */
+    private static boolean isFilteredURL(String url) {
+        return FILTERED_URL_PATTERN.matcher(url).matches();
+    }
 }
--- a/mobile/android/base/home/PanelBackItemView.java
+++ b/mobile/android/base/home/PanelBackItemView.java
@@ -24,20 +24,25 @@ class PanelBackItemView extends LinearLa
         super(context);
 
         LayoutInflater.from(context).inflate(R.layout.panel_back_item, this);
         setOrientation(HORIZONTAL);
 
         title = (TextView) findViewById(R.id.title);
 
         final ImageView image = (ImageView) findViewById(R.id.image);
-        Picasso.with(getContext())
-               .load(backImageUrl)
-               .placeholder(R.drawable.folder_up)
-               .into(image);
+
+        if (TextUtils.isEmpty(backImageUrl)) {
+            image.setImageResource(R.drawable.folder_up);
+        } else {
+            Picasso.with(getContext())
+                   .load(backImageUrl)
+                   .placeholder(R.drawable.folder_up)
+                   .into(image);
+        }
     }
 
     public void updateFromFilter(FilterDetail filter) {
         final String backText = getResources()
             .getString(R.string.home_move_up_to_filter, filter.title);
         title.setText(backText);
     }
 }
--- a/mobile/android/base/home/PanelLayout.java
+++ b/mobile/android/base/home/PanelLayout.java
@@ -432,20 +432,18 @@ abstract class PanelLayout extends Frame
                 textView.setText(R.string.home_default_empty);
             } else {
                 textView.setText(text);
             }
 
             final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl();
             final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image);
 
-            if (imageUrl == null) {
-                Picasso.with(getContext())
-                       .load(R.drawable.icon_home_empty_firefox)
-                       .into(imageView);
+            if (TextUtils.isEmpty(imageUrl)) {
+                imageView.setImageResource(R.drawable.icon_home_empty_firefox);
             } else {
                 Picasso.with(getContext())
                        .load(imageUrl)
                        .error(R.drawable.icon_home_empty_firefox)
                        .into(imageView);
             }
 
             viewState.setEmptyView(view);
--- a/mobile/android/base/tests/JavascriptTest.java
+++ b/mobile/android/base/tests/JavascriptTest.java
@@ -32,17 +32,18 @@ public class JavascriptTest extends Base
             mActions.expectGeckoEvent(EVENT_TYPE);
         mAsserter.dumpLog("Registered listener for " + EVENT_TYPE);
 
         final String url = getAbsoluteUrl(StringHelper.ROBOCOP_JS_HARNESS_URL +
                                           "?path=" + javascriptUrl);
         mAsserter.dumpLog("Loading JavaScript test from " + url);
         loadUrl(url);
 
-        final JavascriptMessageParser testMessageParser = new JavascriptMessageParser(mAsserter);
+        final JavascriptMessageParser testMessageParser =
+                new JavascriptMessageParser(mAsserter, false);
         try {
             while (!testMessageParser.isTestFinished()) {
                 if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
                     Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
                 }
                 String data = expecter.blockForEventData();
                 if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
                     Log.v(LOGTAG, "Got event with data '" + data + "'");
--- a/mobile/android/base/tests/helpers/JavascriptBridge.java
+++ b/mobile/android/base/tests/helpers/JavascriptBridge.java
@@ -105,18 +105,20 @@ public final class JavascriptBridge {
     /* package */ static void init(final UITestContext context) {
         sActions = context.getActions();
         sAsserter = context.getAsserter();
     }
 
     public JavascriptBridge(final Object target) {
         mTarget = target;
         mMethods = target.getClass().getMethods();
-        mLogParser = new JavascriptMessageParser(sAsserter);
         mExpecter = sActions.expectGeckoEvent(EVENT_TYPE);
+        // The JS here is unrelated to a test harness, so we
+        // have our message parser end on assertion failure.
+        mLogParser = new JavascriptMessageParser(sAsserter, true);
     }
 
     /**
      * Synchronously calls a method in Javascript.
      *
      * @param method Name of the method to call
      * @param args Arguments to pass to the Javascript method; must be a list of
      *             values allowed by JSONObject.
--- a/mobile/android/base/tests/helpers/JavascriptMessageParser.java
+++ b/mobile/android/base/tests/helpers/JavascriptMessageParser.java
@@ -29,19 +29,32 @@ public final class JavascriptMessagePars
     private static final Pattern testMessagePattern =
         Pattern.compile("TEST-([A-Z\\-]+) \\| (.*?) \\| (.*)", Pattern.DOTALL);
 
     private final Assert asserter;
     // Used to help print stack traces neatly.
     private String lastTestName = "";
     // Have we seen a message saying the test is finished?
     private boolean testFinishedMessageSeen = false;
+    private final boolean endOnAssertionFailure;
 
-    public JavascriptMessageParser(final Assert asserter) {
+    /**
+     * Constructs a message parser for test result messages sent from JavaScript. When seeing an
+     * assertion failure, the message parser can use the given {@link org.mozilla.gecko.Assert}
+     * instance to immediately end the test (typically if the underlying JS framework is not able
+     * to end the test itself) or to swallow the Errors - this functionality is determined by the
+     * <code>endOnAssertionFailure</code> parameter.
+     *
+     * @param asserter The Assert instance to which test results should be passed.
+     * @param endOnAssertionFailure
+     *        true if the test should end if we see a JS assertion failure, false otherwise.
+     */
+    public JavascriptMessageParser(final Assert asserter, final boolean endOnAssertionFailure) {
         this.asserter = asserter;
+        this.endOnAssertionFailure = endOnAssertionFailure;
     }
 
     public boolean isTestFinished() {
         return testFinishedMessageSeen;
     }
 
     public void logMessage(final String str) {
         final Matcher m = testMessagePattern.matcher(str.trim());
@@ -56,18 +69,25 @@ public final class JavascriptMessagePars
                 testFinishedMessageSeen = testFinishedMessageSeen ||
                                           "exiting test".equals(message);
             } else if ("PASS".equals(type)) {
                 asserter.ok(true, name, message);
             } else if ("UNEXPECTED-FAIL".equals(type)) {
                 try {
                     asserter.ok(false, name, message);
                 } catch (AssertionFailedError e) {
-                    // Swallow this exception.  We want to see all the
-                    // Javascript failures, not die on the very first one!
+                    // Above, we call the assert, allowing it to log.
+                    // Now we can end the test, if applicable.
+                    if (this.endOnAssertionFailure) {
+                        throw e;
+                    }
+                    // Otherwise, swallow the Error. The JS framework we're
+                    // logging messages from is likely capable of ending tests
+                    // when it needs to, and we want to see all of its failures,
+                    // not just the first one!
                 }
             } else if ("KNOWN-FAIL".equals(type)) {
                 asserter.todo(false, name, message);
             } else if ("UNEXPECTED-PASS".equals(type)) {
                 asserter.todo(true, name, message);
             }
 
             lastTestName = name;
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -27,16 +27,17 @@ skip-if = android_version == "10"
 # disabled on 2.3; bug 979603
 skip-if = android_version == "10"
 [testBrowserSearchVisibility]
 [testClearPrivateData]
 # disabled on x86 and 2.3; bug 948591
 skip-if = android_version == "10" || processor == "x86"
 [testDistribution]
 [testDoorHanger]
+[testFilterOpenTab]
 # disabled on 2.3; bug 986172
 skip-if = android_version == "10"
 [testFindInPage]
 # disabled on Android 2.3; bug 975155
 skip-if = android_version == "10"
 [testFlingCorrectness]
 # disabled on x86 only; bug 927476
 skip-if = processor == "x86"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testFilterOpenTab.java
@@ -0,0 +1,125 @@
+package org.mozilla.gecko.tests;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.mozilla.gecko.PrivateTab;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.TabsAccessor;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.TabsProvider;
+
+import android.content.ContentProvider;
+import android.content.Context;
+import android.database.Cursor;
+
+/**
+ * Tests that local tabs are filtered prior to upload.
+ * - create a set of tabs and perists them through TabsAccessor.
+ * - verifies that tabs are filtered by querying.
+ */
+public class testFilterOpenTab extends ContentProviderTest {
+    private static final String[] TABS_PROJECTION_COLUMNS = new String[] {
+                                                                BrowserContract.Tabs.TITLE,
+                                                                BrowserContract.Tabs.URL,
+                                                                BrowserContract.Clients.GUID,
+                                                                BrowserContract.Clients.NAME
+                                                            };
+
+    private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
+
+    /**
+     * Factory function that makes new ContentProvider instances.
+     * <p>
+     * We want a fresh provider each test, so this should be invoked in
+     * <code>setUp</code> before each individual test.
+     */
+    protected static Callable<ContentProvider> sTabProviderCallable = new Callable<ContentProvider>() {
+        @Override
+        public ContentProvider call() {
+            return new TabsProvider();
+        }
+    };
+
+    private Cursor getTabsFromLocalClient() throws Exception {
+        return mProvider.query(BrowserContract.Tabs.CONTENT_URI,
+                               TABS_PROJECTION_COLUMNS,
+                               LOCAL_TABS_SELECTION,
+                               null,
+                               null);
+    }
+
+    private Tab createTab(int id, String url, boolean external, int parentId, String title) {
+        return new Tab((Context) getActivity(), id, url, external, parentId, title);
+    }
+
+    private Tab createPrivateTab(int id, String url, boolean external, int parentId, String title) {
+        return new PrivateTab((Context) getActivity(), id, url, external, parentId, title);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp(sTabProviderCallable, BrowserContract.TABS_AUTHORITY, "tabs.db");
+        mTests.add(new TestInsertLocalTabs());
+    }
+
+    public void testFilterOpenTab() throws Exception {
+        for (int i = 0; i < mTests.size(); i++) {
+            Runnable test = mTests.get(i);
+
+            setTestName(test.getClass().getSimpleName());
+            test.run();
+        }
+    }
+
+    private class TestInsertLocalTabs extends TestCase  {
+        @Override
+        public void test() throws Exception {
+            final String TITLE1 = "Google";
+            final String URL1 = "http://www.google.com/";
+            final String TITLE2 = "Mozilla Start Page";
+            final String URL2 = "about:home";
+            final String TITLE3 = "Chrome Weave URL";
+            final String URL3 = "chrome://weave/";
+            final String TITLE4 = "What You Cache Is What You Get";
+            final String URL4 = "wyciwyg://1/test.com";
+            final String TITLE5 = "Root Folder";
+            final String URL5 = "file:///";
+
+            // Create a list of local tabs.
+            List<Tab> tabs = new ArrayList<Tab>(6);
+            Tab tab1 = createTab(1, URL1, false, 0, TITLE1);
+            Tab tab2 = createTab(2, URL2, false, 0, TITLE2);
+            Tab tab3 = createTab(3, URL3, false, 0, TITLE3);
+            Tab tab4 = createTab(4, URL4, false, 0, TITLE4);
+            Tab tab5 = createTab(5, URL5, false, 0, TITLE5);
+            Tab tab6 = createPrivateTab(6, URL1, false, 0, TITLE1);
+            tabs.add(tab1);
+            tabs.add(tab2);
+            tabs.add(tab3);
+            tabs.add(tab4);
+            tabs.add(tab5);
+            tabs.add(tab6);
+
+            // Persist the created tabs.
+            TabsAccessor.persistLocalTabs(mResolver, tabs);
+
+            // Get the persisted tab and check if urls are filtered.
+            Cursor c = getTabsFromLocalClient();
+            assertCountIsAndClose(c, 1, 1 + " tabs entries found");
+        }
+    }
+
+    /**
+     * Assert that the provided cursor has the expected number of rows,
+     * closing the cursor afterwards.
+     */
+    private void assertCountIsAndClose(Cursor c, int expectedCount, String message) {
+        try {
+            mAsserter.is(c.getCount(), expectedCount, message);
+        } finally {
+            c.close();
+        }
+    }
+}
--- a/mobile/android/themes/core/about.css
+++ b/mobile/android/themes/core/about.css
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 html {
   background: #f0f0f0;
   padding: 0 1em;
-  font-family: "Nokia Sans", Tahoma, sans-serif !important;
+  font-family: "Clear Sans", sans-serif !important;
   font-size: 100% !important;
 }
 
 body {
   color: black;
   position: relative;
   min-width: 330px;
   max-width: 50em;
--- a/mobile/android/themes/core/aboutBase.css
+++ b/mobile/android/themes/core/aboutBase.css
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %filter substitution
 %include defines.inc
 
 html {
-  font-family: Roboto,"Droid Sans",helvetica,arial,clean,sans-serif;
+  font-family: "Clear Sans",sans-serif;
   font-size: 14px;
   background-color: @color_about_background@;
   -moz-text-size-adjust: none;
 }
 
 body {
   margin: 0;
 }
--- a/mobile/android/themes/core/aboutFeedback.css
+++ b/mobile/android/themes/core/aboutFeedback.css
@@ -121,17 +121,17 @@ section:not([active]) {
 
 #sumo-message {
   color: #444;
   -moz-padding-end: 30px;
 }
 
 .description,
 #last-url {
-  font-family: Roboto,"Droid Sans",helvetica,arial,clean,sans-serif;
+  font-family: "Clear Sans",sans-serif;
   font-size: 14px;
   margin-bottom: 10px;
   padding: 5px;
   width: -moz-calc(100% - 10px);
 }
 
 .send-feedback {
   margin-top: 10px;
--- a/mobile/android/themes/core/aboutPage.css
+++ b/mobile/android/themes/core/aboutPage.css
@@ -1,15 +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/. */
 
 body {
   -moz-text-size-adjust: none;
-  font-family: Roboto,"Droid Sans",helvetica,arial,clean,sans-serif;
+  font-family: "Clear Sans",sans-serif;
   font-size: 23px;
   color: #222222;
   background-color: #ced7de;
 }
 
 #header {
   height: 80px;
 }
--- a/mobile/android/themes/core/aboutPrivateBrowsing.css
+++ b/mobile/android/themes/core/aboutPrivateBrowsing.css
@@ -1,14 +1,14 @@
 /* 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/. */
 
 body {
-  font-family: Roboto,"Droid Sans",helvetica,arial,clean,sans-serif;
+  font-family: "Clear Sans",sans-serif;
   font-size: 14px;
 }
 
 body.normal  .showPrivate,
 body.private .showNormal {
   display: none;
 }
 
--- a/mobile/android/themes/core/aboutReader.css
+++ b/mobile/android/themes/core/aboutReader.css
@@ -316,17 +316,17 @@ body {
 }
 
 .font-size5-sample,
 .font-size5 > .content {
   font-size: 22px !important;
 }
 
 .toolbar {
-  font-family: "Droid Sans",helvetica,arial,clean,sans-serif;
+  font-family: "Clear Sans",sans-serif;
   transition-property: visibility, opacity;
   transition-duration: 0.7s;
   visibility: visible;
   opacity: 1.0;
   position: fixed;
   width: 100%;
   bottom: 0px;
   left: 0px;
@@ -460,17 +460,17 @@ body {
   line-height: 50px;
   margin-bottom: 5px;
   border-bottom: 3px solid transparent;
 }
 
 .segmented-button > li > a {
   display: block;
   padding: 5px 0;
-  font-family: "Roboto",sans-serif;
+  font-family: "Clear Sans",sans-serif;
   font-weight: lighter;
 }
 
 #font-type-buttons > li > a:active,
 #font-type-buttons > li.selected > a {
   border-color: #ff9400;
 }
 
--- a/mobile/android/themes/core/config.css
+++ b/mobile/android/themes/core/config.css
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 html,
 body {
     margin: 0;
     padding: 0;
     background-color: #ced7de;
     -moz-user-select: none;
-    font-family: "Open Sans", sans-serif;
+    font-family: "Clear Sans",sans-serif;
     -moz-text-size-adjust: none;
 }
 
 .toolbar {
     width: 100%;
     height: 3em;
     position: fixed;
     top: 0;
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -1217,16 +1217,17 @@ SearchCountMeasurementBase.prototype = O
 
     // Default to a counter.
     return Metrics.Storage.FIELD_DAILY_COUNTER;
   },
 
   SOURCES: [
     "abouthome",
     "contextmenu",
+    "newtab",
     "searchbar",
     "urlbar",
   ],
 });
 
 function SearchCountMeasurement2() {
   SearchCountMeasurementBase.call(this);
 }
--- a/services/healthreport/tests/xpcshell/test_provider_searches.js
+++ b/services/healthreport/tests/xpcshell/test_provider_searches.js
@@ -52,16 +52,17 @@ add_task(function test_record() {
   // Record searches for all but one of our defaults, and one engine that's
   // not a default.
   for (let engine of DEFAULT_ENGINES.concat([{name: "Not Default", identifier: "notdef"}])) {
     if (engine.identifier == "yahoo") {
       continue;
     }
     yield provider.recordSearch(engine, "abouthome");
     yield provider.recordSearch(engine, "contextmenu");
+    yield provider.recordSearch(engine, "newtab");
     yield provider.recordSearch(engine, "searchbar");
     yield provider.recordSearch(engine, "urlbar");
   }
 
   // Invalid sources should throw.
   let errored = false;
   try {
     yield provider.recordSearch(DEFAULT_ENGINES[0], "bad source");
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -4776,35 +4776,37 @@ AddonThreadActor.prototype = Object.crea
 update(AddonThreadActor.prototype, {
   constructor: AddonThreadActor,
 
   // A constant prefix that will be used to form the actor ID by the server.
   actorPrefix: "addonThread",
 
   onAttach: function(aRequest) {
     if (!this.attached) {
-      Services.obs.addObserver(this, "document-element-inserted", false);
+      Services.obs.addObserver(this, "chrome-document-global-created", false);
+      Services.obs.addObserver(this, "content-document-global-created", false);
     }
     return ThreadActor.prototype.onAttach.call(this, aRequest);
   },
 
   disconnect: function() {
     if (this.attached) {
-      Services.obs.removeObserver(this, "document-element-inserted");
+      Services.obs.removeObserver(this, "content-document-global-created");
+      Services.obs.removeObserver(this, "chrome-document-global-created");
     }
     return ThreadActor.prototype.disconnect.call(this);
   },
 
   /**
-   * Called when a new DOM document element is created. Check if the DOM was
-   * laoded from an add-on and if so make the window a debuggee.
+   * Called when a new DOM document global is created. Check if the DOM was
+   * loaded from an add-on and if so make the window a debuggee.
    */
   observe: function(aSubject, aTopic, aData) {
     let id = {};
-    if (mapURIToAddonID(aSubject.documentURIObject, id) && id.value === this.addonID) {
+    if (mapURIToAddonID(aSubject.location, id) && id.value === this.addonID) {
       this.dbg.addDebuggee(aSubject.defaultView);
     }
   },
 
   /**
    * Override the eligibility check for scripts and sources to make
    * sure every script and source with a URL is stored when debugging
    * add-ons.