diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
index 05145ddb92c..f35356c90b8 100644
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1228,7 +1228,7 @@ 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");
@@ -1239,6 +1239,7 @@ 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
diff --git a/browser/base/content/content.js b/browser/base/content/content.js
index b38aff3f3a6..9ee2e28af03 100644
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -191,6 +191,60 @@ let AboutHomeListener = {
};
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
diff --git a/browser/base/content/newtab/grid.js b/browser/base/content/newtab/grid.js
index 44029290bb7..0f2dab013ab 100644
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -197,12 +197,18 @@ let gGrid = {
}
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() {
diff --git a/browser/base/content/newtab/newTab.css b/browser/base/content/newtab/newTab.css
index 29143300d1d..e4f99fc4802 100644
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -2,6 +2,11 @@
* 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;
}
@@ -12,6 +17,7 @@ input[type=button] {
position: relative;
-moz-box-flex: 1;
-moz-user-focus: normal;
+ -moz-box-orient: vertical;
}
#newtab-scrollbox:not([page-disabled]) {
@@ -54,6 +60,7 @@ input[type=button] {
#newtab-margin-undo-container {
display: -moz-box;
-moz-box-pack: center;
+ margin-bottom: 26px; /* 32 - 6 search form top "padding" */
}
#newtab-horizontal-margin {
@@ -64,10 +71,17 @@ input[type=button] {
#newtab-margin-top,
#newtab-margin-bottom {
display: -moz-box;
- -moz-box-flex: 1;
position: relative;
}
+#newtab-margin-top {
+ -moz-box-flex: 1;
+}
+
+#newtab-margin-bottom {
+ -moz-box-flex: 2;
+}
+
.newtab-side-margin {
min-width: 16px;
-moz-box-flex: 1;
@@ -213,7 +227,7 @@ input[type=button] {
opacity: 0.01;
}
-/* PANEL */
+/* SPONSORED PANEL */
#sponsored-panel {
width: 330px;
}
@@ -225,3 +239,156 @@ input[type=button] {
#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;
+}
diff --git a/browser/base/content/newtab/newTab.js b/browser/base/content/newtab/newTab.js
index a56a391bb01..66308468df9 100644
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -41,6 +41,7 @@ function inPrivateBrowsingMode() {
}
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
@@ -54,6 +55,7 @@ const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
#include dropPreview.js
#include updater.js
#include undo.js
+#include search.js
// Everything is loaded. Initialize the New Tab Page.
gPage.init();
diff --git a/browser/base/content/newtab/newTab.xul b/browser/base/content/newtab/newTab.xul
index b736379833b..b70512d2b9d 100644
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -11,6 +11,8 @@
%newTabDTD;
+
+ %searchBarDTD;
]>
+
+
+ &cmd_engineManager.label;
+
+
+
diff --git a/browser/base/content/newtab/page.js b/browser/base/content/newtab/page.js
index 2c3bb8f26e8..d0e28652950 100644
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -111,6 +111,8 @@ let gPage = {
this._initialized = true;
+ gSearch.init();
+
this._mutationObserver = new MutationObserver(() => {
if (this.allowBackgroundCaptures) {
Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
@@ -138,6 +140,10 @@ let gPage = {
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, {
@@ -164,7 +170,7 @@ let gPage = {
*/
_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");
diff --git a/browser/base/content/newtab/search.js b/browser/base/content/newtab/search.js
new file mode 100644
index 00000000000..35f9b987de1
--- /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");
+ }
+ }
+ },
+};
diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini
index cde3ea08095..e450181cb57 100644
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -399,7 +399,6 @@ skip-if = e10s # Bug ????? - test calls gBrowser.contentWindow.stop
[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]
diff --git a/browser/base/content/test/newtab/browser.ini b/browser/base/content/test/newtab/browser.ini
index cc9ecf15d62..da3c02891a3 100644
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -1,6 +1,9 @@
[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]
@@ -25,6 +28,7 @@ skip-if = os == "mac" # Intermittent failures, bug 898317
[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]
diff --git a/browser/base/content/test/newtab/browser_newtab_focus.js b/browser/base/content/test/newtab/browser_newtab_focus.js
index e841d353739..a423e339780 100644
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -9,9 +9,9 @@ function runTests() {
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");
diff --git a/browser/base/content/test/newtab/browser_newtab_search.js b/browser/base/content/test/newtab/browser_newtab_search.js
new file mode 100644
index 00000000000..84073927150
--- /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;
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
index 6253552c75c..ed4147bb926 100644
--- a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -5,6 +5,10 @@ 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");
diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js
index 4348867496a..6699b4ab0d3 100644
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -25,10 +25,41 @@ 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);
@@ -549,3 +580,35 @@ function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
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);
+}
diff --git a/browser/base/content/test/newtab/searchEngineLogo.xml b/browser/base/content/test/newtab/searchEngineLogo.xml
new file mode 100644
index 00000000000..0a8b35f859e
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineLogo.xml
@@ -0,0 +1,7 @@
+
+
+browser_newtab_search searchEngineLogo.xml
+
+data:image/png;base64,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
+data:image/png;base64,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
+
diff --git a/browser/base/content/test/newtab/searchEngineNoLogo.xml b/browser/base/content/test/newtab/searchEngineNoLogo.xml
new file mode 100644
index 00000000000..bbff6cf8f77
--- /dev/null
+++ b/browser/base/content/test/newtab/searchEngineNoLogo.xml
@@ -0,0 +1,5 @@
+
+
+browser_newtab_search searchEngineNoLogo.xml
+
+
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index ccba3af0b68..65fe3d59840 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -599,17 +599,14 @@ function makeURLAbsolute(aBase, aUrl)
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
@@ -620,46 +617,40 @@ function makeURLAbsolute(aBase, aUrl)
* (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
diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js
index 9425820a9aa..4c73a533911 100644
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -98,6 +98,9 @@ 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";
@@ -497,6 +500,7 @@ BrowserGlue.prototype = {
AboutHome.init();
SessionStore.init();
BrowserUITelemetry.init();
+ ContentSearch.init();
if (Services.appinfo.browserTabsRemote) {
ContentClick.init();
diff --git a/browser/components/search/test/browser_bing_behavior.js b/browser/components/search/test/browser_bing_behavior.js
index ec7d050633d..0a2146d857d 100644
--- a/browser/components/search/test/browser_bing_behavior.js
+++ b/browser/components/search/test/browser_bing_behavior.js
@@ -65,6 +65,50 @@ function test() {
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",
diff --git a/browser/components/search/test/browser_google_behavior.js b/browser/components/search/test/browser_google_behavior.js
index 736c6d506b5..205cd2179f1 100644
--- a/browser/components/search/test/browser_google_behavior.js
+++ b/browser/components/search/test/browser_google_behavior.js
@@ -95,6 +95,50 @@ function test() {
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",
diff --git 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
index 57881460dd0..be6143b401c 100644
--- 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
@@ -9,8 +9,6 @@
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;
@@ -59,15 +57,15 @@ function test() {
// 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)
]);
@@ -90,23 +88,6 @@ function test() {
});
});
- 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) => {
diff --git a/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js b/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
index 2e94f10ef1e..0a63208cf35 100644
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
@@ -2,16 +2,15 @@
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]) => {
@@ -19,63 +18,50 @@ function test(){
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);
- 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));
- })
+ yield doInterrupt(gPanel);
+ yield rdpInvoke(gThreadClient, gThreadClient.setBreakpoint, {
+ url: gSources.selectedValue,
+ line: 6
+ });
+ yield doResume(gPanel);
- gDebuggee.secondCall();
+ 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;
+
+ info("Switch to the second source.");
+ const sourceShown = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE);
+ gSources.selectedValue = SECOND_SOURCE_VALUE;
+ yield sourceShown;
+
+ 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;
});
diff --git a/browser/devtools/debugger/test/head.js b/browser/devtools/debugger/test/head.js
index b8e83ea6326..ce3fd654e60 100644
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -22,6 +22,7 @@ let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxP
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;
@@ -856,3 +857,23 @@ function attachAddonActorForUrl(aClient, aUrl) {
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);
+}
+
diff --git a/browser/devtools/eyedropper/commands.js b/browser/devtools/eyedropper/commands.js
new file mode 100644
index 00000000000..cafe5fd1182
--- /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);
+ });
+ }
+}];
diff --git a/browser/devtools/eyedropper/eyedropper.js b/browser/devtools/eyedropper/eyedropper.js
index ef3b601c012..5b9b104b803 100644
--- a/browser/devtools/eyedropper/eyedropper.js
+++ b/browser/devtools/eyedropper/eyedropper.js
@@ -48,6 +48,40 @@ 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
diff --git a/browser/devtools/eyedropper/moz.build b/browser/devtools/eyedropper/moz.build
index 2c2523d00c2..8200a5e80c2 100644
--- a/browser/devtools/eyedropper/moz.build
+++ b/browser/devtools/eyedropper/moz.build
@@ -7,6 +7,7 @@
JS_MODULES_PATH = 'modules/devtools/eyedropper'
EXTRA_JS_MODULES += [
+ 'commands.js',
'eyedropper.js'
]
diff --git a/browser/devtools/eyedropper/test/browser.ini b/browser/devtools/eyedropper/test/browser.ini
index ef603410735..5db588c518f 100644
--- a/browser/devtools/eyedropper/test/browser.ini
+++ b/browser/devtools/eyedropper/test/browser.ini
@@ -7,3 +7,4 @@ support-files =
[browser_eyedropper_basic.js]
skip-if = os == "win" && debug # bug 963492
+[browser_eyedropper_cmd.js]
diff --git a/browser/devtools/eyedropper/test/browser_eyedropper_basic.js b/browser/devtools/eyedropper/test/browser_eyedropper_basic.js
index 5345bab9d86..00be45b5bbf 100644
--- a/browser/devtools/eyedropper/test/browser_eyedropper_basic.js
+++ b/browser/devtools/eyedropper/test/browser_eyedropper_basic.js
@@ -62,14 +62,3 @@ function inspectPage(dropper, click=true) {
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;
-}
diff --git a/browser/devtools/eyedropper/test/browser_eyedropper_cmd.js b/browser/devtools/eyedropper/test/browser_eyedropper_cmd.js
new file mode 100644
index 00000000000..8302172233f
--- /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);
+ });
+}
diff --git a/browser/devtools/eyedropper/test/head.js b/browser/devtools/eyedropper/test/head.js
index b735f2cbde6..ed0dd1bffab 100644
--- a/browser/devtools/eyedropper/test/head.js
+++ b/browser/devtools/eyedropper/test/head.js
@@ -4,10 +4,11 @@
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();
@@ -35,3 +36,10 @@ function addTab(uri) {
return deferred.promise;
}
+
+function dropperLoaded(dropper) {
+ if (dropper.loaded) {
+ return promise.resolve();
+ }
+ return dropper.once("load");
+}
diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js
index dd629c607d1..541f559531c 100644
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -573,7 +573,8 @@ Toolbox.prototype = {
"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
diff --git a/browser/devtools/main.js b/browser/devtools/main.js
index 8aa2fb181d1..04a55e49832 100644
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -128,6 +128,7 @@ Tools.inspector = {
commands: [
"devtools/resize-commands",
"devtools/inspector/inspector-commands",
+ "devtools/eyedropper/commands.js"
],
preventClosingOnKey: true,
diff --git a/browser/devtools/shared/widgets/Tooltip.js b/browser/devtools/shared/widgets/Tooltip.js
index f2e740d4fac..fbdfad0fa61 100644
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -693,7 +693,7 @@ Tooltip.prototype = {
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");
diff --git a/browser/devtools/shared/widgets/spectrum-frame.xhtml b/browser/devtools/shared/widgets/spectrum-frame.xhtml
index 1c26836d072..955ed99ed72 100644
--- a/browser/devtools/shared/widgets/spectrum-frame.xhtml
+++ b/browser/devtools/shared/widgets/spectrum-frame.xhtml
@@ -19,6 +19,6 @@
-
+