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; + + +
@@ -46,6 +55,16 @@
+
+
+
+
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 @@
- +
\ No newline at end of file diff --git a/browser/devtools/shared/widgets/spectrum.css b/browser/devtools/shared/widgets/spectrum.css index a0b5069d21c..9b6a3e5a995 100644 --- a/browser/devtools/shared/widgets/spectrum.css +++ b/browser/devtools/shared/widgets/spectrum.css @@ -3,17 +3,40 @@ * 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 */ diff --git a/browser/installer/windows/nsis/shared.nsh b/browser/installer/windows/nsis/shared.nsh index 6de440e1f71..b6f81b4385a 100755 --- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -430,6 +430,30 @@ FunctionEnd 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" diff --git a/browser/installer/windows/nsis/uninstaller.nsi b/browser/installer/windows/nsis/uninstaller.nsi index a9ba76bfeb9..70bd88f4bf5 100755 --- a/browser/installer/windows/nsis/uninstaller.nsi +++ b/browser/installer/windows/nsis/uninstaller.nsi @@ -324,6 +324,10 @@ Section "Uninstall" ${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} diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties index 209bac09285..032f7b67952 100644 --- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties @@ -165,6 +165,20 @@ inspectNodeDesc=CSS selector # 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 diff --git a/browser/modules/ContentSearch.jsm b/browser/modules/ContentSearch.jsm new file mode 100644 index 00000000000..7cf7d02fffc --- /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), + }; + }, +}; diff --git a/browser/modules/moz.build b/browser/modules/moz.build index c7558198581..00c701809f1 100644 --- a/browser/modules/moz.build +++ b/browser/modules/moz.build @@ -11,6 +11,7 @@ EXTRA_JS_MODULES += [ 'BrowserUITelemetry.jsm', 'ContentClick.jsm', 'ContentLinkHandler.jsm', + 'ContentSearch.jsm', 'CustomizationTabPreloader.jsm', 'Feeds.jsm', 'NetworkPrioritizer.jsm', diff --git a/browser/modules/test/browser.ini b/browser/modules/test/browser.ini index ae8be5ea6dd..c79f697d035 100644 --- a/browser/modules/test/browser.ini +++ b/browser/modules/test/browser.ini @@ -1,10 +1,12 @@ [DEFAULT] support-files = head.js - uitour.* + contentSearch.js image.png + uitour.* [browser_BrowserUITelemetry_buckets.js] +[browser_ContentSearch.js] [browser_NetworkPrioritizer.js] skip-if = e10s # Bug 666804 - Support NetworkPrioritizer in e10s [browser_SignInToWebsite.js] diff --git a/browser/modules/test/browser_ContentSearch.js b/browser/modules/test/browser_ContentSearch.js new file mode 100644 index 00000000000..93eb4e09fd2 --- /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), + }; +} diff --git a/browser/modules/test/contentSearch.js b/browser/modules/test/contentSearch.js new file mode 100644 index 00000000000..3b7cc8a2288 --- /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, + }) + ); +}); diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index 7d4f6302809..7fefc0132db 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -199,6 +199,8 @@ browser.jar: 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) @@ -297,7 +299,6 @@ browser.jar: 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 diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 18a53dc4dab..a46eb881a2b 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -320,6 +320,8 @@ browser.jar: 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) @@ -418,7 +420,6 @@ browser.jar: 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 diff --git a/browser/themes/shared/devtools/filters.svg b/browser/themes/shared/devtools/filters.svg index 25fe5f0669a..9a50a6cc5bc 100644 --- a/browser/themes/shared/devtools/filters.svg +++ b/browser/themes/shared/devtools/filters.svg @@ -6,11 +6,4 @@ - - - - - - - \ No newline at end of file diff --git a/browser/themes/shared/devtools/images/command-eyedropper.png b/browser/themes/shared/devtools/images/command-eyedropper.png new file mode 100644 index 00000000000..a96b8eb8070 Binary files /dev/null and b/browser/themes/shared/devtools/images/command-eyedropper.png differ diff --git a/browser/themes/shared/devtools/images/command-eyedropper@2x.png b/browser/themes/shared/devtools/images/command-eyedropper@2x.png new file mode 100644 index 00000000000..edfe4c17539 Binary files /dev/null and b/browser/themes/shared/devtools/images/command-eyedropper@2x.png differ diff --git a/browser/themes/shared/devtools/images/eyedropper-black.png b/browser/themes/shared/devtools/images/eyedropper-black.png deleted file mode 100644 index 907a135e4d1..00000000000 Binary files a/browser/themes/shared/devtools/images/eyedropper-black.png and /dev/null differ diff --git a/browser/themes/shared/devtools/toolbars.inc.css b/browser/themes/shared/devtools/toolbars.inc.css index be2e56b1918..59bd684f8cc 100644 --- a/browser/themes/shared/devtools/toolbars.inc.css +++ b/browser/themes/shared/devtools/toolbars.inc.css @@ -580,6 +580,9 @@ 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 { @@ -605,6 +608,10 @@ #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 */ diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 12cc5f511c8..8f5ae47de4a 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -239,6 +239,8 @@ browser.jar: 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) @@ -333,7 +335,6 @@ browser.jar: 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 @@ -599,6 +600,8 @@ browser.jar: 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) @@ -695,8 +698,6 @@ browser.jar: 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 diff --git a/content/xul/document/src/XULDocument.cpp b/content/xul/document/src/XULDocument.cpp index 90a87b445d0..0e7d04b9279 100644 --- a/content/xul/document/src/XULDocument.cpp +++ b/content/xul/document/src/XULDocument.cpp @@ -2485,8 +2485,6 @@ XULDocument::PrepareToWalk() // 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 diff --git a/content/xul/document/test/chrome.ini b/content/xul/document/test/chrome.ini index f9c46774385..2c2c5f4b5c4 100644 --- a/content/xul/document/test/chrome.ini +++ b/content/xul/document/test/chrome.ini @@ -5,7 +5,6 @@ support-files = overlay2_bug335375.xul window_bug583948.xul window_bug757137.xul - window_documentnotification.xul [test_bug199692.xul] [test_bug311681.xul] @@ -21,4 +20,3 @@ support-files = [test_bug583948.xul] [test_bug640158_overlay_persist.xul] [test_bug757137.xul] -[test_documentnotification.xul] diff --git a/content/xul/document/test/test_documentnotification.xul b/content/xul/document/test/test_documentnotification.xul deleted file mode 100644 index fcd0a5c5ae6..00000000000 --- a/content/xul/document/test/test_documentnotification.xul +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - diff --git a/content/xul/document/test/window_documentnotification.xul b/content/xul/document/test/window_documentnotification.xul deleted file mode 100644 index 08d081d889b..00000000000 --- a/content/xul/document/test/window_documentnotification.xul +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/mobile/android/base/CrashReporter.java b/mobile/android/base/CrashReporter.java index d575668b4a6..ea39224f5d8 100644 --- a/mobile/android/base/CrashReporter.java +++ b/mobile/android/base/CrashReporter.java @@ -136,6 +136,7 @@ public class CrashReporter extends Activity 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); diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 058083ceab2..0eb30057c76 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -157,6 +157,7 @@ public abstract class GeckoApp 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"; @@ -1772,10 +1773,17 @@ public abstract class GeckoApp 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; } diff --git a/mobile/android/base/TabsAccessor.java b/mobile/android/base/TabsAccessor.java index a09b7b24eef..d65d4bb37af 100644 --- a/mobile/android/base/TabsAccessor.java +++ b/mobile/android/base/TabsAccessor.java @@ -21,6 +21,7 @@ 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"; @@ -49,6 +50,7 @@ public final class TabsAccessor { 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; @@ -149,9 +151,9 @@ public final class TabsAccessor { 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(); @@ -192,4 +194,13 @@ public final class TabsAccessor { 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(); + } } diff --git a/mobile/android/base/home/PanelBackItemView.java b/mobile/android/base/home/PanelBackItemView.java index ff0bc745600..1d9ffa40548 100644 --- a/mobile/android/base/home/PanelBackItemView.java +++ b/mobile/android/base/home/PanelBackItemView.java @@ -29,10 +29,15 @@ class PanelBackItemView extends LinearLayout { 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) { diff --git a/mobile/android/base/home/PanelLayout.java b/mobile/android/base/home/PanelLayout.java index 767cff570a8..d0a7ff24839 100644 --- a/mobile/android/base/home/PanelLayout.java +++ b/mobile/android/base/home/PanelLayout.java @@ -437,10 +437,8 @@ abstract class PanelLayout extends FrameLayout { 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) diff --git a/mobile/android/base/tests/JavascriptTest.java b/mobile/android/base/tests/JavascriptTest.java index d692a71ff43..77a98388414 100644 --- a/mobile/android/base/tests/JavascriptTest.java +++ b/mobile/android/base/tests/JavascriptTest.java @@ -37,7 +37,8 @@ public class JavascriptTest extends BaseTest { 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)) { diff --git a/mobile/android/base/tests/helpers/JavascriptBridge.java b/mobile/android/base/tests/helpers/JavascriptBridge.java index 6a6d9302014..aa6299e1e4a 100644 --- a/mobile/android/base/tests/helpers/JavascriptBridge.java +++ b/mobile/android/base/tests/helpers/JavascriptBridge.java @@ -110,8 +110,10 @@ public final class JavascriptBridge { 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); } /** diff --git a/mobile/android/base/tests/helpers/JavascriptMessageParser.java b/mobile/android/base/tests/helpers/JavascriptMessageParser.java index af48d4189d3..6237f1adcf1 100644 --- a/mobile/android/base/tests/helpers/JavascriptMessageParser.java +++ b/mobile/android/base/tests/helpers/JavascriptMessageParser.java @@ -34,9 +34,22 @@ public final class JavascriptMessageParser { 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 + * endOnAssertionFailure 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() { @@ -61,8 +74,15 @@ public final class JavascriptMessageParser { 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); diff --git a/mobile/android/base/tests/robocop.ini b/mobile/android/base/tests/robocop.ini index 73efd8c428d..4dd8a52fd82 100644 --- a/mobile/android/base/tests/robocop.ini +++ b/mobile/android/base/tests/robocop.ini @@ -32,6 +32,7 @@ skip-if = android_version == "10" skip-if = android_version == "10" || processor == "x86" [testDistribution] [testDoorHanger] +[testFilterOpenTab] # disabled on 2.3; bug 986172 skip-if = android_version == "10" [testFindInPage] diff --git a/mobile/android/base/tests/testFilterOpenTab.java b/mobile/android/base/tests/testFilterOpenTab.java new file mode 100644 index 00000000000..b0c1e1939f7 --- /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. + *

