author | Ryan VanderMeulen <ryanvm@gmail.com> |
Thu, 16 Jul 2015 21:42:22 -0400 | |
changeset 253426 | 15155971639cfd143cc58ee8f205c3900a2b3828 |
parent 253425 | 403f2f4cd9783f19abf010c2bde33679aaabe00e |
child 253427 | 689cd81e0f3cdf7fb735deddf6f1761f81c011d4 |
push id | 62438 |
push user | ryanvm@gmail.com |
push date | Fri, 17 Jul 2015 14:27:33 +0000 |
treeherder | mozilla-inbound@ea2b6887033b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1171344 |
milestone | 42.0a1 |
backs out | 7f2ddcfe4537c52265ce168b3ab182892e7dbcc0 e88770aa21600f3afdefd06dc643eb83e8da6b84 |
first release with | nightly linux32
15155971639c
/
42.0a1
/
20150717030202
/
files
nightly linux64
15155971639c
/
42.0a1
/
20150717030202
/
files
nightly mac
15155971639c
/
42.0a1
/
20150717030202
/
files
nightly win32
15155971639c
/
42.0a1
/
20150717030202
/
files
nightly win64
15155971639c
/
42.0a1
/
20150717030202
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
42.0a1
/
20150717030202
/
pushlog to previous
nightly linux64
42.0a1
/
20150717030202
/
pushlog to previous
nightly mac
42.0a1
/
20150717030202
/
pushlog to previous
nightly win32
42.0a1
/
20150717030202
/
pushlog to previous
nightly win64
42.0a1
/
20150717030202
/
pushlog to previous
|
--- a/CLOBBER +++ b/CLOBBER @@ -17,9 +17,9 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1171344 requires a clobber due to renaming of a test file. +Bug 1178850 requires clobber for Android JNI header changes
--- a/browser/base/content/abouthome/aboutHome.css +++ b/browser/base/content/abouthome/aboutHome.css @@ -44,112 +44,130 @@ a { width: 192px; margin: 22px auto 31px; background-image: url("chrome://branding/content/about-logo.png"); background-size: 192px auto; background-position: center center; background-repeat: no-repeat; } -#searchIconAndTextContainer, +#searchForm, #snippets { width: 470px; } -#searchIconAndTextContainer { +#searchForm { + display: -moz-box; +} + +#searchLogoContainer { display: -moz-box; - height: 36px; - position: relative; + -moz-box-align: center; + padding-top: 2px; + -moz-padding-end: 8px; +} + +#searchLogoContainer[hidden] { + display: none; +} + +#searchEngineLogo { + display: inline-block; + height: 28px; + width: 70px; + min-width: 70px; } #searchIcon { - border: 1px transparent; - padding: 0; - margin: 0; - width: 36px; - height: 36px; - background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat; - position: absolute; + border: 1px solid transparent; + -moz-margin-end: 5px; + height: 38px; + width: 38px; + background: url("chrome://browser/skin/magnifier.png") center center no-repeat; + background-size: 26px; +} + +#searchIcon[active], +#searchIcon:hover { + background-color: #e9e9e9; + border-color: rgb(226, 227, 229); + border-radius: 2.5px; +} + +html[searchUIConfiguration="oldsearchui"] #searchIcon { + display: none; +} + +html:not([searchUIConfiguration="oldsearchui"]) #searchText::-moz-placeholder { + color: transparent; +} + +html:not([searchUIConfiguration="oldsearchui"]) #searchLogoContainer { + display: none; } #searchText { - margin-left: 0; -moz-box-flex: 1; - padding-top: 6px; - padding-bottom: 6px; - padding-left: 34px; - padding-right: 8px; + padding: 6px 8px; background: hsla(0,0%,100%,.9) padding-box; border: 1px solid; - border-radius: 2px 0 0 2px; 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; color: inherit; } #searchText:-moz-dir(rtl) { - border-radius: 0 2px 2px 0; + border-radius: 0 2.5px 2.5px 0; } -#searchText[aria-expanded="true"] { - border-radius: 2px 0 0 0; -} - -#searchText[aria-expanded="true"]:-moz-dir(rtl) { - border-radius: 0 2px 0 0; -} - -#searchText[keepfocus], #searchText:focus, #searchText[autofocus] { border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6); } #searchSubmit { -moz-margin-start: -1px; - background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box; - padding: 0; + 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); - border-radius: 0 2px 2px 0; -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); color: inherit; cursor: pointer; transition-property: background-color, border-color, box-shadow; transition-duration: 150ms; - width: 50px; } #searchSubmit:-moz-dir(rtl) { - border-radius: 2px 0 0 2px; + border-radius: 2.5px 0 0 2.5px; } #searchText:focus + #searchSubmit, -#searchText[keepfocus] + #searchSubmit, #searchText + #searchSubmit:hover, #searchText[autofocus] + #searchSubmit { border-color: #59b5fc #45a3e7 #3294d5; color: white; } #searchText:focus + #searchSubmit, -#searchText[keepfocus] + #searchSubmit, #searchText[autofocus] + #searchSubmit { - background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); + 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); } #searchText + #searchSubmit:hover { - background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#66bdff, #0d9eff); + 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); } #searchText + #searchSubmit:hover:active { box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset, @@ -157,29 +175,29 @@ a { transition-duration: 0ms; } #defaultSnippet1, #defaultSnippet2, #rightsSnippet { display: block; min-height: 38px; - background: 0 center no-repeat; + background: 30px center no-repeat; padding: 6px 0; - -moz-padding-start: 49px; + -moz-padding-start: 79px; } #rightsSnippet[hidden] { display: none; } #defaultSnippet1:-moz-dir(rtl), #defaultSnippet2:-moz-dir(rtl), #rightsSnippet:-moz-dir(rtl) { - background-position: right 0 center; + background-position: right 30px center; } #defaultSnippet1 { background-image: url("chrome://browser/content/abouthome/snippet1.png"); } #defaultSnippet2 { background-image: url("chrome://browser/content/abouthome/snippet2.png"); @@ -224,17 +242,17 @@ body[narrow] #launcher[session] { padding: 14px 6px; min-width: 88px; max-width: 176px; max-height: 85px; vertical-align: top; white-space: normal; background: transparent padding-box; border: 1px solid transparent; - border-radius: 2px; + border-radius: 2.5px; color: #525c66; font-size: 75%; cursor: pointer; transition-property: background-color, border-color, box-shadow; transition-duration: 150ms; } body[narrow] #launcher[session] > .launchButton { @@ -373,16 +391,20 @@ body[narrow] #restorePreviousSession::be * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics * rather than upscaling the original-size ones (bug 818940). */ @media not all and (max-resolution: 1dppx) { #brandLogo { background-image: url("chrome://branding/content/about-logo@2x.png"); } + #searchIcon { + background-image: url("chrome://browser/skin/magnifier@2x.png"); + } + #defaultSnippet1, #defaultSnippet2, #rightsSnippet { background-size: 40px; } #defaultSnippet1 { background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
--- a/browser/base/content/abouthome/aboutHome.js +++ b/browser/base/content/abouthome/aboutHome.js @@ -1,14 +1,146 @@ /* 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"; +const SEARCH_ENGINES = { + "Google": { + // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.; + // it will be scaled down as necessary on lower-dpi displays. + // This needs to be defined in a single line to keep the JS parser from creating many + // intermediate strings in memory. See bug 986672. + image: "data:image/png;base64,\ +iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\ +bWFnZVJlYWR5ccllPAAAGrFJREFUeNrtfHt4VdW172+utZOASLJ5+BaIFrUeXkFsa0Fl++gDnznV\ +VlvFxt7aqvUUarXtse3Bau35ak/rZ9XT26NtfOvV6wFET+FYCQEKWqsQIT5RCAgSXnlnrzXneNw/\ +1lphJSSQ8BB7bub3zW+LO3uN+fiNMcf4jTEX0N/6W3/rb/2tv30smtnXB3zmRi2FQakxQNKX3WkW\ +9S/tgW3HLpmQM543A0BWVSHMYGIwOTDxzxrOf3/RQQfMZ2/SLAvKhTFVBGUqKFONH2QAzwOMF38a\ +wHhYZAxWAqhe/iszp3+b970d/sInc57vz/J8L2eMB2MAEYkBQ6DQ3dRw4dq7AUjcP3rAfPZmLWXC\ +LHKoIAcQAUxaB5EaEfc6AEBhjDEwmcx43/fO9HxT4vkReBIAAZgjgodW3NcPnn1sHgD/iHknn+0d\ +6s8XEUhsXXac/34WAAGw8afuT8GZ3X055YeSJcIsG+pMZwFn0UihezRofPt3G54f/0E8cNMN+Myo\ +8jVTCgYd823PLzrPeIBnABiUQ1F+UoWsVOYb33mkoKp/7/dKyT0AGc47X4s0sjBEoLxbBqAQAMfW\ +Rfe38B4BM+VHUkYOs8mi1FrABbK4dcvK73zwp1M3xYPOxANKBqbpCdXNGb0UwPKRF74xpfDQ0t+K\ +54+IvlKoahmAhaO/mv/ZmicG3tqPgT61ZM2dZMQJOYhIdByRM/F3dCCOox4Bc3oEliqyyNoQCPPu\ +sXceKZqRsigu7pwaWBowiRb46+f9Q1V2wl1nDx09/R7jF30x9adNlN8yPx4DHwht+B/cBIBoRqeI\ +E4hE/oshTcB0wNbT6/o/zrhFyohR5ZxmrVWE+fDxdx4puhGAH4OkPe5B6pykeJAc/7cDEMZ/095Y\ +870P339m+BXs2v4kbCFsm9u2vnpJ3bzR7wAo2B/R2v+PjSnyXcRxtOLUSXFxwAFz5i2SZUIVO82S\ +BWye/vLOIwNvjL8OYqCEfXCmJAZPHkC7sK1REbj2+lmbq86qTVmmfuuyN2cTiREWKCvACgml9kDL\ +7HQksehsZmSdA6yVpsa6P38v3swg7m4vN1dGXrThKGP8yS5fP33j/LEvxKDbl2f2A0YFCtkZQDOa\ +PjLAnP4jrmBGjh1AVhG2ttxfX33++vjY2eeNXf/siLUAzgEwMJZrY2vF/Vu/t4BRqCqgCmj07wMV\ +HXUCzJQfUlZE72ICnANcqNj21h8eiK1AX46gXh29KT9H+rd9XxBjYGCgig7QHOgjPgMAKigXQZYp\ +si4uCOc3v35zY2wF9ufGSgxA7fdd9g8ho9ol4P4ojiQWnSUMMANECrJNy1NWYH8eGfsEvJbLv1IK\ +1XIAUwEtA0xplJMwjcaYlTDeShg8dOgjj6/cJxNYfWIWkHJoh5yyjkSZ8RbB89YBZq4/pXafGeuz\ +b9WciXJxo2B2houqgAjABJCLOwFMqFv57+bBxMIAJm1det3avnl1OYCLAeSgWhofaY1QXQSRuYc+\ +/OiD3QLmUzNdqTBKhRVMADsF5beuToXJB90KtFz+lVIVniXOVUAUqjpXVB4WwPjGTPB8/0zjeTnj\ +ezl43szmKy6vNkDF4MeeXNc3oJyUhfAMkJsJkSxUVrLos6o6z/O8Ucb3phrPzyHKeVTwkpPXseg3\ +Cqe+1SfG+swfaw6KGTAoJ5eyGF3IBeEIJB2AcXxb0FI/L45uFQBMGiu6Z3ai9eqrclBUClFWVatV\ +5GERNT5wEVQnQLUcIuVNX75kFjn60rA5c1d0AoywlkcxfdwZ2LSgbOmBZAv70povu7RcyFUqcZYd\ +Pbxix44fnLv8pbYUOWh+P3ZM9uJRo34xoLDgq8b3YTxvqhqsaPzyJTdmn36msjdyqPqkMhWqBFGZ\ +MtV8uDX4zMjp2zemyEoPgGn4zyOvGzy48A54GcD3Sz1jFrqqE+4uOOvdmb0ASlYEs5mQE9afUdhy\ +0yv3lHzwya/8ZcjgI0+5yssU3QKYkgQ4Ivp60LL1n8kBQfOWuvdnj6uLldgHQKoKxU7HV/eg2y1X\ +XXmXEs1U0ZVb29o//4k5c5P5eQB+s+68aVeUFBTcCxUoS6kRWfjhueecc9SfX3ytA9QTr7eVACqY\ +FDYEwnbB2qcHHg6gLY6ODhpomi77coUyVaojhKH9+ZHzF/wqXiztEg34APxNX/jCvQOLCi83fpy8\ +UsCJXHLYnGdn785S0uKTyyBUBXJZcW5x4bSN56ciyLQcD4Bf/+ThVwwbUvRb+JkoswqAWX5b9Lm1\ +M3uSM/UnUiaCKiZk2blvvnxX0ePxuBNAmpMur51wyLBPzjVeBBoVwIXBk6vuP+SG+LkcuwkWAA96\ +/JjZKnKxkACkkFb5Nztz220xX9bJlWi+6opKFalQlpqlmzZNu6B6SaJ0knKJ/DW5qd8p8TO3x6AB\ +qza1EE06cdmy9wDAY5LjmBTMkQnUnZ42H0ywNF52aU6FK4UY5NySI+cv+E3MCnMM5HyqtwFoO3rB\ +gmuDMFjGjiCOIEQwzH9c+7lzju+JTaYlJ2ehUqXMWWFqeurFxqsAFMVf25Ss9kTOEZdvebClJbxT\ +yUGZoEzwlL/b9tzRX+pOztSfSBZApSqyIrL45buKnkaUJEzLCN5+csxr+ab6fyILkI2OIZYBlx9/\ +2bYvpLgw2+EqKLKdwoceVKJp+tfuEpYKZcaW1tZbLqheEsbj3GV+oxdV3x0GwQZrHUIiWKIST3Vm\ +DG54zFrKrBBWiGgSyx9Uv6Xh0n/MKlGlOII4h80trQ+kuJt8HGklZHg6FZF/Y/uOb7O1YOvAzkGt\ +Kxmoehe6SYNEpkErwZIFC4I2fuLKf2tLtDOPzumPhA6wAPJDLt1yuzjaAEcAMUCMApXfvPP7IcO6\ +gkYFs4RRpgy49qanUsAPu/T8W48e/YwL6S/kYtBYwM8U/yu6KVlQUShr9CkKyK7b1vDVy0qVeaYy\ +gaxbdeK85/8a/z7sYR3zgXM1gXUInEPoCEw8PR6z8YQxaidQPh6RrgrPEOZS4chKjFuydEEKFD1x\ +QgrAnfO3V98Jw/B5dhFgmByU+MK/nnrq6K6gcQtPyqlIubJAibCxPv/fsVVNgCI9yGEAQdBq71NH\ +UEdQIoBo5PBBeklazuQfSpYFM0UAFsDmd2yMf9+1XkUT3otc8AiRwpFChCBCI0detGbSLtYr5uw6\ +tk26XctZwgxhRt65ZSmr1t389M1Jk85wzKcHRAiJkCfasDnI/0sMGN+jlLMrAigMhp0+f+TBBIw4\ +milEYOcQBHZZAoZeEIgKgIIgeJbD2MqEFhxaDAFmdAWMisxQFigzlAUnX9e4rA9yeHuTna3koBQB\ +RogxwOPvxNbQAAA7VHQEFKSQKEFIu4lA5d3HiiuFNB4XQZlhUHBK11QO0oRdD7ouROVCkeJZG7ak\ +/KBOYHlz4sTy1WVlVY5oYego2+bs82+3tFw6YcVrp01dteqpxNfyhKQuGlxCMSsKBh570ABT/8XP\ +5dhRVpyDWAd2Ns0O9yrhWdfcMpvCEByEoNCCwhBgvgBdM+PM5TH5FPW+1ZLo8de2viehe12dhVoH\ +OAtDPO61O4o+kYCTnE5wVuGsxlzKHul7BUDKdomKgwpB2QHAyNiP2Dl+0Z2WRXZ9YP0F55WJczvX\ +0jp09U3fLiurWD1+/NqQaHZIVNbu3O1vt7aM+fSqVRWXvPvu0pRldwAkQ5brjO+NMh0kgMIvGjYZ\ +wIKETPxIrYt1U5M8iThKJil9yZGc++ab298dP36Jb8wZohqhQHRErKEeAA6fG5FT5yIlYYI6tzfO\ +vtiQni3MYDw0ChqEgUMyejyAdwGwDeW4ZI9FAGQOmwzgv/cERmZbDXhnKBNUGMJkUhGVduSSJJ1P\ +6rw8HIalJo7ilBkchgCgL48fVzLceDc4kZnWUdap1AQi10x+660n4jXyk1M7ZXEZgHhMUkMO4Njp\ +hQGMf8h56Fx++ZE1a+1xZC2Szjs3sk9uUEhUbSMvP3LeyOGZ0tKJiearo1J1DHVRPYmS7JUcG2g1\ +pxxUsooBnpmQWAOb10YbKGygcKFCZOC0XqxrRKokCBQG5euX77In2k1P+2hhWEZBAAoCuCCEcW7E\ +2xMn/m6oYo0jyjnmuc3Off6UN96YMvmtt5LILSmQ61r3xAA0I+xqPBiIejAd1f7e2MPPfvm4LQs/\ +89a+bP6nZuSzfsaU+T7g+UBixYQVRFGS01kFO22srRy0EgA4CEvFRHS3MANMY/fGbybmlQqAFSBV\ +sCp8kWwCGA5dqefFShnnRV77ecHYU37iXuqLoB0tsuIo34v3NfJR1GlJsrnOuiXGy1y8k+rwxh57\ +3srSD/6rbLdra7yMqgjUCGAULR8uWr0LJPYAGApCeCbKNygLPKIxJ65YOSU+YpLUUCYGiqBzQVy3\ +Ft1zbevnJl60UARqACgcVDo9ZZr63Mqua68QxlpmrWJC1FmrmLSKCFVktcpZrbKhzg4D26E5Lgjg\ +8vnoMwwh1hU/dvTRo/qcDyJqcESw5Dp6o3XNHVrqLDSubAdFjuXwwWZcX+Wc9APboKxQUoiLurXa\ +IYfCpjlCDsoxZ6OCouLRt+xpbY3nA8aDMR6E2+9vffOWxl02cQ+Bbdjevt7l83D5ABRaKNHYO484\ +YmgMkoJ4jElCOL8Lz9NN87YumrRDxc2DElQZKgIVhZcZcO1hZ74wtK/H0thvtuXGXdM2S0S/ziQ1\ +FPJiG7pHwvbgDhtKnQ0VNhCEeUHQLmiuf2fymieGvJGY8DCfX+yCEC5xWIlwtO+P6+s4VESJGS4+\ +liwxKjZ/2FGRZvPhYgktxEZdHWOAr2P34ihWIQWTgJ2CnWJbo9Ymz1g/5+h1QsF9wgKJ19Z4hV87\ +4fKNE3cnx8v4V8H4UOjqhvce+zW6qdWVlOvSjQsDlw/WUT4A5QNQGIJDizMPHXR+CiRBb4GSzlYr\ +26Z7vYKSC42nUOPBqA9VU1I0ZOJPEYWj1NvVW/3AoEUAFgO4IzZ1hYk2jf9WUw7IjCIXHUVhXrFp\ +/sQtKZPIoXXr/PjoSkZeoHo6gP/bFyeciECqcHG3IrXp37a2SF3xQNPxRAXgq5nS1bHsDWCYALYA\ +u+h0W/impI8Pad9ec/vAoWVTjV84Nsn5FAwcvmDMN5rOqf1jyatdHzjuGjvThloKYH3b5qVXt775\ +44ZuN1QEKknF3a6ImfDee4tWjBrV6R5Qoeq1AP6Avaxx8gDolhdPXAh2qzQmZFQ4ZhALrj/mvLpT\ ++qhxya0BP5VVZQBkA6jNR0AJ2xUUcjKGjsx4k3PVYUwaJU6rJ3reLiHlHppjBjF3fLYSzU/noEZ8\ +3611VusoVJBVsFWAdezim/3jemSFe+SNIsvCpAhCXf7TBZI+PnTr4nO2t2xcME3ZroYKIouEEqDo\ +xfHfav/GxOttFgBOucGWll0XVqrqXYDWNLz3aG7bsovWp4i2TvkhScLqNBezq/M/zxLBxV2Yx/75\ +yCPP6usc04CJ+B3bcLMwQTiK+0UIwgz1ip8+4pyaYX0x0SnWMkjnYGygkm9nBO0MGzoI2TTDyQBw\ +7ubNawPmeZYZNt5wZhrxX8OHX9yXSTJzGcVgIWasbs8/hc7XRzXM670cg0Vs5H+MHm6u74ucrb/K\ +lAlFPoySoqFFn+rm+OCGV762df2cYWe4fP0M5qDWhoowRIm1/h+s1YZx3wrVOV1LDhXMaGzfXntF\ +46vXtMQRS/clsqRRT9SNd0GMBo6edRStZbKeg4D//ciQIcP2CTDbqsdVKQePq1JMFkXxv4qO9AaM\ +fPGoaeuG9kXp0LkU0wGgMFC1gYAdAeyg0m3IrE3W3mtTvodjRpHq9X3xL4h5Qsq63P/z9ra6LqSc\ +vvmBPkwOTex2lnf4wNee/47fa99NGGVJ8Zl1qP3UPfwkdr15mDDV+Y3Pf+Kh9c9kz9pee89J7dve\ +vaRt+7qLbVv47y5UUKggp3BB/okNz0/aHI8332OaIgELxWDpptQtt6X+Qcu03nVYGQYxjxzl+7/e\ +GyvjdYrCtv31JiW7QTjy6qWj83jF4AeP/MLaodiHRtZBXAihEEIWkq4eSgGmvKGhqpX5d1YEVhiW\ +BaI6Zf6QITN7s5ELhw4tZZavkwhIZMOC1rZfo5s64nPv4+1NzXot2/hYiqKckglH4/7eRojCOosp\ +St6u2ijfS1Hv3I0SdVy5aam9ecumBeOqN8w7aRkxSlMVdRDmRHa4m5xWPKPEusUA6maIrcy/cCKw\ +InASKaCoXrlo2LAH+xpMpAEjLauu2ObaNnxVmZqUHaI8SaR+KnIhTPHCo6ZtOn6vk4qUPNNGnV2P\ +J0ptENweMq92zHBMcMwwIrfMLS6etKdJEnMlCYOZm9YE4dUPkWvsIUckJ/+SZwd5PCEOEBc5rh7j\ +grqf+VfvSc7mO/xZSihVAra3YMY/PqqrUhZVe7C8yRHTBqAVQJuQN5idgJ2ASQAz4PJjptWevKc0\ +RZQ0TQATRWDd/dmFDQ2VeaLH0z4dRVTK9EXZ7IqFJSXH7W6eLw0blntp2NAydGOSqPGVs/5mW9Zc\ +JGKbRSxELIRDCFuIuAmiBa8eMW37rcdc1JDtM+3PYdSp43k9/ulPgmDrsnz+vFBktRWBZYEVKSlU\ +feH5wYPP7u5Hfy4uzi4oLq50IjkSaXrf2vIfBPnV6PlKiwKg0XfyNe2BPkmJ8+oUGeh/bLjNu7En\ +0Gy+w5sppLcyKRra9IZJ98hTvciop9MPSSFUwGTnEjHICsgpyKHYHzjquWMvrJ+wewUENPFjCIAx\ +k3uStyIMbw5FVieWJvJpBE5kgqq+X1VcPGdRcfHMxSUluSUlJbmlUZ+1tKRkLRGVnrZ9Rw12rSLt\ +sDpFg8vmfbpw0HH3wcuMMSaiao2XAbwMjPFhPL/ReN6DfsY8tHHekN0WXR929vqsCpWruFshPEqF\ +o3IyADuWTxgea1rYTbRVeEMmc+SnCwp+OcB4l3kmLq0D4BnzkA/MMUBjvDMXC1DBqlkCFr9N9E//\ +HIZpPyDsQVuTFwsMfP273k8GFeLbvo9izwe8DGA8VMPgIc/D2piALlPFDGWUMqNuazOun/RbeQU7\ +L/zl0cfC+SPOXjG84NBRawCvJNoSE7PiBgr5Xx/MKf7jLnzIbUPKlHVF5C11KgJfD9+shY8Vxjd3\ +0780rEvP8bFDDvnVQGO+lU5MeTDwzM5aTbOzNyrw/XNbWx9JFLknk+sjqjobUHJq9XS/cNj3jZcZ\ +Ac9PwBIDyAeMD2O8RhhvpTFYqYpGqMQOM2UhlFOhsvjfgNJ6ofxyoZaXbHPt8mDNjDU9ACYBbyGA\ +AT/KZEZ/MpO5qciYyRlgROeJGSh0nQCL21Ufmx4EL8dMpqScRt4DFVAAYMCtORx+0Rhz7aFF+GJB\ +BmNM/JKklGo1KlBtHZ474U79P9hZOZcQYb0unD/mwu05qADCZwE4C8Y7I3kTk4kFx+mUuzfMKf5e\ ++rn+rUMq4PR4hFII0gw0xpdvGAWGoDqHf9m8IuV8m2Qtf1pQMPok37+50JhpHlC8EzwRcAzwOqs+\ +Vkv06I+da04nInd3RvuxgCIAhcUTF5zvFQ79oucP+Cy8zIjE6qQnt5Pviu5IqAogVKNCNSrBUte6\ +blnrqi/Vo3O9rI3Pc7cbP6sgGQcAf7rvl3zK908uBKjAGK5jrrmNKKHj/RS3E6L3V2USLUzkZAB4\ +i75pTivwwQMyoKYQ685+QOtScvzUHPbIlJ54ZVsuDPTrZDmnQqUQggo1qkoNRDyFeJ6XGQfjF0fW\ +3O9YWxW6adNzw36Dzm/JKEJ0k7QgtfiSygd1vSrkdZ3jlb6fneT7Y+MN1xrmVX9gbkw9q1MdsemF\ +U5wkpwqSRSw49gfZAcPPHOsVlIww/sBjjPEVnqfGZEQlWKVCjWK31TW/dv56pCruU126TGxPl+US\ +IrAgNQ7TQ+pNukQqfalLNimApvMt6CZMTvsiu3VOJ17XnrNWZ9m85oK8Qmz4sFB+CeXrF29dfOqG\ +1PwKs6fOKyvKjrnb8wrHGD8TWfCOEoX85zb96dgXY9leN2NM+y3SJZG4u7XsSldIykFPz09NHxbR\ +T2U3M11AsKf8aRqtnBqQoG91oWkGOS0/XaQo2Pf3u5mUDK9LukD7Mv5Tv9teSQ4VzipsINUtW9Zc\ +t/mFiRu7WbcOuQNP+MXQ4hGX3mEKBl1mjB9bbwAqSz6cf+TZ8Qaabta/u6hM92ItpZs5dvyor5R/\ +dwvp9QAa6eFzfxRlpVMk2mXh93czeyPn1Bn5ShWtYAJsyEve+OPgC7Hzmgx3USDtejQedlbtDX7h\ +0Ns6HChV5LcvP7rpb1+qx/690dHrtewL05c2c7ZLtrM91fOpDGjXyvT9+WYBPQAg3NPcey1n4vVt\ +FUJSIfGNjJZNy2ekkqzpazIJOefSoTaA9q1VY+5Wbvs9NAoYVBkFh5Sesi9lJ/u6lt5+WETpoi2M\ +PpZU/k9szmKGtVGRWBjQ6g3zP78pxfSGKb+tJ4LPAsi31S/+uXCUlVZmCIc+DlI15L4Cpr/1FA1d\ +0VLqAilzgcCGChdQc5eoTXqpkNS66hv1YLsUElURiG1sOZj7lunf3v3fwlBKjRfX9EjEHKcscV98\ +D40zRKIqgEpz4yvTVnfjU/VbmL/r4yhwTTbPCNsZNi8g50/OnvbCsXu5wQqVURCBuOb7seu98n7A\ +/L23Tc8NX8mW6pL73UoOhYPH/GJv/I7Dzlqbg5pRUG1q++A//+Ng+4f9gDlATVzLHfErZiHioKrn\ +H37uhgeG597sdYnIYeeszypQqQawre9dHNbd0Yj9/5KnfsB8DJpuXXj8Q+ryj3dUZglD1Uz3MsWv\ +HX7uh1fv6QGHn7upAmrWQpEV2zSt+bVptamw+6C9VaP/hcoHrvkABgydUjPLywy6Oboh6HW6PgLj\ +LYqStqYRQHKDMQflMhXOQrnata27tvGvufrEn8ZBfmdPP2AO7NpmAAw85B8qTyjKlt1svAHTjPGL\ +k4w0jAcTAyllnBoh9Kxw/tEdS8cuT0WyH4vX1PYD5qMBzQDE2eFDxz09zsscWuwVHX6a8YwaFAiM\ +NAkHr4vdUdf82rQN6JwnSl4N4vAxeKdxP2A+mjXuKTvcXcY9TdOnyxPk4zKZ/vbRAqe75C3QfZZY\ +0P/y6/7299z+H4QrdGsoib8JAAAAAElFTkSuQmCC" + } +}; + // The process of adding a new default snippet involves: // * add a new entity to aboutHome.dtd // * add a <span/> for it in aboutHome.xhtml // * add an entry here in the proper ordering (based on spans) // The <a/> part of the snippet will be linked to the corresponding url. const DEFAULT_SNIPPETS_URLS = [ "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet" , "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons" @@ -21,32 +153,32 @@ const DATABASE_NAME = "abouthome"; const DATABASE_VERSION = 1; const DATABASE_STORAGE = "persistent"; const SNIPPETS_OBJECTSTORE_NAME = "snippets"; // This global tracks if the page has been set up before, to prevent double inits let gInitialized = false; let gObserver = new MutationObserver(function (mutations) { for (let mutation of mutations) { - if (mutation.attributeName == "snippetsVersion") { + if (mutation.attributeName == "searchEngineName") { + setupSearchEngine(); if (!gInitialized) { ensureSnippetsMapThen(loadSnippets); gInitialized = true; } return; } } }); window.addEventListener("pageshow", function () { // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs // later and may use asynchronous getters. window.gObserver.observe(document.documentElement, { attributes: true }); fitToWidth(); - setupSearch(); window.addEventListener("resize", fitToWidth); // Ask chrome to update snippets. var event = new CustomEvent("AboutHomeLoad", {bubbles:true}); document.dispatchEvent(event); }); window.addEventListener("pagehide", function() { @@ -163,38 +295,94 @@ function ensureSnippetsMapThen(aCallback setTimeout(invokeCallbacks, 0); } } } function onSearchSubmit(aEvent) { - gContentSearchController.search(aEvent); + let searchText = document.getElementById("searchText"); + let searchTerms = searchText.value; + let engineName = document.documentElement.getAttribute("searchEngineName"); + + if (engineName && searchTerms.length > 0) { + // Send an event that will perform a search and Firefox Health Report will + // record that a search from about:home has occurred. + let eventData = { + engineName: engineName, + searchTerms: searchTerms, + originalEvent: { + target: { + ownerDocument: null + }, + shiftKey: aEvent.shiftKey, + ctrlKey: aEvent.ctrlKey, + metaKey: aEvent.metaKey, + altKey: aEvent.altKey, + button: aEvent.button, + }, + }; + + if (searchText.hasAttribute("selection-index")) { + eventData.selection = { + index: searchText.getAttribute("selection-index"), + kind: searchText.getAttribute("selection-kind") + }; + } + + eventData = JSON.stringify(eventData); + + let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData}); + document.dispatchEvent(event); + } + + gSearchSuggestionController.addInputValueToFormHistory(); + + if (aEvent) { + aEvent.preventDefault(); + } } -let gContentSearchController; +let gSearchSuggestionController; -function setupSearch() +function setupSearchEngine() { // The "autofocus" attribute doesn't focus the form element // immediately when the element is first drawn, so the // attribute is also used for styling when the page first loads. let searchText = document.getElementById("searchText"); searchText.addEventListener("blur", function searchText_onBlur() { searchText.removeEventListener("blur", searchText_onBlur); searchText.removeAttribute("autofocus"); }); + + let searchEngineName = document.documentElement.getAttribute("searchEngineName"); + let searchEngineInfo = SEARCH_ENGINES[searchEngineName]; + let logoElt = document.getElementById("searchEngineLogo"); - if (!gContentSearchController) { - gContentSearchController = - new ContentSearchUIController(searchText, searchText.parentNode, - "abouthome", "homepage"); + // Add search engine logo. + if (searchEngineInfo && searchEngineInfo.image) { + logoElt.parentNode.hidden = false; + logoElt.src = searchEngineInfo.image; + logoElt.alt = searchEngineName; + searchText.placeholder = ""; } + else { + logoElt.parentNode.hidden = true; + searchText.placeholder = searchEngineName; + } + + if (!gSearchSuggestionController) { + gSearchSuggestionController = + new SearchSuggestionUIController(searchText, searchText.parentNode, + onSearchSubmit); + } + gSearchSuggestionController.engineName = searchEngineName; } /** * Inform the test harness that we're done loading the page. */ function loadCompleted() { var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
--- a/browser/base/content/abouthome/aboutHome.xhtml +++ b/browser/base/content/abouthome/aboutHome.xhtml @@ -19,37 +19,39 @@ <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>&abouthome.pageTitle;</title> <link rel="icon" type="image/png" id="favicon" href="chrome://branding/content/icon32.png"/> <link rel="stylesheet" type="text/css" media="all" - href="chrome://browser/content/contentSearchUI.css"/> + href="chrome://browser/content/searchSuggestionUI.css"/> <link rel="stylesheet" type="text/css" media="all" defer="defer" href="chrome://browser/content/abouthome/aboutHome.css"/> <script type="text/javascript;version=1.8" src="chrome://browser/content/abouthome/aboutHome.js"/> <script type="text/javascript;version=1.8" - src="chrome://browser/content/contentSearchUI.js"/> + src="chrome://browser/content/searchSuggestionUI.js"/> </head> <body dir="&locale.dir;"> <div class="spacer"/> <div id="topSection"> <div id="brandLogo"></div> - <div id="searchIconAndTextContainer"> - <div id="searchIcon"/> - <input type="text" name="q" value="" id="searchText" maxlength="256" - aria-label="&contentSearchInput.label;" autofocus="autofocus" dir="auto"/> - <input id="searchSubmit" type="button" value="" onclick="onSearchSubmit(event)" - aria-label="&contentSearchSubmit.label;"/> + <div id="searchContainer"> + <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)"> + <div id="searchLogoContainer" hidden="true"><img id="searchEngineLogo"/></div> + <button id="searchIcon" type="button" /> + <input type="text" name="q" value="" id="searchText" maxlength="256" + autofocus="autofocus" dir="auto"/> + <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/> + </form> </div> <div id="snippetContainer"> <div id="defaultSnippets" hidden="true"> <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span> <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span> </div> <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span>
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -3462,18 +3462,19 @@ const BrowserSearch = { } return; } #endif let openSearchPageIfFieldIsNotActive = function(aSearchBar) { if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) { let url = gBrowser.currentURI.spec.toLowerCase(); let mm = gBrowser.selectedBrowser.messageManager; - if (url === "about:home" || - (url === "about:newtab" && NewTabUtils.allPages.enabled)) { + if (url === "about:home") { + AboutHome.focusInput(mm); + } else if (url === "about:newtab" && NewTabUtils.allPages.enabled) { ContentSearch.focusInput(mm); } else { openUILinkIn("about:home", "current"); } } }; let searchBar = this.searchBar;
--- a/browser/base/content/newtab/newTab.css +++ b/browser/base/content/newtab/newTab.css @@ -325,116 +325,144 @@ input[type=button] { background-color: #fff; opacity: 0.01; } /* 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; - position: relative; - height: 36px; -moz-box-flex: 1; + -moz-box-orient: horizontal; + -moz-box-align: center; + height: 44px; /* 32 + 6 logo top "padding" + 6 logo bottom "padding" */ + margin: 26px 20px 10px; /* top: 32 - 6 search form top "padding", bottom: 32 - 16 tiles top margin - 6 logo bottom "padding" */ max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */ } -#newtab-search-icon { - border: 1px transparent; - padding: 0; - margin: 0; - width: 36px; - height: 36px; - background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat; - position: absolute; +#newtab-search-logo { + display: -moz-box; + width: 38px; + 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-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon); + background-size: 26px 26px; +} + +#newtab-search-logo.magnifier { + width: 38px; /* 26 image width + 6 left "padding" + 6 right "padding" */ + -moz-margin-end: 5px; + background-size: 26px; + background-image: url("chrome://browser/skin/magnifier.png"); +} + +@media not all and (max-resolution: 1dppx) { + #newtab-search-logo.magnifier { + background-image: url("chrome://browser/skin/magnifier@2x.png"); + } +} + +#newtab-search-logo[type="logo"] { + background-size: 65px 26px; + width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */ +} + +#newtab-search-logo[type="favicon"] { + background-size: 16px 16px; +} + +#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: 38px; /* same height as #newtab-search-logo */ -moz-box-flex: 1; - padding-top: 6px; - padding-bottom: 6px; - padding-left: 34px; - padding-right: 8px; + + padding: 0 8px; background: hsla(0,0%,100%,.9) padding-box; border: 1px solid; - border-spacing: 0; - border-radius: 2px 0 0 2px; 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; color: inherit; } #newtab-search-text:-moz-dir(rtl) { - border-radius: 0 2px 2px 0; + border-radius: 0 2.5px 2.5px 0; } -#newtab-search-text[aria-expanded="true"] { - border-radius: 2px 0 0 0; -} - -#newtab-search-text[aria-expanded="true"]:-moz-dir(rtl) { - border-radius: 0 2px 0 0; -} - -#newtab-search-text[keepfocus], #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: 38px; /* same height as #newtab-search-logo */ + font-size: 13px !important; + -moz-margin-start: -1px; - background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box; - padding: 0; + 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); - border-radius: 0 2px 2px 0; -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); color: inherit; cursor: pointer; transition-property: background-color, border-color, box-shadow; transition-duration: 150ms; - width: 50px; } #newtab-search-submit:-moz-dir(rtl) { - border-radius: 2px 0 0 2px; + 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[keepfocus] + #newtab-search-submit, #newtab-search-text[autofocus] + #newtab-search-submit { - background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); + 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: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); + 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, @@ -511,35 +539,48 @@ input[type=button] { display: table-cell; border-top: none; } #newtab-customize-title > label { cursor: default; } -#newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent { +#newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent, +#newtab-search-panel > .panel-arrowcontainer > .panel-arrowcontent { padding: 0; } -.newtab-customize-panel-item { +.newtab-customize-panel-item, +.newtab-search-panel-engine, +#newtab-search-manage { line-height: 25px; padding: 15px; -moz-padding-start: 40px; font-size: 14px; cursor: pointer; max-width: 300px; } -.newtab-customize-panel-item:not(:first-child) { +.newtab-customize-panel-item:not(:first-child), +.newtab-search-panel-engine { border-top: 1px solid threedshadow; } +.newtab-search-panel-engine > image { + -moz-margin-end: 8px; + width: 16px; + height: 16px; + list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); +} + .newtab-customize-panel-subitem > label, .newtab-customize-panel-item > label, +.newtab-search-panel-engine > label, +#newtab-search-manage > label, .newtab-customize-complex-option { padding: 0; margin: 0; cursor: pointer; } .newtab-customize-panel-item, .newtab-customize-complex-option { @@ -579,17 +620,18 @@ input[type=button] { background-position: 15px 15px; color: #171F26; } .newtab-customize-complex-option:hover > .selectable:not([selected]) + .newtab-customize-panel-subitem { background-color: #FFFFFF; } -.newtab-customize-panel-item[selected] { +.newtab-customize-panel-item[selected], +.newtab-search-panel-engine[selected] { background: url("chrome://global/skin/menu/shared-menu-check-active.svg") no-repeat transparent; background-size: 16px 16px; background-position: 15px 15px; color: black; font-weight: 600; } .newtab-customize-panel-subitem > .checkbox { @@ -624,17 +666,17 @@ input[type=button] { .newtab-customize-panel-superitem { line-height: 20px; border-bottom: medium none !important; padding: 15px 15px 10px 15px; -moz-padding-start: 40px; border-top: 1px solid threedshadow; } -.contentSearchSuggestionTable { +.searchSuggestionTable { font: message-box; font-size: 16px; } /** * Onboarding styling */
--- a/browser/base/content/newtab/newTab.xul +++ b/browser/base/content/newtab/newTab.xul @@ -1,30 +1,39 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> -<?xml-stylesheet href="chrome://browser/content/contentSearchUI.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/searchSuggestionUI.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?> <?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?> <!DOCTYPE window [ <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd"> %newTabDTD; + <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd"> + %searchBarDTD; <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> %browserDTD; ]> <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="&newtab.pageTitle;"> + <xul:panel id="newtab-search-panel" orient="vertical" type="arrow" + noautohide="true" hidden="true"> + <xul:hbox id="newtab-search-manage"> + <xul:label>&changeSearchSettings.button;</xul:label> + </xul:hbox> + </xul:panel> + <div class="newtab-customize-panel-container"> <div id="newtab-customize-panel" orient="vertical"> <div id="newtab-customize-panel-anchor"></div> <div id="newtab-customize-title" class="newtab-customize-panel-item"> <label>&newtab.customize.cog.title2;</label> </div> <div class="newtab-customize-complex-option"> @@ -94,23 +103,23 @@ class="newtab-undo-button" /> <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1" class="close-icon tabbable" tooltiptext="&newtab.undo.closeTooltip;" /> </div> </div> <div id="newtab-search-container"> - <div id="newtab-search-form"> - <div id="newtab-search-icon"/> + <form id="newtab-search-form" name="searchForm"> + <div id="newtab-search-logo"/> <input type="text" name="q" value="" id="newtab-search-text" - aria-label="&contentSearchInput.label;" maxlength="256" dir="auto"/> - <input id="newtab-search-submit" type="button" value="" - aria-label="&contentSearchSubmit.label;"/> - </div> + maxlength="256" dir="auto"/> + <input id="newtab-search-submit" type="submit" + value="&searchEndCap.label;"/> + </form> </div> <div id="newtab-horizontal-margin"> <div class="newtab-side-margin"/> <div id="newtab-grid"> </div> @@ -119,12 +128,12 @@ <div id="newtab-margin-bottom"/> </div> <input id="newtab-customize-button" type="button" title="&newtab.customize.title;"/> </div> <xul:script type="text/javascript;version=1.8" - src="chrome://browser/content/contentSearchUI.js"/> + src="chrome://browser/content/searchSuggestionUI.js"/> <xul:script type="text/javascript;version=1.8" src="chrome://browser/content/newtab/newTab.js"/> </xul:window>
--- a/browser/base/content/newtab/search.js +++ b/browser/base/content/newtab/search.js @@ -1,15 +1,261 @@ #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, + + get useNewUI() { + let newUI = Services.prefs.getBoolPref("browser.search.showOneOffButtons"); + delete this.useNewUI; + this.useNewUI = newUI; + return newUI; + }, + init: function () { - document.getElementById("newtab-search-submit") - .addEventListener("click", e => this._contentSearchController.search(e)); - let textbox = document.getElementById("newtab-search-text"); - this._contentSearchController = - new ContentSearchUIController(textbox, textbox.parentNode, "newtab", "newtab"); + for (let idSuffix of this._nodeIDSuffixes) { + this._nodes[idSuffix] = + document.getElementById("newtab-search-" + idSuffix); + } + + if (this.useNewUI) { + this._nodes.logo.classList.add("magnifier"); + } + + window.addEventListener("ContentSearchService", this); + this._send("GetState"); + }, + + showPanel: function () { + let panel = this._nodes.panel; + let logo = this._nodes.logo; + panel.hidden = false; + panel.openPopup(logo); + logo.setAttribute("active", "true"); + panel.addEventListener("popuphidden", function onHidden() { + panel.removeEventListener("popuphidden", onHidden); + panel.hidden = true; + logo.removeAttribute("active"); + }); + }, + + search: function (event) { + if (event) { + event.preventDefault(); + } + let searchText = this._nodes.text; + let searchStr = searchText.value; + if (this.currentEngineName && searchStr.length) { + let useNewTab = event && event.button == 1; + let eventData = { + engineName: this.currentEngineName, + searchString: searchStr, + whence: "newtab", + originalEvent: { + target: { + ownerDocument: null + }, + shiftKey: event.shiftKey, + ctrlKey: event.ctrlKey, + metaKey: event.metaKey, + altKey: event.altKey, + button: event.button, + }, + } + + if (searchText.hasAttribute("selection-index")) { + eventData.selection = { + index: searchText.getAttribute("selection-index"), + kind: searchText.getAttribute("selection-kind") + }; + } + + this._send("Search", eventData); + } + this._suggestionController.addInputValueToFormHistory(); + }, + + manageEngines: function () { + this._nodes.panel.hidePopup(); + this._send("ManageEngines"); + }, + + handleEvent: function (event) { + let methodName = "on" + event.detail.type; + if (this.hasOwnProperty(methodName)) { + this[methodName](event.detail.data); + } + }, + + onState: function (data) { + this._newEngines = data.engines; + this._setCurrentEngine(data.currentEngine); + this._initWhenInitalStateReceived(); + }, + + onCurrentState: function (data) { + if (this._initialStateReceived) { + this._newEngines = data.engines; + this._setCurrentEngine(data.currentEngine); + } + }, + + onCurrentEngine: function (engineName) { + if (this._initialStateReceived) { + this._nodes.panel.hidePopup(); + this._setCurrentEngine(engineName); + } + }, + + onFocusInput: function () { + this._nodes.text.focus(); + }, + + _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._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel()); + this._initialStateReceived = true; + this._initWhenInitalStateReceived = function () {}; + }, + + _send: function (type, data=null) { + window.dispatchEvent(new CustomEvent("ContentSearchClient", { + detail: { + type: type, + data: data, + }, + })); + }, + + _setUpPanel: function () { + // The new search UI only contains the "manage" engine entry in the panel + if (this.useNewUI) { + return; + } + + // Build the panel if necessary. + if (this._newEngines) { + this._buildPanel(this._newEngines); + delete this._newEngines; + } + + // Set the selected states of the engines. + let panel = this._nodes.panel; + for (let box of panel.childNodes) { + if (box.getAttribute("engine") == this.currentEngineName) { + box.setAttribute("selected", "true"); + } + else { + box.removeAttribute("selected"); + } + } + }, + + _buildPanel: 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); + } + }, + + // Converts favicon array buffer into data URI of the right size and dpi. + _getFaviconURIFromBuffer: function (buffer) { + let blob = new Blob([buffer]); + let dpiSize = Math.round(16 * window.devicePixelRatio); + let sizeStr = dpiSize + "," + dpiSize; + return URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr; + }, + + _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.iconBuffer) { + let uri = this._getFaviconURIFromBuffer(engine.iconBuffer); + image.setAttribute("src", uri); + } + 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; + + if (!this.useNewUI) { + let type = ""; + let uri; + let logoBuf = window.devicePixelRatio >= 2 ? + engine.logo2xBuffer || engine.logoBuffer : + engine.logoBuffer || engine.logo2xBuffer; + if (logoBuf) { + uri = URL.createObjectURL(new Blob([logoBuf])); + type = "logo"; + } + else if (engine.iconBuffer) { + uri = this._getFaviconURIFromBuffer(engine.iconBuffer); + type = "favicon"; + } + this._nodes.logo.setAttribute("type", type); + + if (uri) { + this._nodes.logo.style.backgroundImage = "url(" + uri + ")"; + } + else { + this._nodes.logo.style.backgroundImage = ""; + } + this._nodes.text.placeholder = engine.placeholder; + } + + // Set up the suggestion controller. + if (!this._suggestionController) { + let parent = document.getElementById("newtab-scrollbox"); + this._suggestionController = + new SearchSuggestionUIController(this._nodes.text, parent, + event => this.search(event)); + } + this._suggestionController.engineName = engine.name; }, };
rename from browser/base/content/contentSearchUI.css rename to browser/base/content/searchSuggestionUI.css --- a/browser/base/content/contentSearchUI.css +++ b/browser/base/content/searchSuggestionUI.css @@ -1,152 +1,44 @@ /* 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/. */ -.contentSearchSuggestionTable { +.searchSuggestionTable { background-color: hsla(0,0%,100%,.99); - border: 1px solid hsla(0, 0%, 0%, .2); - border-top: none; - box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1); + border: 1px solid; + border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2); + border-spacing: 0; + border-top: 0; + 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); + overflow: hidden; + padding: 0; position: absolute; - left: 0; + text-align: start; z-index: 1001; - -moz-user-select: none; - cursor: default; } -.contentSearchSuggestionsList { - border-bottom: 1px solid hsl(0, 0%, 92%); - width: 100%; - height: 100%; -} - -.contentSearchSuggestionTable, -.contentSearchSuggestionsList { - border-spacing: 0; - overflow: hidden; - padding: 0; - margin: 0; - text-align: start; -} - -.contentSearchHeaderRow, -.contentSearchSuggestionRow { +.searchSuggestionRow { + cursor: default; margin: 0; max-width: inherit; padding: 0; } -.contentSearchHeaderRow > td > img, -.contentSearchSuggestionRow > td > .historyIcon { - margin-right: 8px; - margin-bottom: -3px; -} - -.contentSearchSuggestionTable .historyIcon { - width: 16px; - height: 16px; - display: inline-block; - background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon"); -} - -.contentSearchSuggestionRow.selected > td > .historyIcon { - background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active"); +.searchSuggestionRow.formHistory + .searchSuggestionRow.remote > td { + border-top: 1px solid GrayText; } -.contentSearchHeader > img { - height: 16px; - width: 16px; - margin: 0; - padding: 0; +.searchSuggestionRow.selected { + background-color: hsl(210,100%,40%); + color: hsl(0,0%,100%); } -.contentSearchSuggestionRow.remote > td > .historyIcon { - visibility: hidden; -} - -.contentSearchSuggestionRow.selected { - background-color: Highlight; - color: HighlightText; -} - -.contentSearchHeader, -.contentSearchSuggestionEntry { +.searchSuggestionEntry { margin: 0; max-width: inherit; overflow: hidden; - padding: 4px 10px; + padding: 6px 8px; text-overflow: ellipsis; white-space: nowrap; - font-size: 75%; } - -.contentSearchHeader { - background-color: hsl(0, 0%, 97%); - color: #666; - border-bottom: 1px solid hsl(0, 0%, 92%); -} - -.contentSearchSuggestionsContainer { - margin: 0; - padding: 0; - border-spacing: 0; - width: 100%; -} - -.contentSearchSearchWithHeaderSearchText { - white-space: pre; - font-weight: bold; -} - -.contentSearchOneOffItem { - -moz-appearance: none; - height: 32px; - margin: 0; - padding: 0; - border: none; - background: none; - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII='); - background-repeat: no-repeat; - background-position: right center; -} - -.contentSearchOneOffItem > img { - width: 16px; - height: 16px; - margin-bottom: -2px; -} - -.contentSearchOneOffItem:not(.last-row) { - border-bottom: 1px solid hsl(0, 0%, 92%); -} - -.contentSearchOneOffItem.end-of-row { - background-image: none; -} - -.contentSearchOneOffItem.selected { - background-color: Highlight; - background-image: none; -} - -.contentSearchOneOffsTable { - width: 100%; -} - -.contentSearchSettingsButton { - margin: 0; - padding: 0; - height: 32px; - border: none; - border-top: 1px solid hsla(0, 0%, 0%, .08); - text-align: center; - width: 100%; -} - -.contentSearchSettingsButton.selected { - background-color: hsl(0, 0%, 90%); -} - -.contentSearchSettingsButton:active { - background-color: hsl(0, 0%, 85%); -}
rename from browser/base/content/contentSearchUI.js rename to browser/base/content/searchSuggestionUI.js --- a/browser/base/content/contentSearchUI.js +++ b/browser/base/content/searchSuggestionUI.js @@ -1,20 +1,19 @@ /* 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.ContentSearchUIController = (function () { +this.SearchSuggestionUIController = (function () { const MAX_DISPLAYED_SUGGESTIONS = 6; const SUGGESTION_ID_PREFIX = "searchSuggestion"; -const ONE_OFF_ID_PREFIX = "oneOff"; -const CSS_URI = "chrome://browser/content/contentSearchUI.css"; +const CSS_URI = "chrome://browser/content/searchSuggestionUI.css"; const HTML_NS = "http://www.w3.org/1999/xhtml"; /** * Creates a new object that manages search suggestions and their UI for a text * box. * * The UI consists of an html:table that's inserted into the DOM after the given @@ -25,372 +24,267 @@ const HTML_NS = "http://www.w3.org/1999/ * Assumed to be an html:input. xul:textbox is untested but might work. * @param tableParent * The suggestion table is appended as a child to this element. Since * the table is absolutely positioned and its top and left values are set * to be relative to the top and left of the page, either the parent and * all its ancestors should not be positioned elements (i.e., their * positions should be "static"), or the parent's position should be the * top left of the page. - * @param healthReportKey - * This will be sent with the search data for FHR to record the search. - * @param searchPurpose - * Sent with search data, see nsISearchEngine.getSubmission. + * @param onClick + * A function that's called when a search suggestion is clicked. Ideally + * we could call submit() on inputElement's ancestor form, but that + * doesn't trigger submit listeners. The function is passed one argument, + * the click event. * @param idPrefix * The IDs of elements created by the object will be prefixed with this * string. */ -function ContentSearchUIController(inputElement, tableParent, healthReportKey, - searchPurpose, idPrefix="") { +function SearchSuggestionUIController(inputElement, tableParent, onClick=null, + idPrefix="") { this.input = inputElement; + this.onClick = onClick; this._idPrefix = idPrefix; - this._healthReportKey = healthReportKey; - this._searchPurpose = searchPurpose; let tableID = idPrefix + "searchSuggestionTable"; this.input.autocomplete = "off"; this.input.setAttribute("aria-autocomplete", "true"); this.input.setAttribute("aria-controls", tableID); tableParent.appendChild(this._makeTable(tableID)); this.input.addEventListener("keypress", this); this.input.addEventListener("input", this); this.input.addEventListener("focus", this); this.input.addEventListener("blur", this); window.addEventListener("ContentSearchService", this); this._stickyInputValue = ""; this._hideSuggestions(); - this._getSearchEngines(); - this._getStrings(); + this._ignoreInputEvent = false; } -ContentSearchUIController.prototype = { +SearchSuggestionUIController.prototype = { // The timeout (ms) of the remote suggestions. Corresponds to // SearchSuggestionController.remoteTimeout. Uses // SearchSuggestionController's default timeout if falsey. remoteTimeout: undefined, - _oneOffButtons: [], - get defaultEngine() { - return this._defaultEngine; + get engineName() { + return this._engineName; }, - set defaultEngine(val) { - this._defaultEngine = val; - this._updateDefaultEngineHeader(); - + set engineName(val) { + this._engineName = val; if (val && document.activeElement == this.input) { this._speculativeConnect(); } }, - get engines() { - return this._engines; - }, - - set engines(val) { - this._engines = val; - this._setUpOneOffButtons(); - }, - - // The selectedIndex is the index of the element with the "selected" class in - // the list obtained by concatenating the suggestion rows, one-off buttons, and - // search settings button. get selectedIndex() { - let allElts = [...this._suggestionsList.children, - ...this._oneOffButtons, - document.getElementById("contentSearchSettingsButton")]; - for (let i = 0; i < allElts.length; ++i) { - let elt = allElts[i]; - if (elt.classList.contains("selected")) { + for (let i = 0; i < this._table.children.length; i++) { + let row = this._table.children[i]; + if (row.classList.contains("selected")) { return i; } } return -1; }, set selectedIndex(idx) { // Update the table's rows, and the input when there is a selection. this._table.removeAttribute("aria-activedescendant"); - this.input.removeAttribute("aria-activedescendant"); - - let allElts = [...this._suggestionsList.children, - ...this._oneOffButtons, - document.getElementById("contentSearchSettingsButton")]; - for (let i = 0; i < allElts.length; ++i) { - let elt = allElts[i]; - let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt; + for (let i = 0; i < this._table.children.length; i++) { + let row = this._table.children[i]; if (i == idx) { - elt.classList.add("selected"); - ariaSelectedElt.setAttribute("aria-selected", "true"); - this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id); + row.classList.add("selected"); + row.firstChild.setAttribute("aria-selected", "true"); + this._table.setAttribute("aria-activedescendant", row.firstChild.id); } else { - elt.classList.remove("selected"); - ariaSelectedElt.setAttribute("aria-selected", "false"); + row.classList.remove("selected"); + row.firstChild.setAttribute("aria-selected", "false"); } } }, - get selectedEngineName() { - let selectedElt = this._table.querySelector(".selected"); - if (selectedElt && selectedElt.engineName) { - return selectedElt.engineName; - } - return this.defaultEngine.name; - }, - get numSuggestions() { - return this._suggestionsList.children.length; + return this._table.children.length; }, selectAndUpdateInput: function (idx) { this.selectedIndex = idx; - let newValue = this.suggestionAtIndex(idx) || this._stickyInputValue; - // Setting the input value when the value has not changed commits the current - // IME composition, which we don't want to do. - if (this.input.value != newValue) { - this.input.value = newValue; - } - this._updateSearchWithHeader(); + this.input.value = idx >= 0 ? this.suggestionAtIndex(idx) : + this._stickyInputValue; }, suggestionAtIndex: function (idx) { - let row = this._suggestionsList.children[idx]; + let row = this._table.children[idx]; return row ? row.textContent : null; }, deleteSuggestionAtIndex: function (idx) { // Only form history suggestions can be deleted. if (this.isFormHistorySuggestionAtIndex(idx)) { let suggestionStr = this.suggestionAtIndex(idx); this._sendMsg("RemoveFormHistoryEntry", suggestionStr); - this._suggestionsList.children[idx].remove(); + this._table.children[idx].remove(); this.selectAndUpdateInput(-1); } }, isFormHistorySuggestionAtIndex: function (idx) { - let row = this._suggestionsList.children[idx]; + let row = this._table.children[idx]; return row && row.classList.contains("formHistory"); }, addInputValueToFormHistory: function () { this._sendMsg("AddFormHistoryEntry", this.input.value); }, handleEvent: function (event) { this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event); }, - _onCommand: function(aEvent) { - if (this.selectedIndex == this.numSuggestions + this._oneOffButtons.length) { - // Settings button was selected. - this._sendMsg("ManageEngines"); + _onInput: function (event) { + if (this._ignoreInputEvent) { return; } - - this.search(aEvent); - - if (aEvent) { - aEvent.preventDefault(); - } - }, - - search: function (aEvent) { - if (!this.defaultEngine) { - return; // Not initialized yet. - } - - let searchText = this.input; - let searchTerms; - if (this._table.hidden || - aEvent.originalTarget.id == "contentSearchDefaultEngineHeader") { - searchTerms = searchText.value; + if (this.input.value) { + this._getSuggestions(); } else { - searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value; - } - // Send an event that will perform a search and Firefox Health Report will - // record that a search from the healthReportKey passed to the constructor. - let eventData = { - engineName: this.selectedEngineName, - searchString: searchTerms, - healthReportKey: this._healthReportKey, - searchPurpose: this._searchPurpose, - originalEvent: { - shiftKey: aEvent.shiftKey, - ctrlKey: aEvent.ctrlKey, - metaKey: aEvent.metaKey, - altKey: aEvent.altKey, - button: aEvent.button, - }, - }; - - if (this.suggestionAtIndex(this.selectedIndex)) { - eventData.selection = { - index: this.selectedIndex, - kind: aEvent instanceof MouseEvent ? "mouse" : - aEvent instanceof KeyboardEvent ? "key" : undefined, - }; - } - - this._sendMsg("Search", eventData); - this.addInputValueToFormHistory(); - }, - - _onInput: function () { - if (!this.input.value) { this._stickyInputValue = ""; this._hideSuggestions(); } - else if (this.input.value != this._stickyInputValue) { - // Only fetch new suggestions if the input value has changed. - this._getSuggestions(); - this.selectAndUpdateInput(-1); - } - this._updateSearchWithHeader(); + this.selectedIndex = -1; }, _onKeypress: function (event) { let selectedIndexDelta = 0; switch (event.keyCode) { case event.DOM_VK_UP: - if (!this._table.hidden) { + if (this.numSuggestions) { selectedIndexDelta = -1; } break; case event.DOM_VK_DOWN: - if (this._table.hidden) { - this._getSuggestions(); + if (this.numSuggestions) { + selectedIndexDelta = 1; } else { - selectedIndexDelta = 1; + this._getSuggestions(); } break; case event.DOM_VK_RIGHT: // Allow normal caret movement until the caret is at the end of the input. if (this.input.selectionStart != this.input.selectionEnd || this.input.selectionEnd != this.input.value.length) { return; } - if (this.numSuggestions && this.selectedIndex >= 0 && - this.selectedIndex < this.numSuggestions) { + // else, fall through + case event.DOM_VK_RETURN: + if (this.selectedIndex >= 0) { this.input.value = this.suggestionAtIndex(this.selectedIndex); this.input.setAttribute("selection-index", this.selectedIndex); this.input.setAttribute("selection-kind", "key"); } else { // If we didn't select anything, make sure to remove the attributes // in case they were populated last time. this.input.removeAttribute("selection-index"); this.input.removeAttribute("selection-kind"); } this._stickyInputValue = this.input.value; this._hideSuggestions(); break; - case event.DOM_VK_RETURN: - this._onCommand(event); - break; case event.DOM_VK_DELETE: if (this.selectedIndex >= 0) { this.deleteSuggestionAtIndex(this.selectedIndex); } break; - case event.DOM_VK_ESCAPE: - if (!this._table.hidden) { - this._hideSuggestions(); - } default: return; } if (selectedIndexDelta) { // Update the selection. let newSelectedIndex = this.selectedIndex + selectedIndexDelta; if (newSelectedIndex < -1) { - newSelectedIndex = this.numSuggestions + this._oneOffButtons.length; + newSelectedIndex = this.numSuggestions - 1; } - else if (this.numSuggestions + this._oneOffButtons.length < newSelectedIndex) { + else if (this.numSuggestions <= newSelectedIndex) { newSelectedIndex = -1; } this.selectAndUpdateInput(newSelectedIndex); // Prevent the input's caret from moving. event.preventDefault(); } }, _onFocus: function () { - if (this._mousedown) { - return; - } - // When the input box loses focus to something in our table, we refocus it - // immediately. This causes the focus highlight to flicker, so we set a - // custom attribute which consumers should use for focus highlighting. This - // attribute is removed only when we do not immediately refocus the input - // box, thus eliminating flicker. - this.input.setAttribute("keepfocus", "true"); this._speculativeConnect(); }, _onBlur: function () { - if (this._mousedown) { - // At this point, this.input has lost focus, but a new element has not yet - // received it. If we re-focus this.input directly, the new element will - // steal focus immediately, so we queue it instead. - setTimeout(() => this.input.focus(), 0); - return; - } - this.input.removeAttribute("keepfocus"); this._hideSuggestions(); }, _onMousemove: function (event) { - this.selectedIndex = this._indexOfTableItem(event.target); + this.selectedIndex = this._indexOfTableRowOrDescendent(event.target); }, - _onMouseup: function (event) { + _onMousedown: function (event) { if (event.button == 2) { return; } - this._onCommand(event); - }, + let idx = this._indexOfTableRowOrDescendent(event.target); + let suggestion = this.suggestionAtIndex(idx); + this._stickyInputValue = suggestion; - _onClick: function (event) { - this._onMouseup(event); + // Setting value commits composition string forcibly. While IME commits + // composition, this needs to ignore input event at committed composition + // string which will be overwritten by the suggestion. + this._ignoreInputEvent = true; + this.input.value = suggestion; + this._ignoreInputEvent = false; + this.input.setAttribute("selection-index", idx); + this.input.setAttribute("selection-kind", "mouse"); + this._hideSuggestions(); + if (this.onClick) { + this.onClick.call(null, event); + } }, _onContentSearchService: function (event) { let methodName = "_onMsg" + event.detail.type; if (methodName in this) { this[methodName](event.detail.data); } }, - _onMsgFocusInput: function (event) { - this.input.focus(); - }, - _onMsgSuggestions: function (suggestions) { // Ignore the suggestions if their search string or engine doesn't match // ours. Due to the async nature of message passing, this can easily happen // when the user types quickly. if (this._stickyInputValue != suggestions.searchString || - this.defaultEngine.name != suggestions.engineName) { + this.engineName != suggestions.engineName) { return; } - this._clearSuggestionRows(); + // Empty the table. + while (this._table.firstElementChild) { + this._table.firstElementChild.remove(); + } // Position and size the table. - let { left } = this.input.getBoundingClientRect(); - this._table.style.top = this.input.offsetHeight + "px"; + let { left, bottom } = this.input.getBoundingClientRect(); + this._table.style.left = (left + window.scrollX) + "px"; + this._table.style.top = (bottom + window.scrollY) + "px"; this._table.style.minWidth = this.input.offsetWidth + "px"; this._table.style.maxWidth = (window.innerWidth - left - 40) + "px"; // Add the suggestions to the table. let searchWords = new Set(suggestions.searchString.trim().toLowerCase().split(/\s+/)); for (let i = 0; i < MAX_DISPLAYED_SUGGESTIONS; i++) { let type, idx; @@ -401,108 +295,41 @@ ContentSearchUIController.prototype = { let j = i - suggestions.formHistory.length; if (j < suggestions.remote.length) { [type, idx] = ["remote", j]; } else { break; } } - this._suggestionsList.appendChild( - this._makeTableRow(type, suggestions[type][idx], i, searchWords)); + this._table.appendChild(this._makeTableRow(type, suggestions[type][idx], + i, searchWords)); } - if (this._table.hidden) { - this.selectedIndex = -1; - this._table.hidden = false; - this.input.setAttribute("aria-expanded", "true"); - } - }, - - _onMsgState: function (state) { - this.defaultEngine = { - name: state.currentEngine.name, - icon: this._getFaviconURIFromBuffer(state.currentEngine.iconBuffer), - }; - this.engines = state.engines; - }, - - _onMsgCurrentState: function (state) { - this._onMsgState(state); - }, - - _onMsgCurrentEngine: function (engine) { - this.defaultEngine = { - name: engine.name, - icon: this._getFaviconURIFromBuffer(engine.iconBuffer), - }; - this._setUpOneOffButtons(); - }, - - _onMsgStrings: function (strings) { - this._strings = strings; - this._updateDefaultEngineHeader(); - this._updateSearchWithHeader(); - }, - - _updateDefaultEngineHeader: function () { - let header = document.getElementById("contentSearchDefaultEngineHeader"); - if (this.defaultEngine.icon) { - header.firstChild.setAttribute("src", this.defaultEngine.icon); - } - if (!this._strings) { - return; - } - while (header.firstChild.nextSibling) { - header.firstChild.nextSibling.remove(); - } - header.appendChild(document.createTextNode( - this._strings.searchHeader.replace("%S", this.defaultEngine.name))); - }, - - _updateSearchWithHeader: function () { - if (!this._strings) { - return; - } - let searchWithHeader = document.getElementById("contentSearchSearchWithHeader"); - while (searchWithHeader.firstChild) { - searchWithHeader.firstChild.remove(); - } - if (this.input.value) { - searchWithHeader.appendChild(document.createTextNode(this._strings.searchFor)); - let span = document.createElementNS(HTML_NS, "span"); - span.setAttribute("class", "contentSearchSearchWithHeaderSearchText"); - span.appendChild(document.createTextNode(" " + this.input.value + " ")); - searchWithHeader.appendChild(span); - searchWithHeader.appendChild(document.createTextNode(this._strings.searchWith)); - return; - } - searchWithHeader.appendChild(document.createTextNode(this._strings.searchWithHeader)); + this._table.hidden = false; + this.input.setAttribute("aria-expanded", "true"); }, _speculativeConnect: function () { - if (this.defaultEngine) { - this._sendMsg("SpeculativeConnect", this.defaultEngine.name); + if (this.engineName) { + this._sendMsg("SpeculativeConnect", this.engineName); } }, _makeTableRow: function (type, suggestionStr, currentRow, searchWords) { let row = document.createElementNS(HTML_NS, "tr"); row.dir = "auto"; - row.classList.add("contentSearchSuggestionRow"); + row.classList.add("searchSuggestionRow"); row.classList.add(type); row.setAttribute("role", "presentation"); row.addEventListener("mousemove", this); - row.addEventListener("mouseup", this); + row.addEventListener("mousedown", this); let entry = document.createElementNS(HTML_NS, "td"); - let img = document.createElementNS(HTML_NS, "div"); - img.setAttribute("class", "historyIcon"); - entry.appendChild(img); - entry.classList.add("contentSearchSuggestionEntry"); + entry.classList.add("searchSuggestionEntry"); entry.setAttribute("role", "option"); entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow; entry.setAttribute("aria-selected", "false"); let suggestionWords = suggestionStr.trim().toLowerCase().split(/\s+/); for (let i = 0; i < suggestionWords.length; i++) { let word = suggestionWords[i]; let wordSpan = document.createElementNS(HTML_NS, "span"); @@ -515,221 +342,59 @@ ContentSearchUIController.prototype = { entry.appendChild(document.createTextNode(" ")); } } row.appendChild(entry); return row; }, - // Converts favicon array buffer into data URI of the right size and dpi. - _getFaviconURIFromBuffer: function (buffer) { - let blob = new Blob([buffer]); - let dpiSize = Math.round(16 * window.devicePixelRatio); - let sizeStr = dpiSize + "," + dpiSize; - return URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr; - }, - - _getSearchEngines: function () { - this._sendMsg("GetState"); - }, - - _getStrings: function () { - this._sendMsg("GetStrings"); - }, - _getSuggestions: function () { this._stickyInputValue = this.input.value; - if (this.defaultEngine) { + if (this.engineName) { this._sendMsg("GetSuggestions", { - engineName: this.defaultEngine.name, + engineName: this.engineName, searchString: this.input.value, remoteTimeout: this.remoteTimeout, }); } }, - _clearSuggestionRows: function() { - while (this._suggestionsList.firstElementChild) { - this._suggestionsList.firstElementChild.remove(); - } - }, - _hideSuggestions: function () { this.input.setAttribute("aria-expanded", "false"); this._table.hidden = true; + while (this._table.firstElementChild) { + this._table.firstElementChild.remove(); + } + this.selectAndUpdateInput(-1); }, - _indexOfTableItem: function (elt) { - if (elt.classList.contains("contentSearchOneOffItem")) { - return this.numSuggestions + this._oneOffButtons.indexOf(elt); + _indexOfTableRowOrDescendent: function (row) { + while (row && row.localName != "tr") { + row = row.parentNode; } - if (elt.classList.contains("contentSearchSettingsButton")) { - return this.numSuggestions + this._oneOffButtons.length; - } - while (elt && elt.localName != "tr") { - elt = elt.parentNode; - } - if (!elt) { + if (!row) { throw new Error("Element is not a row"); } - return elt.rowIndex; + return row.rowIndex; }, _makeTable: function (id) { this._table = document.createElementNS(HTML_NS, "table"); this._table.id = id; this._table.hidden = true; - this._table.classList.add("contentSearchSuggestionTable"); - this._table.setAttribute("role", "presentation"); - - // When the search input box loses focus, we want to immediately give focus - // back to it if the blur was because the user clicked somewhere in the table. - // onBlur uses the _mousedown flag to detect this. - this._table.addEventListener("mousedown", () => { this._mousedown = true; }); - document.addEventListener("mouseup", () => { delete this._mousedown; }); - - // Deselect the selected element on mouseout if it wasn't a suggestion. - this._table.addEventListener("mouseout", () => { - if (this.selectedIndex >= this.numSuggestions) { - this.selectAndUpdateInput(-1); - } - }); - - // If a search is loaded in the same tab, ensure the suggestions dropdown - // is hidden immediately when the page starts loading and not when it first - // appears, in order to provide timely feedback to the user. - window.addEventListener("beforeunload", () => { this._hideSuggestions(); }); - - let headerRow = document.createElementNS(HTML_NS, "tr"); - let header = document.createElementNS(HTML_NS, "td"); - headerRow.setAttribute("class", "contentSearchHeaderRow"); - header.setAttribute("class", "contentSearchHeader"); - let img = document.createElementNS(HTML_NS, "img"); - img.setAttribute("src", "chrome://browser/skin/search-engine-placeholder.png"); - header.appendChild(img); - header.id = "contentSearchDefaultEngineHeader"; - headerRow.appendChild(header); - headerRow.addEventListener("click", this); - this._table.appendChild(headerRow); - - let row = document.createElementNS(HTML_NS, "tr"); - row.setAttribute("class", "contentSearchSuggestionsContainer"); - let cell = document.createElementNS(HTML_NS, "td"); - cell.setAttribute("class", "contentSearchSuggestionsContainer"); - this._suggestionsList = document.createElementNS(HTML_NS, "table"); - this._suggestionsList.setAttribute("class", "contentSearchSuggestionsList"); - cell.appendChild(this._suggestionsList); - row.appendChild(cell); - this._table.appendChild(row); - this._suggestionsList.setAttribute("role", "listbox"); - - this._oneOffsTable = document.createElementNS(HTML_NS, "table"); - this._oneOffsTable.setAttribute("class", "contentSearchOneOffsTable"); - this._oneOffsTable.classList.add("contentSearchSuggestionsContainer"); - this._oneOffsTable.setAttribute("role", "group"); - this._table.appendChild(this._oneOffsTable); - - headerRow = document.createElementNS(HTML_NS, "tr"); - header = document.createElementNS(HTML_NS, "td"); - headerRow.setAttribute("class", "contentSearchHeaderRow"); - header.setAttribute("class", "contentSearchHeader"); - headerRow.appendChild(header); - header.id = "contentSearchSearchWithHeader"; - this._oneOffsTable.appendChild(headerRow); - - let button = document.createElementNS(HTML_NS, "button"); - button.appendChild(document.createTextNode("Change Search Settings")); - button.setAttribute("class", "contentSearchSettingsButton"); - button.classList.add("contentSearchHeaderRow"); - button.classList.add("contentSearchHeader"); - button.id = "contentSearchSettingsButton"; - button.addEventListener("click", this); - button.addEventListener("mousemove", this); - this._table.appendChild(button); - + this._table.classList.add("searchSuggestionTable"); + this._table.setAttribute("role", "listbox"); return this._table; }, - _setUpOneOffButtons: function () { - // Sometimes we receive a CurrentEngine message from the ContentSearch service - // before we've received a State message - i.e. before we have our engines. - if (!this._engines) { - return; - } - - while (this._oneOffsTable.firstChild.nextSibling) { - this._oneOffsTable.firstChild.nextSibling.remove(); - } - - this._oneOffButtons = []; - - let engines = this._engines.filter(aEngine => aEngine.name != this.defaultEngine.name); - if (!engines.length) { - this._oneOffsTable.hidden = true; - return; - } - - const kDefaultButtonWidth = 49; // 48px + 1px border. - let rowWidth = this.input.offsetWidth - 2; // 2px border. - let enginesPerRow = Math.floor(rowWidth / kDefaultButtonWidth); - let buttonWidth = Math.floor(rowWidth / enginesPerRow); - - let row = document.createElementNS(HTML_NS, "tr"); - let cell = document.createElementNS(HTML_NS, "td"); - row.setAttribute("class", "contentSearchSuggestionsContainer"); - cell.setAttribute("class", "contentSearchSuggestionsContainer"); - - for (let i = 0; i < engines.length; ++i) { - let engine = engines[i]; - if (i > 0 && i % enginesPerRow == 0) { - row.appendChild(cell); - this._oneOffsTable.appendChild(row); - row = document.createElementNS(HTML_NS, "tr"); - cell = document.createElementNS(HTML_NS, "td"); - row.setAttribute("class", "contentSearchSuggestionsContainer"); - cell.setAttribute("class", "contentSearchSuggestionsContainer"); - } - let button = document.createElementNS(HTML_NS, "button"); - button.setAttribute("class", "contentSearchOneOffItem"); - let img = document.createElementNS(HTML_NS, "img"); - let uri = "chrome://browser/skin/search-engine-placeholder.png"; - if (engine.iconBuffer) { - uri = this._getFaviconURIFromBuffer(engine.iconBuffer); - } - img.setAttribute("src", uri); - button.appendChild(img); - button.style.width = buttonWidth + "px"; - button.setAttribute("title", engine.name); - - button.engineName = engine.name; - button.addEventListener("click", this); - button.addEventListener("mousemove", this); - - if (engines.length - i <= enginesPerRow - (i % enginesPerRow)) { - button.classList.add("last-row"); - } - - if ((i + 1) % enginesPerRow == 0) { - button.classList.add("end-of-row"); - } - - button.id = ONE_OFF_ID_PREFIX + i; - cell.appendChild(button); - this._oneOffButtons.push(button); - } - row.appendChild(cell); - this._oneOffsTable.appendChild(row); - this._oneOffsTable.hidden = false; - }, - _sendMsg: function (type, data=null) { dispatchEvent(new CustomEvent("ContentSearchClient", { detail: { type: type, data: data, }, })); }, }; -return ContentSearchUIController; +return SearchSuggestionUIController; })();
--- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -109,16 +109,22 @@ let AboutHomeListener = { handleEvent: function(aEvent) { if (!this.isAboutHome) { return; } switch (aEvent.type) { case "AboutHomeLoad": this.onPageLoad(); break; + case "AboutHomeSearchEvent": + this.onSearch(aEvent); + break; + case "AboutHomeSearchPanel": + this.onOpenSearchPanel(aEvent); + break; case "click": this.onClick(aEvent); break; case "pagehide": this.onPageHide(aEvent); break; } }, @@ -126,49 +132,57 @@ let AboutHomeListener = { receiveMessage: function(aMessage) { if (!this.isAboutHome) { return; } switch (aMessage.name) { case "AboutHome:Update": this.onUpdate(aMessage.data); break; + case "AboutHome:FocusInput": + this.onFocusInput(); + break; } }, onUpdate: function(aData) { let doc = content.document; if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content)) doc.getElementById("launcher").setAttribute("session", "true"); // Inject search engine and snippets URL. let docElt = doc.documentElement; - // Set snippetsVersion last, which triggers to show the snippets when it's set. + // set the following attributes BEFORE searchEngineName, which triggers to + // show the snippets when it's set. docElt.setAttribute("snippetsURL", aData.snippetsURL); if (aData.showKnowYourRights) docElt.setAttribute("showKnowYourRights", "true"); docElt.setAttribute("snippetsVersion", aData.snippetsVersion); + docElt.setAttribute("searchEngineName", aData.defaultEngineName); }, onPageLoad: function() { let doc = content.document; if (doc.documentElement.hasAttribute("hasBrowserHandlers")) { return; } doc.documentElement.setAttribute("hasBrowserHandlers", "true"); addMessageListener("AboutHome:Update", this); + addMessageListener("AboutHome:FocusInput", this); addEventListener("click", this, true); addEventListener("pagehide", this, true); if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) { doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui"); } sendAsyncMessage("AboutHome:RequestUpdate"); + doc.addEventListener("AboutHomeSearchEvent", this, true, true); + doc.addEventListener("AboutHomeSearchPanel", this, true, true); }, onClick: function(aEvent) { if (!aEvent.isTrusted || // Don't trust synthetic events aEvent.button == 2 || aEvent.target.localName != "button") { return; } @@ -209,30 +223,49 @@ let AboutHomeListener = { case "sync": sendAsyncMessage("AboutHome:Sync"); break; case "settings": sendAsyncMessage("AboutHome:Settings"); break; + + case "searchIcon": + sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget }); + break; } }, onPageHide: function(aEvent) { if (aEvent.target.defaultView.frameElement) { return; } removeMessageListener("AboutHome:Update", this); removeEventListener("click", this, true); removeEventListener("pagehide", this, true); if (aEvent.target.documentElement) { aEvent.target.documentElement.removeAttribute("hasBrowserHandlers"); } }, + + onSearch: function(aEvent) { + sendAsyncMessage("AboutHome:Search", { searchData: aEvent.detail }); + }, + + onOpenSearchPanel: function(aEvent) { + sendAsyncMessage("AboutHome:OpenSearchPanel"); + }, + + onFocusInput: function () { + let searchInput = content.document.getElementById("searchText"); + if (searchInput) { + searchInput.focus(); + } + }, }; AboutHomeListener.init(this); let AboutPrivateBrowsingListener = { init(chromeGlobal) { chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this, false, true); },
--- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -75,17 +75,16 @@ support-files = parsingTestHelpers.jsm pinning_headers.sjs pinning_reports.sjs popup_blocker.html print_postdata.sjs redirect_bug623155.sjs searchSuggestionEngine.sjs searchSuggestionEngine.xml - searchSuggestionEngine2.xml subtst_contextmenu.html test-mixedcontent-securityerrors.html test_bug435035.html test_bug462673.html test_bug628179.html test_bug839103.html test_bug959531.html test_process_flags_chrome.html @@ -365,20 +364,20 @@ skip-if = buildapp == 'mulet' || e10s # [browser_save_private_link_perwindowpb.js] skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly [browser_save_link_when_window_navigates.js] skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly [browser_save_video.js] skip-if = buildapp == 'mulet' [browser_save_video_frame.js] [browser_scope.js] -[browser_contentSearchUI.js] +[browser_searchSuggestionUI.js] support-files = - contentSearchUI.html - contentSearchUI.js + searchSuggestionUI.html + searchSuggestionUI.js [browser_selectpopup.js] run-if = e10s [browser_selectTabAtIndex.js] [browser_ssl_error_reports.js] [browser_star_hsts.js] [browser_subframe_favicons_not_used.js] [browser_syncui.js] [browser_tabDrop.js]
--- a/browser/base/content/test/general/browser_aboutHome.js +++ b/browser/base/content/test/general/browser_aboutHome.js @@ -96,45 +96,53 @@ let gTests = [ // Health Report disabled, or no SearchesProvider. return Promise.resolve(); } let engine = yield promiseNewEngine("searchSuggestionEngine.xml"); // Make this actually work in healthreport by giving it an ID: engine.wrappedJSObject._identifier = 'org.mozilla.testsearchsuggestions'; - let p = promiseContentSearchChange(engine.name); + let promise = promiseBrowserAttributes(gBrowser.selectedTab); Services.search.currentEngine = engine; - yield p; + yield promise; let numSearchesBefore = 0; let searchEventDeferred = Promise.defer(); let doc = gBrowser.contentDocument; - let engineName = gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name; + let engineName = doc.documentElement.getAttribute("searchEngineName"); is(engine.name, engineName, "Engine name in DOM should match engine we just added"); + let mm = gBrowser.selectedBrowser.messageManager; + + mm.loadFrameScript(TEST_CONTENT_HELPER, false); + + mm.addMessageListener("AboutHomeTest:CheckRecordedSearch", function (msg) { + let data = JSON.parse(msg.data); + is(data.engineName, engineName, "Detail is search engine name"); + + getNumberOfSearches(engineName).then(num => { + is(num, numSearchesBefore + 1, "One more search recorded."); + searchEventDeferred.resolve(); + }); + }); // Get the current number of recorded searches. let searchStr = "a search"; getNumberOfSearches(engineName).then(num => { numSearchesBefore = num; info("Perform a search."); doc.getElementById("searchText").value = searchStr; doc.getElementById("searchSubmit").click(); }); let expectedURL = Services.search.currentEngine. getSubmission(searchStr, null, "homepage"). uri.spec; - let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => { - getNumberOfSearches(engineName).then(num => { - is(num, numSearchesBefore + 1, "One more search recorded."); - searchEventDeferred.resolve(); - }); - }); + let loadPromise = waitForDocLoadAndStopIt(expectedURL); try { yield Promise.all([searchEventDeferred.promise, loadPromise]); } catch (ex) { Cu.reportError(ex); ok(false, "An error occurred waiting for the search to be performed: " + ex); } finally { try { @@ -223,58 +231,67 @@ let gTests = [ Services.prefs.clearUserPref("browser.rights.override"); } }, { desc: "Check POST search engine support", setup: function() {}, - run: function* () + run: function() { let deferred = Promise.defer(); let currEngine = Services.search.defaultEngine; - let searchObserver = Task.async(function* search_observer(aSubject, aTopic, aData) { + let searchObserver = function search_observer(aSubject, aTopic, aData) { let engine = aSubject.QueryInterface(Ci.nsISearchEngine); info("Observer: " + aData + " for " + engine.name); if (aData != "engine-added") return; if (engine.name != "POST Search") return; // Ready to execute the tests! let needle = "Search for something awesome."; let document = gBrowser.selectedBrowser.contentDocument; let searchText = document.getElementById("searchText"); - let p = promiseContentSearchChange(engine.name); - Services.search.defaultEngine = engine; - yield p; + // We're about to change the search engine. Once the change has + // propagated to the about:home content, we want to perform a search. + let mutationObserver = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + if (mutation.attributeName == "searchEngineName") { + searchText.value = needle; + searchText.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + } + } + }); + mutationObserver.observe(document.documentElement, { attributes: true }); - searchText.value = needle; - searchText.focus(); - EventUtils.synthesizeKey("VK_RETURN", {}); + // Change the search engine, triggering the observer above. + Services.search.defaultEngine = engine; registerCleanupFunction(function() { + mutationObserver.disconnect(); Services.search.removeEngine(engine); Services.search.defaultEngine = currEngine; }); // When the search results load, check them for correctness. waitForLoad(function() { let loadedText = gBrowser.contentDocument.body.textContent; ok(loadedText, "search page loaded"); is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")), "Search text should arrive correctly"); deferred.resolve(); }); - }); + }; Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false); registerCleanupFunction(function () { Services.obs.removeObserver(searchObserver, "browser-search-engine-modified"); }); Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml", Ci.nsISearchEngine.DATA_XML, null, false); return deferred.promise; } @@ -313,30 +330,31 @@ let gTests = [ }); browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html"); return deferred.promise; } }, { - // See browser_contentSearchUI.js for comprehensive content search UI tests. + // See browser_searchSuggestionUI.js for comprehensive content search + // suggestion UI tests. desc: "Search suggestion smoke test", setup: function() {}, run: function() { return Task.spawn(function* () { // Add a test engine that provides suggestions and switch to it. let engine = yield promiseNewEngine("searchSuggestionEngine.xml"); - let p = promiseContentSearchChange(engine.name); + let promise = promiseBrowserAttributes(gBrowser.selectedTab); Services.search.currentEngine = engine; - yield p; + yield promise; // Avoid intermittent failures. - gBrowser.contentWindow.wrappedJSObject.gContentSearchController.remoteTimeout = 5000; + gBrowser.contentWindow.wrappedJSObject.gSearchSuggestionController.remoteTimeout = 5000; // Type an X in the search input. let input = gBrowser.contentDocument.getElementById("searchText"); input.focus(); EventUtils.synthesizeKey("x", {}); // Wait for the search suggestions to become visible. let table = @@ -378,21 +396,19 @@ let gTests = [ string: "x", clauses: [ { length: 1, attr: EventUtils.COMPOSITION_ATTR_RAW_CLAUSE } ] }, caret: { start: 1, length: 0 } }, gBrowser.contentWindow); - let searchController = - gBrowser.contentWindow.wrappedJSObject.gContentSearchController; - // Wait for the search suggestions to become visible. - let table = searchController._suggestionsList; + let table = + gBrowser.contentDocument.getElementById("searchSuggestionTable"); let deferred = Promise.defer(); let observer = new MutationObserver(() => { if (input.getAttribute("aria-expanded") == "true") { observer.disconnect(); ok(!table.hidden, "Search suggestion table unhidden"); deferred.resolve(); } }); @@ -403,24 +419,19 @@ let gTests = [ yield deferred.promise; // Click the second suggestion. let expectedURL = Services.search.currentEngine. getSubmission("xbar", null, "homepage"). uri.spec; let loadPromise = waitForDocLoadAndStopIt(expectedURL); let row = table.children[1]; - // ContentSearchUIController looks at the current selectedIndex when - // performing a search. Synthesizing the mouse event on the suggestion - // doesn't actually mouseover the suggestion and trigger it to be flagged - // as selected, so we manually select it first. - searchController.selectedIndex = 1; - EventUtils.synthesizeMouseAtCenter(row, {button: 0}, gBrowser.contentWindow); + EventUtils.sendMouseEvent({ type: "mousedown" }, row, gBrowser.contentWindow); yield loadPromise; - ok(input.value == "x", "Input value did not change"); + ok(input.value == "xbar", "Suggestion is selected"); }); } }, { desc: "Cmd+k should focus the search box in the page when the search box in the toolbar is absent", setup: function () { // Remove the search bar from toolbar CustomizableUI.removeWidgetFromArea("search-container"); @@ -461,16 +472,36 @@ let gTests = [ run: Task.async(function* () { let syncButton = gBrowser.selectedBrowser.contentDocument.getElementById("sync"); yield EventUtils.synthesizeMouseAtCenter(syncButton, {}, gBrowser.contentWindow); yield promiseTabLoadEvent(gBrowser.selectedTab, null, "load"); is(gBrowser.currentURI.spec, "about:accounts?entrypoint=abouthome", "Entry point should be `abouthome`."); }) +}, +{ + desc: "Clicking the icon should open the popup", + setup: function () {}, + run: Task.async(function* () { + let doc = gBrowser.selectedBrowser.contentDocument; + let searchIcon = doc.getElementById("searchIcon"); + let panel = window.document.getElementById("abouthome-search-panel"); + + info("Waiting for popup to open"); + EventUtils.synthesizeMouseAtCenter(searchIcon, {}, gBrowser.selectedBrowser.contentWindow); + yield promiseWaitForEvent(panel, "popupshown"); + info("Saw popup open"); + + let promise = promisePrefsOpen(); + let item = window.document.getElementById("abouthome-search-panel-manage"); + EventUtils.synthesizeMouseAtCenter(item, {}); + + yield promise; + }) } ]; function test() { waitForExplicitFinish(); requestLongerTimeout(2); @@ -541,16 +572,48 @@ function promiseSetupSnippetsMap(aTab, a aSetupFn(aSnippetsMap); deferred.resolve(aSnippetsMap); }); }, true, true); return deferred.promise; } /** + * Waits for the attributes being set by browser.js. + * + * @param aTab + * The tab containing about:home. + * @return {Promise} resolved when the attributes are ready. + */ +function promiseBrowserAttributes(aTab) +{ + let deferred = Promise.defer(); + + let docElt = aTab.linkedBrowser.contentDocument.documentElement; + let observer = new MutationObserver(function (mutations) { + for (let mutation of mutations) { + info("Got attribute mutation: " + mutation.attributeName + + " from " + mutation.oldValue); + // Now we just have to wait for the last attribute. + if (mutation.attributeName == "searchEngineName") { + info("Remove attributes observer"); + observer.disconnect(); + // Must be sure to continue after the page mutation observer. + executeSoon(function() deferred.resolve()); + break; + } + } + }); + info("Add attributes observer"); + observer.observe(docElt, { attributes: true }); + + return deferred.promise; +} + +/** * Retrieves the number of about:home searches recorded for the current day. * * @param aEngineName * name of the setup search engine. * * @return {Promise} Returns a promise resolving to the number of searches. */ function getNumberOfSearches(aEngineName) { @@ -643,28 +706,16 @@ let promisePrefsOpen = Task.async(functi }); }); } }); }); } }); -function promiseContentSearchChange(newEngineName) { - return new Promise(resolve => { - content.addEventListener("ContentSearchService", function listener(aEvent) { - if (aEvent.detail.type == "CurrentState" && - gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name == newEngineName) { - content.removeEventListener("ContentSearchService", listener); - resolve(); - } - }); - }); -} - function promiseNewEngine(basename) { info("Waiting for engine to be added: " + basename); 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); registerCleanupFunction(() => {
rename from browser/base/content/test/general/browser_contentSearchUI.js rename to browser/base/content/test/general/browser_searchSuggestionUI.js --- a/browser/base/content/test/general/browser_contentSearchUI.js +++ b/browser/base/content/test/general/browser_searchSuggestionUI.js @@ -1,18 +1,16 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -const TEST_PAGE_BASENAME = "contentSearchUI.html"; -const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js"; -const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine"; +const TEST_PAGE_BASENAME = "searchSuggestionUI.html"; +const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js"; const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; -const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml"; -const TEST_MSG = "ContentSearchUIControllerTest"; +const TEST_MSG = "SearchSuggestionUIControllerTest"; add_task(function* emptyInput() { yield setUp(); let state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", ["xfoo", "xbar"], -1); state = yield msg("key", "VK_BACK_SPACE"); @@ -28,406 +26,233 @@ add_task(function* blur() { checkState(state, "x", ["xfoo", "xbar"], -1); state = yield msg("blur"); checkState(state, "x", [], -1); yield msg("reset"); }); -add_task(function* upDownKeys() { +add_task(function* arrowKeys() { yield setUp(); let state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", ["xfoo", "xbar"], -1); // Cycle down the suggestions starting from no selection. state = yield msg("key", "VK_DOWN"); checkState(state, "xfoo", ["xfoo", "xbar"], 0); state = yield msg("key", "VK_DOWN"); checkState(state, "xbar", ["xfoo", "xbar"], 1); state = yield msg("key", "VK_DOWN"); - checkState(state, "x", ["xfoo", "xbar"], 2); - - state = yield msg("key", "VK_DOWN"); - checkState(state, "x", ["xfoo", "xbar"], 3); - - state = yield msg("key", "VK_DOWN"); checkState(state, "x", ["xfoo", "xbar"], -1); // Cycle up starting from no selection. state = yield msg("key", "VK_UP"); - checkState(state, "x", ["xfoo", "xbar"], 3); - - state = yield msg("key", "VK_UP"); - checkState(state, "x", ["xfoo", "xbar"], 2); - - state = yield msg("key", "VK_UP"); checkState(state, "xbar", ["xfoo", "xbar"], 1); state = yield msg("key", "VK_UP"); checkState(state, "xfoo", ["xfoo", "xbar"], 0); state = yield msg("key", "VK_UP"); checkState(state, "x", ["xfoo", "xbar"], -1); yield msg("reset"); }); -add_task(function* rightLeftKeys() { - yield setUp(); - - let state = yield msg("key", { key: "x", waitForSuggestions: true }); - checkState(state, "x", ["xfoo", "xbar"], -1); +// The right arrow and return key function the same. +function rightArrowOrReturn(keyName) { + return function* rightArrowOrReturnTest() { + yield setUp(); - state = yield msg("key", "VK_LEFT"); - checkState(state, "x", ["xfoo", "xbar"], -1); - - state = yield msg("key", "VK_LEFT"); - checkState(state, "x", ["xfoo", "xbar"], -1); + let state = yield msg("key", { key: "x", waitForSuggestions: true }); + checkState(state, "x", ["xfoo", "xbar"], -1); - state = yield msg("key", "VK_RIGHT"); - checkState(state, "x", ["xfoo", "xbar"], -1); - - state = yield msg("key", "VK_RIGHT"); - checkState(state, "x", [], -1); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoo", "xbar"], 0); - state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); - checkState(state, "x", ["xfoo", "xbar"], -1); - - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoo", ["xfoo", "xbar"], 0); + // This should make the xfoo suggestion sticky. To make sure it sticks, + // trigger suggestions again and cycle through them by pressing Down until + // nothing is selected again. + state = yield msg("key", keyName); + checkState(state, "xfoo", [], -1); - // This should make the xfoo suggestion sticky. To make sure it sticks, - // trigger suggestions again and cycle through them by pressing Down until - // nothing is selected again. - state = yield msg("key", "VK_RIGHT"); - checkState(state, "xfoo", [], 0); + state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); - state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); - checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0); - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1); - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1); - - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3); + yield msg("reset"); + }; +} - state = yield msg("key", "VK_DOWN"); - checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1); - - yield msg("reset"); -}); +add_task(rightArrowOrReturn("VK_RIGHT")); +add_task(rightArrowOrReturn("VK_RETURN")); add_task(function* mouse() { yield setUp(); let state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", ["xfoo", "xbar"], -1); - for (let i = 0; i < 4; ++i) { - state = yield msg("mousemove", i); - checkState(state, "x", ["xfoo", "xbar"], i); - } + // Mouse over the first suggestion. + state = yield msg("mousemove", 0); + checkState(state, "x", ["xfoo", "xbar"], 0); + + // Mouse over the second suggestion. + state = yield msg("mousemove", 1); + checkState(state, "x", ["xfoo", "xbar"], 1); - state = yield msg("mousemove", -1); - checkState(state, "x", ["xfoo", "xbar"], -1); + // Click the second suggestion. This should make it sticky. To make sure it + // sticks, trigger suggestions again and cycle through them by pressing Down + // until nothing is selected again. + state = yield msg("mousedown", 1); + checkState(state, "xbar", [], -1); + + state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1); yield msg("reset"); }); add_task(function* formHistory() { yield setUp(); // Type an X and add it to form history. let state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", ["xfoo", "xbar"], -1); + yield msg("addInputValueToFormHistory"); + // Wait for Satchel to say it's been added to form history. let deferred = Promise.defer(); Services.obs.addObserver(function onAdd(subj, topic, data) { if (data == "formhistory-add") { Services.obs.removeObserver(onAdd, "satchel-storage-changed"); executeSoon(() => deferred.resolve()); } }, "satchel-storage-changed", false); - yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]); + yield deferred.promise; // Reset the input. state = yield msg("reset"); checkState(state, "", [], -1); // Type an X again. The form history entry should appear. state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"], -1); // Select the form history entry and delete it. state = yield msg("key", "VK_DOWN"); checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"], 0); + state = yield msg("key", "VK_DELETE"); + checkState(state, "x", ["xfoo", "xbar"], -1); + // Wait for Satchel. deferred = Promise.defer(); Services.obs.addObserver(function onRemove(subj, topic, data) { if (data == "formhistory-remove") { Services.obs.removeObserver(onRemove, "satchel-storage-changed"); executeSoon(() => deferred.resolve()); } }, "satchel-storage-changed", false); - - state = yield msg("key", "VK_DELETE"); - checkState(state, "x", ["xfoo", "xbar"], -1); - yield deferred.promise; // Reset the input. state = yield msg("reset"); checkState(state, "", [], -1); // Type an X again. The form history entry should still be gone. state = yield msg("key", { key: "x", waitForSuggestions: true }); checkState(state, "x", ["xfoo", "xbar"], -1); yield msg("reset"); }); -add_task(function* search() { - yield setUp(); - - let modifiers = {}; - ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true); - - // Test typing a query and pressing enter. - let p = msg("waitForSearch"); - yield msg("key", { key: "x", waitForSuggestions: true }); - yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); - let mesg = yield p; - let eventData = { - engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME, - searchString: "x", - healthReportKey: "test", - searchPurpose: "test", - originalEvent: modifiers, - }; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - // Test typing a query, then selecting a suggestion and pressing enter. - p = msg("waitForSearch"); - yield msg("key", { key: "x", waitForSuggestions: true }); - yield msg("key", "VK_DOWN"); - yield msg("key", "VK_DOWN"); - yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); - mesg = yield p; - eventData.searchString = "xfoo"; - eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; - eventData.selection = { - index: 1, - kind: "key", - } - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); +add_task(function* composition() { yield setUp(); - // Test typing a query, then selecting a one-off button and pressing enter. - p = msg("waitForSearch"); - yield msg("key", { key: "x", waitForSuggestions: true }); - yield msg("key", "VK_UP"); - yield msg("key", "VK_UP"); - yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); - mesg = yield p; - delete eventData.selection; - eventData.searchString = "x"; - eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - // Test typing a query and clicking the search engine header. - p = msg("waitForSearch"); - modifiers.button = 0; - yield msg("key", { key: "x", waitForSuggestions: true }); - yield msg("mousemove", -1); - yield msg("click", { eltIdx: -1, modifiers: modifiers }); - mesg = yield p; - eventData.originalEvent = modifiers; - eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - // Test typing a query and then clicking a suggestion. - yield msg("key", { key: "x", waitForSuggestions: true }); - p = msg("waitForSearch"); - yield msg("mousemove", 1); - yield msg("click", { eltIdx: 1, modifiers: modifiers }); - mesg = yield p; - eventData.searchString = "xfoo"; - eventData.selection = { - index: 1, - kind: "mouse", - }; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - // Test typing a query and then clicking a one-off button. - yield msg("key", { key: "x", waitForSuggestions: true }); - p = msg("waitForSearch"); - yield msg("mousemove", 3); - yield msg("click", { eltIdx: 3, modifiers: modifiers }); - mesg = yield p; - eventData.searchString = "x"; - eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME; - delete eventData.selection; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - // Test searching when using IME composition. let state = yield msg("startComposition", { data: "" }); checkState(state, "", [], -1); state = yield msg("changeComposition", { data: "x", waitForSuggestions: true }); - checkState(state, "x", [{ str: "x", type: "formHistory" }, - { str: "xfoo", type: "formHistory" }, "xbar"], -1); - yield msg("commitComposition"); - delete modifiers.button; - p = msg("waitForSearch"); - yield msg("key", { key: "VK_RETURN", modifiers: modifiers }); - mesg = yield p; - eventData.originalEvent = modifiers; - eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); - - yield promiseTab(); - yield setUp(); - - state = yield msg("startComposition", { data: "" }); - checkState(state, "", [], -1); - state = yield msg("changeComposition", { data: "x", waitForSuggestions: true }); - checkState(state, "x", [{ str: "x", type: "formHistory" }, - { str: "xfoo", type: "formHistory" }, "xbar"], -1); + checkState(state, "x", ["xfoo", "xbar"], -1); // Mouse over the first suggestion. state = yield msg("mousemove", 0); - checkState(state, "x", [{ str: "x", type: "formHistory" }, - { str: "xfoo", type: "formHistory" }, "xbar"], 0); + checkState(state, "x", ["xfoo", "xbar"], 0); // Mouse over the second suggestion. state = yield msg("mousemove", 1); - checkState(state, "x", [{ str: "x", type: "formHistory" }, - { str: "xfoo", type: "formHistory" }, "xbar"], 1); + checkState(state, "x", ["xfoo", "xbar"], 1); - modifiers.button = 0; - let currentTab = gBrowser.selectedTab; - p = msg("waitForSearch"); - yield msg("click", { eltIdx: 1, modifiers: modifiers }); - mesg = yield p; - eventData.searchString = "xfoo"; - eventData.originalEvent = modifiers; - eventData.selection = { - index: 1, - kind: "mouse", - }; - SimpleTest.isDeeply(eventData, mesg, "Search event data"); + // Click the second suggestion. This should make it sticky. To make sure it + // sticks, trigger suggestions again and cycle through them by pressing Down + // until nothing is selected again. + state = yield msg("mousedown", 1); - yield promiseTab(); - yield setUp(); + checkState(state, "xbar", [], -1); - // Remove form history entries. - // Wait for Satchel. - let deferred = Promise.defer(); - let historyCount = 2; - Services.obs.addObserver(function onRemove(subj, topic, data) { - if (data == "formhistory-remove") { - if (--historyCount) { - return; - } - Services.obs.removeObserver(onRemove, "satchel-storage-changed"); - executeSoon(() => deferred.resolve()); - } - }, "satchel-storage-changed", false); + state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); + checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1); + + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0); - yield msg("key", { key: "x", waitForSuggestions: true }); - yield msg("key", "VK_DOWN"); - yield msg("key", "VK_DOWN"); - yield msg("key", "VK_DELETE"); - yield msg("key", "VK_DOWN"); - yield msg("key", "VK_DELETE"); - yield deferred.promise; - - yield msg("reset"); - state = yield msg("key", { key: "x", waitForSuggestions: true }); - checkState(state, "x", ["xfoo", "xbar"], -1); + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1); - yield promiseTab(); - yield setUp(); - yield msg("reset"); -}); - -add_task(function* settings() { - yield setUp(); - yield msg("key", { key: "VK_DOWN", waitForSuggestions: true }); - yield msg("key", "VK_UP"); - let p = msg("waitForSearchSettings"); - yield msg("key", "VK_RETURN"); - yield p; + state = yield msg("key", "VK_DOWN"); + checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1); yield msg("reset"); }); + let gDidInitialSetUp = false; -function setUp(aNoEngine) { +function setUp() { return Task.spawn(function* () { if (!gDidInitialSetUp) { - Cu.import("resource:///modules/ContentSearch.jsm"); - let originalOnMessageSearch = ContentSearch._onMessageSearch; - let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines; - ContentSearch._onMessageSearch = () => {}; - ContentSearch._onMessageManageEngines = () => {}; - registerCleanupFunction(() => { - ContentSearch._onMessageSearch = originalOnMessageSearch; - ContentSearch._onMessageManageEngines = originalOnMessageManageEngines; - }); - yield setUpEngines(); + yield promiseNewEngine(TEST_ENGINE_BASENAME); yield promiseTab(); gDidInitialSetUp = true; } yield msg("focus"); }); } function msg(type, data=null) { gMsgMan.sendAsyncMessage(TEST_MSG, { type: type, data: data, }); let deferred = Promise.defer(); gMsgMan.addMessageListener(TEST_MSG, function onMsg(msg) { - if (msg.data.type != type) { - return; - } gMsgMan.removeMessageListener(TEST_MSG, onMsg); - deferred.resolve(msg.data.data); + deferred.resolve(msg.data); }); return deferred.promise; } function checkState(actualState, expectedInputVal, expectedSuggestions, expectedSelectedIdx) { expectedSuggestions = expectedSuggestions.map(sugg => { return typeof(sugg) == "object" ? sugg : { @@ -439,16 +264,27 @@ function checkState(actualState, expecte let expectedState = { selectedIndex: expectedSelectedIdx, numSuggestions: expectedSuggestions.length, suggestionAtIndex: expectedSuggestions.map(s => s.str), isFormHistorySuggestionAtIndex: expectedSuggestions.map(s => s.type == "formHistory"), tableHidden: expectedSuggestions.length == 0, + tableChildrenLength: expectedSuggestions.length, + tableChildren: expectedSuggestions.map((s, i) => { + let expectedClasses = new Set([s.type]); + if (i == expectedSelectedIdx) { + expectedClasses.add("selected"); + } + return { + textContent: s.str, + classes: expectedClasses, + }; + }), inputValue: expectedInputVal, ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true", }; SimpleTest.isDeeply(actualState, expectedState, "State"); } @@ -465,17 +301,17 @@ function promiseTab() { gMsgMan = tab.linkedBrowser.messageManager; gMsgMan.sendAsyncMessage("ContentSearch", { type: "AddToWhitelist", data: [pageURL], }); promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => { let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME; gMsgMan.loadFrameScript(jsURL, false); - deferred.resolve(msg("init")); + deferred.resolve(); }); }, true, true); openUILinkIn(pageURL, "current"); return deferred.promise; } function promiseMsg(name, type, msgMan) { let deferred = Promise.defer(); @@ -485,44 +321,24 @@ function promiseMsg(name, type, msgMan) if (msg.data.type == type) { msgMan.removeMessageListener(name, onMsg); deferred.resolve(msg); } }); return deferred.promise; } -function setUpEngines() { - return Task.spawn(function* () { - info("Removing default search engines"); - let currentEngineName = Services.search.currentEngine.name; - let currentEngines = Services.search.getVisibleEngines(); - info("Adding test search engines"); - let engine1 = yield promiseNewEngine(TEST_ENGINE_BASENAME); - let engine2 = yield promiseNewEngine(TEST_ENGINE_2_BASENAME); - Services.search.currentEngine = engine1; - for (let engine of currentEngines) { - Services.search.removeEngine(engine); - } - registerCleanupFunction(() => { - Services.search.restoreDefaultEngines(); - Services.search.removeEngine(engine1); - Services.search.removeEngine(engine2); - Services.search.currentEngine = Services.search.getEngineByName(currentEngineName); - }); - }); -} - function promiseNewEngine(basename) { info("Waiting for engine to be added: " + basename); 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); + registerCleanupFunction(() => Services.search.removeEngine(engine)); addDeferred.resolve(engine); }, onError: function (errCode) { ok(false, "addEngine failed with error code " + errCode); addDeferred.reject(); }, }); return addDeferred.promise;
--- a/browser/base/content/test/general/searchSuggestionEngine.xml +++ b/browser/base/content/test/general/searchSuggestionEngine.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ --> <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName> <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/> -<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine&terms={searchTerms}" rel="searchform"/> +<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine" rel="searchform"/> </SearchPlugin>
deleted file mode 100644 --- a/browser/base/content/test/general/searchSuggestionEngine2.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Any copyright is dedicated to the Public Domain. - - http://creativecommons.org/publicdomain/zero/1.0/ --> - -<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/"> -<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName> -<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/> -<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&terms={searchTerms}" rel="searchform"/> -</SearchPlugin>
rename from browser/base/content/test/general/contentSearchUI.html rename to browser/base/content/test/general/searchSuggestionUI.html --- a/browser/base/content/test/general/contentSearchUI.html +++ b/browser/base/content/test/general/searchSuggestionUI.html @@ -4,18 +4,17 @@ <html> <head> <meta charset="utf-8"> <script type="application/javascript;version=1.8" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"> </script> <script type="application/javascript;version=1.8" - src="chrome://browser/content/contentSearchUI.js"> + src="chrome://browser/content/searchSuggestionUI.js"> </script> -<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/> </head> <body> -<div id="container" style="position: relative;"><input type="text" value=""/></div> +<input> </body> </html>
rename from browser/base/content/test/general/contentSearchUI.js rename to browser/base/content/test/general/searchSuggestionUI.js --- a/browser/base/content/test/general/contentSearchUI.js +++ b/browser/base/content/test/general/searchSuggestionUI.js @@ -1,189 +1,158 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; (function () { -const TEST_MSG = "ContentSearchUIControllerTest"; +const TEST_MSG = "SearchSuggestionUIControllerTest"; const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml"; -var gController; + +let input = content.document.querySelector("input"); +let gController = + new content.SearchSuggestionUIController(input, input.parentNode); +gController.engineName = ENGINE_NAME; +gController.remoteTimeout = 5000; addMessageListener(TEST_MSG, msg => { messageHandlers[msg.data.type](msg.data.data); }); let messageHandlers = { - init: function() { - Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME); - let input = content.document.querySelector("input"); - gController = - new content.ContentSearchUIController(input, input.parentNode, "test", "test"); - content.addEventListener("ContentSearchService", function listener(aEvent) { - if (aEvent.detail.type == "State" && - gController.defaultEngine.name == ENGINE_NAME) { - content.removeEventListener("ContentSearchService", listener); - ack("init"); - } - }); - gController.remoteTimeout = 5000; - }, - key: function (arg) { let keyName = typeof(arg) == "string" ? arg : arg.key; - content.synthesizeKey(keyName, arg.modifiers || {}); + content.synthesizeKey(keyName, {}); let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb(); - wait(ack.bind(null, "key")); + wait(ack); }, startComposition: function (arg) { content.synthesizeComposition({ type: "compositionstart", data: "" }); - ack("startComposition"); + ack(); }, changeComposition: function (arg) { let data = typeof(arg) == "string" ? arg : arg.data; content.synthesizeCompositionChange({ composition: { string: data, clauses: [ { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE } ] }, caret: { start: data.length, length: 0 } }); let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb(); - wait(ack.bind(null, "changeComposition")); - }, - - commitComposition: function () { - content.synthesizeComposition({ type: "compositioncommitasis" }); - ack("commitComposition"); + wait(ack); }, focus: function () { gController.input.focus(); - ack("focus"); + ack(); }, blur: function () { gController.input.blur(); - ack("blur"); - }, - - waitForSearch: function () { - waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData)); - }, - - waitForSearchSettings: function () { - waitForContentSearchEvent("ManageEngines", - aData => ack("waitForSearchSettings", aData)); + ack(); }, - mousemove: function (itemIndex) { - let row; - if (itemIndex == -1) { - row = gController._table.firstChild; - } - else { - let allElts = [...gController._suggestionsList.children, - ...gController._oneOffButtons, - content.document.getElementById("contentSearchSettingsButton")]; - row = allElts[itemIndex]; - } - let event = { - type: "mousemove", - clickcount: 0, - } - content.synthesizeMouseAtCenter(row, event); - ack("mousemove"); + mousemove: function (suggestionIdx) { + // Copied from widget/tests/test_panel_mouse_coords.xul and + // browser/base/content/test/newtab/head.js + let row = gController._table.children[suggestionIdx]; + let rect = row.getBoundingClientRect(); + let left = content.mozInnerScreenX + rect.left; + let x = left + rect.width / 2; + let y = content.mozInnerScreenY + rect.top + rect.height / 2; + + let utils = content.SpecialPowers.getDOMWindowUtils(content); + let scale = utils.screenPixelsPerCSSPixel; + + let widgetToolkit = content.SpecialPowers. + Cc["@mozilla.org/xre/app-info;1"]. + getService(content.SpecialPowers.Ci.nsIXULRuntime). + widgetToolkit; + let nativeMsg = widgetToolkit == "cocoa" ? 5 : // NSMouseMoved + widgetToolkit == "windows" ? 1 : // MOUSEEVENTF_MOVE + 3; // GDK_MOTION_NOTIFY + + row.addEventListener("mousemove", function onMove() { + row.removeEventListener("mousemove", onMove); + ack(); + }); + utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null); }, - click: function (arg) { - let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg; - let row; - if (eltIdx == -1) { - row = gController._table.firstChild; - } - else { - let allElts = [...gController._suggestionsList.children, - ...gController._oneOffButtons, - content.document.getElementById("contentSearchSettingsButton")]; - row = allElts[eltIdx]; - } - let event = arg.modifiers || {}; - // synthesizeMouseAtCenter defaults to sending a mousedown followed by a - // mouseup if the event type is not specified. - content.synthesizeMouseAtCenter(row, event); - ack("click"); + mousedown: function (suggestionIdx) { + gController.onClick = () => { + gController.onClick = null; + ack(); + }; + let row = gController._table.children[suggestionIdx]; + content.sendMouseEvent({ type: "mousedown" }, row); }, addInputValueToFormHistory: function () { gController.addInputValueToFormHistory(); - ack("addInputValueToFormHistory"); + ack(); }, reset: function () { // Reset both the input and suggestions by select all + delete. gController.input.focus(); content.synthesizeKey("a", { accelKey: true }); content.synthesizeKey("VK_DELETE", {}); - ack("reset"); + ack(); }, }; -function ack(aType, aData) { - sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() }); +function ack() { + sendAsyncMessage(TEST_MSG, currentState()); } function waitForSuggestions(cb) { let observer = new content.MutationObserver(() => { if (gController.input.getAttribute("aria-expanded") == "true") { observer.disconnect(); cb(); } }); observer.observe(gController.input, { attributes: true, attributeFilter: ["aria-expanded"], }); } -function waitForContentSearchEvent(messageType, cb) { - let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"]. - getService(content.SpecialPowers.Ci.nsIMessageListenerManager); - mm.addMessageListener("ContentSearch", function listener(aMsg) { - if (aMsg.data.type != messageType) { - return; - } - mm.removeMessageListener("ContentSearch", listener); - cb(aMsg.data.data); - }); -} - function currentState() { let state = { selectedIndex: gController.selectedIndex, - numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions, + numSuggestions: gController.numSuggestions, suggestionAtIndex: [], isFormHistorySuggestionAtIndex: [], tableHidden: gController._table.hidden, + tableChildrenLength: gController._table.children.length, + tableChildren: [], inputValue: gController.input.value, ariaExpanded: gController.input.getAttribute("aria-expanded"), }; - if (state.numSuggestions) { - for (let i = 0; i < gController.numSuggestions; i++) { - state.suggestionAtIndex.push(gController.suggestionAtIndex(i)); - state.isFormHistorySuggestionAtIndex.push( - gController.isFormHistorySuggestionAtIndex(i)); - } + for (let i = 0; i < gController.numSuggestions; i++) { + state.suggestionAtIndex.push(gController.suggestionAtIndex(i)); + state.isFormHistorySuggestionAtIndex.push( + gController.isFormHistorySuggestionAtIndex(i)); + } + + for (let child of gController._table.children) { + state.tableChildren.push({ + textContent: child.textContent, + classes: new Set(child.className.split(/\s+/)), + }); } return state; } })();
--- a/browser/base/content/test/newtab/browser_newtab_search.js +++ b/browser/base/content/test/newtab/browser_newtab_search.js @@ -71,16 +71,23 @@ let runTaskifiedTests = Task.async(funct yield addNewTabPageTabPromise(); // 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 engine without any logos and switch to it. let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO); Services.search.currentEngine = noLogoEngine; yield promiseSearchEvents(["CurrentEngine"]); yield checkCurrentEngine(ENGINE_NO_LOGO); // Add the engine with favicon and switch to it. let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON); @@ -101,24 +108,37 @@ let runTaskifiedTests = Task.async(funct yield checkCurrentEngine(ENGINE_2X_LOGO); // Add the engine with 1x- and 2x-DPI logos and switch to it. let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO); Services.search.currentEngine = logo1x2xEngine; yield promiseSearchEvents(["CurrentEngine"]); yield checkCurrentEngine(ENGINE_1X_2X_LOGO); + // Click the logo to open the search panel. + yield Promise.all([ + promisePanelShown(panel), + promiseClick(logoImg()), + ]); + + let manageBox = $("manage"); + ok(!!manageBox, "The Manage Engines box should be present in the document"); + is(panel.childNodes.length, 1, "Search panel should only contain the Manage Engines entry"); + is(panel.childNodes[0], manageBox, "Search panel should contain the Manage Engines entry"); + + panel.hidePopup(); + // Add the engine that provides search suggestions and switch to it. let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS); Services.search.currentEngine = suggestionEngine; yield promiseSearchEvents(["CurrentEngine"]); yield checkCurrentEngine(ENGINE_SUGGESTIONS); // Avoid intermittent failures. - gSearch().remoteTimeout = 5000; + gSearch()._suggestionController.remoteTimeout = 5000; // Type an X in the search input. This is only a smoke test. See // browser_searchSuggestionUI.js for comprehensive content search suggestion // UI tests. let input = $("text"); input.focus(); EventUtils.synthesizeKey("x", {}); let suggestionsPromise = promiseSearchEvents(["Suggestions"]); @@ -284,36 +304,51 @@ function blobToBase64(blob) { let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, logoPrefix2x}) { let engine = Services.search.currentEngine; ok(engine.name.includes(basename), "Sanity check: current engine: engine.name=" + engine.name + " basename=" + basename); // gSearch.currentEngineName - is(gSearch().defaultEngine.name, engine.name, + is(gSearch().currentEngineName, engine.name, "currentEngineName: " + engine.name); }); +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"); + 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 searchPanel() { + return $("panel"); +} + function logoImg() { return $("logo"); } function gSearch() { - return getContentWindow().gSearch._contentSearchController; + return getContentWindow().gSearch; } /** * Waits for a load (or custom) event to finish in a given tab. If provided * load an uri into the tab. * * @param tab * The tab to load into.
--- a/browser/base/content/test/newtab/head.js +++ b/browser/base/content/test/newtab/head.js @@ -717,33 +717,24 @@ function whenPagesUpdated(aCallback = Te }); } /** * Waits for the response to the page's initial search state request. */ function whenSearchInitDone() { let deferred = Promise.defer(); - let searchController = getContentWindow().gSearch._contentSearchController; - if (searchController.defaultEngine) { + if (getContentWindow().gSearch._initialStateReceived) { return Promise.resolve(); } let eventName = "ContentSearchService"; getContentWindow().addEventListener(eventName, function onEvent(event) { if (event.detail.type == "State") { getContentWindow().removeEventListener(eventName, onEvent); - // Wait for the search controller to receive the event, then resolve. - let resolver = function() { - if (searchController.defaultEngine) { - deferred.resolve(); - return; - } - executeSoon(resolver); - } - executeSoon(resolver); + deferred.resolve(); } }); return deferred.promise; } /** * Changes the newtab customization option and waits for the panel to open and close *
--- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -139,18 +139,18 @@ browser.jar: #endif content/browser/safeMode.css (content/safeMode.css) content/browser/safeMode.js (content/safeMode.js) content/browser/safeMode.xul (content/safeMode.xul) * content/browser/sanitize.js (content/sanitize.js) * content/browser/sanitize.xul (content/sanitize.xul) * content/browser/sanitizeDialog.js (content/sanitizeDialog.js) content/browser/sanitizeDialog.css (content/sanitizeDialog.css) - content/browser/contentSearchUI.js (content/contentSearchUI.js) - content/browser/contentSearchUI.css (content/contentSearchUI.css) + content/browser/searchSuggestionUI.js (content/searchSuggestionUI.js) + content/browser/searchSuggestionUI.css (content/searchSuggestionUI.css) content/browser/tabbrowser.css (content/tabbrowser.css) content/browser/tabbrowser.xml (content/tabbrowser.xml) content/browser/urlbarBindings.xml (content/urlbarBindings.xml) * content/browser/utilityOverlay.js (content/utilityOverlay.js) content/browser/web-panels.js (content/web-panels.js) * content/browser/web-panels.xul (content/web-panels.xul) * content/browser/baseMenuOverlay.xul (content/baseMenuOverlay.xul) * content/browser/nsContextMenu.js (content/nsContextMenu.js)
--- a/browser/components/loop/test/mochitest/browser_mozLoop_context.js +++ b/browser/components/loop/test/mochitest/browser_mozLoop_context.js @@ -25,19 +25,17 @@ add_task(function* test_mozLoop_getSelec let tab = gBrowser.selectedTab = gBrowser.addTab(); yield promiseTabLoadEvent(tab, "about:home"); metadata = yield promiseGetMetadata(); Assert.strictEqual(metadata.url, null, "URL should be empty for about:home"); Assert.strictEqual(metadata.favicon, null, "Favicon should be empty for about:home"); Assert.ok(metadata.title, "Title should be set for about:home"); - // Filter out null elements in the previews - contentSearchUI adds some img - // elements with chrome:// srcs, which show up as null in metadata.previews. - Assert.deepEqual(metadata.previews.filter(e => e), [], "No previews available for about:home"); + Assert.deepEqual(metadata.previews, [], "No previews available for about:home"); gBrowser.removeTab(tab); }); add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() { let tab = gBrowser.selectedTab = gBrowser.addTab(); yield promiseTabLoadEvent(tab, "http://example.com/"); let metadata = yield promiseGetMetadata();
--- a/browser/locales/en-US/chrome/browser/aboutHome.dtd +++ b/browser/locales/en-US/chrome/browser/aboutHome.dtd @@ -6,17 +6,17 @@ %brandDTD; <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd"> %syncBrandDTD; <!-- These strings are used in the about:home page --> <!ENTITY abouthome.pageTitle "&brandFullName; Start Page"> -<!ENTITY abouthome.search.placeholder "Search"> +<!ENTITY abouthome.searchEngineButton.label "Search"> <!-- LOCALIZATION NOTE (abouthome.defaultSnippet1.v1): text in <a/> will be linked to the Firefox features page on mozilla.com --> <!ENTITY abouthome.defaultSnippet1.v1 "Thanks for choosing Firefox! To get the most out of your browser, learn more about the <a>latest features</a>."> <!-- LOCALIZATION NOTE (abouthome.defaultSnippet2.v1): text in <a/> will be linked to the featured add-ons on addons.mozilla.org -->
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -416,22 +416,16 @@ These should match what Safari and other is a fundamental keybinding and we are maintaining a XP binding so that it is easy for people to switch to Linux. --> <!ENTITY searchFocus.commandkey "k"> <!ENTITY searchFocus.commandkey2 "e"> <!ENTITY searchFocusUnix.commandkey "j"> -<!-- LOCALIZATION NOTE (contentSearchInput.label, contentSearchSubmit.label): - These are set as the aria-label attribute for the search input box and - submit button in the in-content search UI, to be used by screen readers. --> -<!ENTITY contentSearchInput.label "Search query"> -<!ENTITY contentSearchSubmit.label "Submit search"> - <!-- LOCALIZATION NOTE (searchFor.label, searchWith.label): These two strings are used to build the header above the list of one-click search providers: "Search for <used typed keywords> with:" --> <!ENTITY searchFor.label "Search for "> <!ENTITY searchWith.label " with:"> <!-- LOCALIZATION NOTE (search.label, searchAfter.label): This string is used to build the header above the list of one-click search
--- a/browser/locales/en-US/chrome/browser/search.properties +++ b/browser/locales/en-US/chrome/browser/search.properties @@ -27,20 +27,8 @@ cmd_showSuggestions_accesskey=S # a search engine offered by a web page. Each engine is displayed as a # menuitem at the bottom of the search panel. cmd_addFoundEngine=Add "%S" # LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines # are offered by a web page, instead of listing all of them in the # search panel using the cmd_addFoundEngine string, they will be # grouped in a submenu using cmd_addFoundEngineMenu as a label. cmd_addFoundEngineMenu=Add search engine - -# LOCALIZATION NOTE (searchFor, searchWith): -# These two strings are used to build the header above the list of one-click -# search providers: "Search for <user-typed keywords> with:" -searchFor=Search for -searchWith= with: - -# LOCALIZATION NOTE (searchWithHeader): -# The wording of this string should be as close as possible to -# searchFor and searchWith. This string will be used instead of -# them when the user has not typed any keyword. -searchWithHeader=Search with:
--- a/browser/modules/AboutHome.jsm +++ b/browser/modules/AboutHome.jsm @@ -95,24 +95,36 @@ let AboutHome = { "AboutHome:Downloads", "AboutHome:Bookmarks", "AboutHome:History", "AboutHome:Apps", "AboutHome:Addons", "AboutHome:Sync", "AboutHome:Settings", "AboutHome:RequestUpdate", + "AboutHome:Search", + "AboutHome:OpenSearchPanel", ], init: function() { let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); for (let msg of this.MESSAGES) { mm.addMessageListener(msg, this); } + + Services.obs.addObserver(this, "browser-search-engine-modified", false); + }, + + observe: function(aEngine, aTopic, aVerb) { + switch (aTopic) { + case "browser-search-engine-modified": + this.sendAboutHomeData(null); + break; + } }, receiveMessage: function(aMessage) { let window = aMessage.target.ownerDocument.defaultView; switch (aMessage.name) { case "AboutHome:RestorePreviousSession": let ss = Cc["@mozilla.org/browser/sessionstore;1"]. @@ -162,33 +174,105 @@ let AboutHome = { case "AboutHome:Settings": window.openPreferences(); break; case "AboutHome:RequestUpdate": this.sendAboutHomeData(aMessage.target); break; + + case "AboutHome:Search": + let data; + try { + data = JSON.parse(aMessage.data.searchData); + } catch(ex) { + Cu.reportError(ex); + break; + } + + Services.search.init(function(status) { + if (!Components.isSuccessCode(status)) { + return; + } + + let engine = Services.search.currentEngine; + if (AppConstants.MOZ_SERVICES_HEALTHREPORT) { + window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome", data.selection); + } + + // Trigger a search through nsISearchEngine.getSubmission() + let submission = engine.getSubmission(data.searchTerms, null, "homepage"); + let where = window.whereToOpenLink(data.originalEvent); + + // There is a chance that by the time we receive the search message, the + // user has switched away from the tab that triggered the search. If, + // based on the event, we need to load the search in the same tab that + // triggered it (i.e. where == "current"), openUILinkIn will not work + // because that tab is no longer the current one. For this case we + // manually load the URI in the target browser. + if (where == "current") { + aMessage.target.loadURIWithFlags(submission.uri.spec, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, null, submission.postData); + } else { + let params = { + postData: submission.postData, + inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"), + }; + window.openLinkIn(submission.uri.spec, where, params); + } + // Used for testing + let mm = aMessage.target.messageManager; + mm.sendAsyncMessage("AboutHome:SearchTriggered", aMessage.data.searchData); + }); + + break; + + case "AboutHome:OpenSearchPanel": + let panel = window.document.getElementById("abouthome-search-panel"); + let anchor = aMessage.objects.anchor; + panel.hidden = false; + panel.openPopup(anchor); + anchor.setAttribute("active", "true"); + panel.addEventListener("popuphidden", function onHidden() { + panel.removeEventListener("popuphidden", onHidden); + anchor.removeAttribute("active"); + }); + break; } }, // Send all the chrome-privileged data needed by about:home. This // gets re-sent when the search engine changes. sendAboutHomeData: function(target) { let wrapper = {}; Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm", wrapper); let ss = wrapper.SessionStore; ss.promiseInitialized.then(function() { + let deferred = Promise.defer(); + + Services.search.init(function (status){ + if (!Components.isSuccessCode(status)) { + deferred.reject(status); + } else { + deferred.resolve(Services.search.defaultEngine.name); + } + }); + + return deferred.promise; + }).then(function(engineName) { let data = { showRestoreLastSession: ss.canRestoreLastSession, snippetsURL: AboutHomeUtils.snippetsURL, showKnowYourRights: AboutHomeUtils.showKnowYourRights, snippetsVersion: AboutHomeUtils.snippetsVersion, + defaultEngineName: engineName }; if (AboutHomeUtils.showKnowYourRights) { // Set pref to indicate we've shown the notification. let currentVersion = Services.prefs.getIntPref("browser.rights.version"); Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true); } @@ -196,10 +280,19 @@ let AboutHome = { target.messageManager.sendAsyncMessage("AboutHome:Update", data); } else { let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager); mm.broadcastAsyncMessage("AboutHome:Update", data); } }).then(null, function onError(x) { Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x); }); + }, + + /** + * Focuses the search input in the page with the given message manager. + * @param messageManager + * The MessageManager object of the selected browser. + */ + focusInput: function (messageManager) { + messageManager.sendAsyncMessage("AboutHome:FocusInput"); } };
--- a/browser/modules/ContentSearch.jsm +++ b/browser/modules/ContentSearch.jsm @@ -40,28 +40,25 @@ const MAX_SUGGESTIONS = 6; * Adds an entry to the search form history. * data: the entry, a string * GetSuggestions * Retrieves an array of search suggestions given a search string. * data: { engineName, searchString, [remoteTimeout] } * GetState * Retrieves the current search engine state. * data: null - * GetStrings - * Retrieves localized search UI strings. - * data: null * ManageEngines * Opens the search engine management window. * data: null * RemoveFormHistoryEntry * Removes an entry from the search form history. * data: the entry, a string * Search * Performs a search. - * data: { engineName, searchString, healthReportKey, searchPurpose } + * data: { engineName, searchString, whence } * SetCurrentEngine * Sets the current engine. * data: the name of the engine * SpeculativeConnect * Speculatively connects to an engine. * data: the name of the engine * * Outbound messages have the following types: @@ -70,19 +67,16 @@ const MAX_SUGGESTIONS = 6; * Broadcast when the current engine changes. * data: see _currentEngineObj * CurrentState * Broadcast when the current search state changes. * data: see _currentStateObj * State * Sent in reply to GetState. * data: see _currentStateObj - * Strings - * Sent in reply to GetStrings - * data: Object containing string names and values for the current locale. * Suggestions * Sent in reply to GetSuggestions. * data: see _onMessageGetSuggestions */ this.ContentSearch = { // Inbound events are queued and processed in FIFO order instead of handling @@ -99,34 +93,19 @@ this.ContentSearch = { _destroyedPromise: null, init: function () { Cc["@mozilla.org/globalmessagemanager;1"]. getService(Ci.nsIMessageListenerManager). addMessageListener(INBOUND_MESSAGE, this); Services.obs.addObserver(this, "browser-search-engine-modified", false); Services.obs.addObserver(this, "shutdown-leaks-before-check", false); - Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false); this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties"); }, - get searchSuggestionUIStrings() { - if (this._searchSuggestionUIStrings) { - return this._searchSuggestionUIStrings; - } - this._searchSuggestionUIStrings = {}; - let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties"); - let stringNames = ["searchHeader", "searchPlaceholder", "searchFor", - "searchWith", "searchWithHeader"]; - for (let name of stringNames) { - this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name); - } - return this._searchSuggestionUIStrings; - }, - destroy: function () { if (this._destroyedPromise) { return this._destroyedPromise; } Cc["@mozilla.org/globalmessagemanager;1"]. getService(Ci.nsIMessageListenerManager). removeMessageListener(INBOUND_MESSAGE, this); @@ -169,17 +148,16 @@ this.ContentSearch = { type: "Message", data: msg, }); this._processEventQueue(); }, observe: function (subj, topic, data) { switch (topic) { - case "nsPref:changed": case "browser-search-engine-modified": this._eventQueue.push({ type: "Observe", data: data, }); this._processEventQueue(); break; case "shutdown-leaks-before-check": @@ -218,29 +196,24 @@ this.ContentSearch = { }), _onMessageGetState: function (msg, data) { return this._currentStateObj().then(state => { this._reply(msg, "State", state); }); }, - _onMessageGetStrings: function (msg, data) { - this._reply(msg, "Strings", this.searchSuggestionUIStrings); - }, - _onMessageSearch: function (msg, data) { this._ensureDataHasProperties(data, [ "engineName", "searchString", - "healthReportKey", - "searchPurpose", + "whence", ]); let engine = Services.search.getEngineByName(data.engineName); - let submission = engine.getSubmission(data.searchString, "", data.searchPurpose); + let submission = engine.getSubmission(data.searchString, "", data.whence); let browser = msg.target; let win; try { win = browser.ownerDocument.defaultView; } catch (err) { // The browser may have been closed between the time its content sent the // message and the time we handle it. In that case, trying to call any @@ -261,17 +234,17 @@ this.ContentSearch = { submission.postData); } else { let params = { postData: submission.postData, inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"), }; win.openUILinkIn(submission.uri.spec, where, params); } - win.BrowserSearch.recordSearchInHealthReport(engine, data.healthReportKey, + win.BrowserSearch.recordSearchInHealthReport(engine, data.whence, data.selection || null); return Promise.resolve(); }, _onMessageSetCurrentEngine: function (msg, data) { Services.search.currentEngine = Services.search.getEngineByName(data); return Promise.resolve(); }, @@ -436,22 +409,17 @@ this.ContentSearch = { }]; }, _currentStateObj: Task.async(function* () { let state = { engines: [], currentEngine: yield this._currentEngineObj(), }; - let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs"); - let hiddenList = pref ? pref.split(",") : []; for (let engine of Services.search.getVisibleEngines()) { - if (hiddenList.indexOf(engine.name) != -1) { - continue; - } let uri = engine.getIconURLBySize(16, 16); state.engines.push({ name: engine.name, iconBuffer: yield this._arrayBufferFromDataURI(uri), }); } return state; }),
--- a/browser/modules/DirectoryLinksProvider.jsm +++ b/browser/modules/DirectoryLinksProvider.jsm @@ -668,17 +668,17 @@ let DirectoryLinksProvider = { * @param action String of the behavior to report * @param triggeringSiteIndex optional Int index of the site triggering action * @return download promise */ reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) { let pastImpressions; // Check if the suggested tile was shown if (action == "view") { - sites.slice(0, triggeringSiteIndex + 1).filter(s => s).forEach(site => { + sites.slice(0, triggeringSiteIndex + 1).forEach(site => { let {targetedSite, url} = site.link; if (targetedSite) { this._addFrequencyCapView(url); } }); } // any click action on a suggested tile should stop that tile suggestion // click/block - user either removed a tile or went to a landing page
--- a/browser/modules/test/browser_ContentSearch.js +++ b/browser/modules/test/browser_ContentSearch.js @@ -89,18 +89,17 @@ add_task(function* modifyEngine() { }); add_task(function* search() { yield addTab(); let engine = Services.search.currentEngine; let data = { engineName: engine.name, searchString: "ContentSearchTest", - healthReportKey: "ContentSearchTest", - searchPurpose: "ContentSearchTest", + whence: "ContentSearchTest", }; gMsgMan.sendAsyncMessage(TEST_MSG, { type: "Search", data: data, }); let submissionURL = engine.getSubmission(data.searchString, "", data.whence).uri.spec; yield waitForLoadAndStopIt(gBrowser.selectedBrowser, submissionURL); @@ -112,18 +111,17 @@ add_task(function* searchInBackgroundTab // search page should be loaded in the same tab that performed the search, in // the background tab. yield addTab(); let searchBrowser = gBrowser.selectedBrowser; let engine = Services.search.currentEngine; let data = { engineName: engine.name, searchString: "ContentSearchTest", - healthReportKey: "ContentSearchTest", - searchPurpose: "ContentSearchTest", + whence: "ContentSearchTest", }; gMsgMan.sendAsyncMessage(TEST_MSG, { type: "Search", data: data, }); let newTab = gBrowser.addTab(); gBrowser.selectedTab = newTab;
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -88,18 +88,16 @@ browser.jar: skin/classic/browser/reload-stop-go@2x.png skin/classic/browser/searchbar.css skin/classic/browser/search-pref.png (../shared/search/search-pref.png) skin/classic/browser/search-indicator.png (../shared/search/search-indicator.png) skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png) skin/classic/browser/badge-add-engine.png (../shared/search/badge-add-engine.png) skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png) skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg) - skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg) - skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg) skin/classic/browser/Security-broken.png skin/classic/browser/setDesktopBackground.css skin/classic/browser/slowStartup-16.png skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png) skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar-inverted.png skin/classic/browser/Toolbar-small.png skin/classic/browser/undoCloseTab.png (../shared/undoCloseTab.png)
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -119,18 +119,16 @@ browser.jar: skin/classic/browser/search-indicator@2x.png (../shared/search/search-indicator@2x.png) skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png) skin/classic/browser/search-engine-placeholder@2x.png (../shared/search/search-engine-placeholder@2x.png) skin/classic/browser/badge-add-engine.png (../shared/search/badge-add-engine.png) skin/classic/browser/badge-add-engine@2x.png (../shared/search/badge-add-engine@2x.png) skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png) skin/classic/browser/search-indicator-badge-add@2x.png (../shared/search/search-indicator-badge-add@2x.png) skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg) - skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg) - skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg) skin/classic/browser/slowStartup-16.png skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png) skin/classic/browser/theme-switcher-icon@2x.png (../shared/theme-switcher-icon@2x.png) skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar@2x.png skin/classic/browser/Toolbar-inverted.png skin/classic/browser/Toolbar-inverted@2x.png skin/classic/browser/toolbarbutton-dropmarker.png
deleted file mode 100644 --- a/browser/themes/shared/search/search-arrow-go.svg +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.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/. --> -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16"> - <style> - use:not(:target) { - display: none; - } - use { - fill: #616366; - } - use[id$="-inverted"] { - fill: #fff; - } - </style> - <defs> - <path id="search-arrow-go-glyph" d="M1,7v2.2C1,9.8,1.4,10,2,10h7.5l-3,3.1c-0.4,0.3-0.4,1,0,1.4l0.8,0.8 c0.4,0.4,1,0.4,1.4,0l6.6-6.6c0.4-0.4,0.4-1,0-1.4L8.7,0.7c-0.4-0.4-1-0.4-1.4,0L6.5,1.6C6.1,2,6.1,2.6,6.5,3l3,3H2C1.4,6,1,6.4,1,7z"/> - </defs> - <use id="search-arrow-go" xlink:href="#search-arrow-go-glyph"/> - <use id="search-arrow-go-inverted" xlink:href="#search-arrow-go-glyph"/> -</svg>
deleted file mode 100644 --- a/browser/themes/shared/search/search-indicator-magnifying-glass.svg +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this - - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> - <path fill="#808080" d="M21.7,20.3l-1.4,1.4l-5.4-5.4c-1.3,1-3,1.7-4.9,1.7 c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8c4.4,0,8,3.6,8,8c0,1.8-0.6,3.5-1.7,4.9L21.7,20.3z M10,4c-3.3,0-6,2.7-6,6s2.7,6,6,6s6-2.7,6-6 S13.3,4,10,4z"/> -</svg>
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -112,18 +112,16 @@ browser.jar: skin/classic/browser/search-indicator@2x.png (../shared/search/search-indicator@2x.png) skin/classic/browser/search-engine-placeholder.png (../shared/search/search-engine-placeholder.png) skin/classic/browser/search-engine-placeholder@2x.png (../shared/search/search-engine-placeholder@2x.png) skin/classic/browser/badge-add-engine.png (../shared/search/badge-add-engine.png) skin/classic/browser/badge-add-engine@2x.png (../shared/search/badge-add-engine@2x.png) skin/classic/browser/search-indicator-badge-add.png (../shared/search/search-indicator-badge-add.png) skin/classic/browser/search-indicator-badge-add@2x.png (../shared/search/search-indicator-badge-add@2x.png) skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg) - skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg) - skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg) skin/classic/browser/setDesktopBackground.css skin/classic/browser/slowStartup-16.png skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png) skin/classic/browser/Toolbar.png skin/classic/browser/Toolbar@2x.png skin/classic/browser/Toolbar-aero.png skin/classic/browser/Toolbar-aero@2x.png skin/classic/browser/Toolbar-inverted.png