+ * We want a fresh provider each test, so this should be invoked in + * setUp before each individual test. + */ + protected static Callable sTabProviderCallable = new Callable() { + @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 tabs = new ArrayList(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(); + } + } +} diff --git a/mobile/android/themes/core/about.css b/mobile/android/themes/core/about.css index f3daef91262..80a8866280a 100644 --- a/mobile/android/themes/core/about.css +++ b/mobile/android/themes/core/about.css @@ -5,7 +5,7 @@ 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; } diff --git a/mobile/android/themes/core/aboutBase.css b/mobile/android/themes/core/aboutBase.css index 903b9e8c3c8..49d26fdc0c3 100644 --- a/mobile/android/themes/core/aboutBase.css +++ b/mobile/android/themes/core/aboutBase.css @@ -6,7 +6,7 @@ %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; diff --git a/mobile/android/themes/core/aboutFeedback.css b/mobile/android/themes/core/aboutFeedback.css index 2cf1cac2cec..1388f6a5ca4 100644 --- a/mobile/android/themes/core/aboutFeedback.css +++ b/mobile/android/themes/core/aboutFeedback.css @@ -126,7 +126,7 @@ section:not([active]) { .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; diff --git a/mobile/android/themes/core/aboutPage.css b/mobile/android/themes/core/aboutPage.css index 5e2bad272a5..a7fcbb91eca 100644 --- a/mobile/android/themes/core/aboutPage.css +++ b/mobile/android/themes/core/aboutPage.css @@ -4,7 +4,7 @@ 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; diff --git a/mobile/android/themes/core/aboutPrivateBrowsing.css b/mobile/android/themes/core/aboutPrivateBrowsing.css index fc667c39757..b69b2a01f75 100644 --- a/mobile/android/themes/core/aboutPrivateBrowsing.css +++ b/mobile/android/themes/core/aboutPrivateBrowsing.css @@ -3,7 +3,7 @@ * 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; } diff --git a/mobile/android/themes/core/aboutReader.css b/mobile/android/themes/core/aboutReader.css index 6d5d40309d3..1751f09d2d7 100644 --- a/mobile/android/themes/core/aboutReader.css +++ b/mobile/android/themes/core/aboutReader.css @@ -321,7 +321,7 @@ body { } .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; @@ -465,7 +465,7 @@ body { .segmented-button > li > a { display: block; padding: 5px 0; - font-family: "Roboto",sans-serif; + font-family: "Clear Sans",sans-serif; font-weight: lighter; } diff --git a/mobile/android/themes/core/config.css b/mobile/android/themes/core/config.css index f9099768ef8..5ee330af84a 100644 --- a/mobile/android/themes/core/config.css +++ b/mobile/android/themes/core/config.css @@ -8,7 +8,7 @@ body { 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; } diff --git a/services/healthreport/providers.jsm b/services/healthreport/providers.jsm index 3a8146ea2c6..5ed8fb7e278 100644 --- a/services/healthreport/providers.jsm +++ b/services/healthreport/providers.jsm @@ -1222,6 +1222,7 @@ SearchCountMeasurementBase.prototype = Object.freeze({ SOURCES: [ "abouthome", "contextmenu", + "newtab", "searchbar", "urlbar", ], diff --git a/services/healthreport/tests/xpcshell/test_provider_searches.js b/services/healthreport/tests/xpcshell/test_provider_searches.js index 94ce7976085..0cdf1bb52d0 100644 --- a/services/healthreport/tests/xpcshell/test_provider_searches.js +++ b/services/healthreport/tests/xpcshell/test_provider_searches.js @@ -57,6 +57,7 @@ add_task(function test_record() { } 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"); } diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index 2d903c0471e..bdb4933ea8a 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -4781,25 +4781,27 @@ update(AddonThreadActor.prototype, { 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); } },