Backed out changesets 7f2ddcfe4537 and e88770aa2160 (bug 1171344) for intermittent OSX browser_tabopen_reflows.js failures.

--HG--
rename : browser/base/content/contentSearchUI.css => browser/base/content/searchSuggestionUI.css
rename : browser/base/content/contentSearchUI.js => browser/base/content/searchSuggestionUI.js
rename : browser/base/content/test/general/browser_contentSearchUI.js => browser/base/content/test/general/browser_searchSuggestionUI.js
rename : browser/base/content/test/general/contentSearchUI.html => browser/base/content/test/general/searchSuggestionUI.html
rename : browser/base/content/test/general/contentSearchUI.js => browser/base/content/test/general/searchSuggestionUI.js
This commit is contained in:
Ryan VanderMeulen 2015-07-16 21:42:22 -04:00
Родитель 9ddcd2a3b5
Коммит d7e5192373
38 изменённых файлов: 1849 добавлений и 1894 удалений

Просмотреть файл

@ -22,4 +22,4 @@
# 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

Просмотреть файл

@ -49,57 +49,78 @@ a {
background-repeat: no-repeat;
}
#searchIconAndTextContainer,
#searchForm,
#snippets {
width: 470px;
}
#searchIconAndTextContainer {
#searchForm {
display: -moz-box;
height: 36px;
position: relative;
}
#searchLogoContainer {
display: -moz-box;
-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);
@ -107,27 +128,25 @@ a {
#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;
@ -135,16 +154,15 @@ a {
}
#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),
@ -162,9 +180,9 @@ a {
#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] {
@ -174,7 +192,7 @@ a {
#defaultSnippet1:-moz-dir(rtl),
#defaultSnippet2:-moz-dir(rtl),
#rightsSnippet:-moz-dir(rtl) {
background-position: right 0 center;
background-position: right 30px center;
}
#defaultSnippet1 {
@ -229,7 +247,7 @@ body[narrow] #launcher[session] {
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;
@ -378,6 +396,10 @@ body[narrow] #restorePreviousSession::before {
background-image: url("chrome://branding/content/about-logo@2x.png");
}
#searchIcon {
background-image: url("chrome://browser/skin/magnifier@2x.png");
}
#defaultSnippet1,
#defaultSnippet2,
#rightsSnippet {

Просмотреть файл

@ -4,6 +4,138 @@
"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,\
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"
}
};
// The process of adding a new default snippet involves:
// * add a new entity to aboutHome.dtd
// * add a <span/> for it in aboutHome.xhtml
@ -26,7 +158,8 @@ const SNIPPETS_OBJECTSTORE_NAME = "snippets";
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;
@ -41,7 +174,6 @@ window.addEventListener("pageshow", function () {
// later and may use asynchronous getters.
window.gObserver.observe(document.documentElement, { attributes: true });
fitToWidth();
setupSearch();
window.addEventListener("resize", fitToWidth);
// Ask chrome to update snippets.
@ -168,13 +300,52 @@ function ensureSnippetsMapThen(aCallback)
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
@ -184,12 +355,29 @@ function setupSearch()
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;
}
/**

Просмотреть файл

@ -24,14 +24,14 @@
<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;">
@ -39,12 +39,14 @@
<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">

Просмотреть файл

@ -3467,8 +3467,9 @@ const BrowserSearch = {
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");

Просмотреть файл

@ -1,152 +0,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/. */
.contentSearchSuggestionTable {
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);
position: absolute;
left: 0;
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 {
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");
}
.contentSearchHeader > img {
height: 16px;
width: 16px;
margin: 0;
padding: 0;
}
.contentSearchSuggestionRow.remote > td > .historyIcon {
visibility: hidden;
}
.contentSearchSuggestionRow.selected {
background-color: Highlight;
color: HighlightText;
}
.contentSearchHeader,
.contentSearchSuggestionEntry {
margin: 0;
max-width: inherit;
overflow: hidden;
padding: 4px 10px;
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%);
}

Просмотреть файл

@ -1,735 +0,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/. */
"use strict";
this.ContentSearchUIController = (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 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
* text box and styled so that it appears as a dropdown below the text box.
*
* @param inputElement
* Search suggestions will be based on the text in this text box.
* 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 idPrefix
* The IDs of elements created by the object will be prefixed with this
* string.
*/
function ContentSearchUIController(inputElement, tableParent, healthReportKey,
searchPurpose, idPrefix="") {
this.input = inputElement;
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();
}
ContentSearchUIController.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;
},
set defaultEngine(val) {
this._defaultEngine = val;
this._updateDefaultEngineHeader();
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")) {
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;
if (i == idx) {
elt.classList.add("selected");
ariaSelectedElt.setAttribute("aria-selected", "true");
this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
}
else {
elt.classList.remove("selected");
ariaSelectedElt.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;
},
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();
},
suggestionAtIndex: function (idx) {
let row = this._suggestionsList.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.selectAndUpdateInput(-1);
}
},
isFormHistorySuggestionAtIndex: function (idx) {
let row = this._suggestionsList.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");
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;
}
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();
},
_onKeypress: function (event) {
let selectedIndexDelta = 0;
switch (event.keyCode) {
case event.DOM_VK_UP:
if (!this._table.hidden) {
selectedIndexDelta = -1;
}
break;
case event.DOM_VK_DOWN:
if (this._table.hidden) {
this._getSuggestions();
}
else {
selectedIndexDelta = 1;
}
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) {
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;
}
else if (this.numSuggestions + this._oneOffButtons.length < 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);
},
_onMouseup: function (event) {
if (event.button == 2) {
return;
}
this._onCommand(event);
},
_onClick: function (event) {
this._onMouseup(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) {
return;
}
this._clearSuggestionRows();
// Position and size the table.
let { left } = this.input.getBoundingClientRect();
this._table.style.top = this.input.offsetHeight + "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;
if (i < suggestions.formHistory.length) {
[type, idx] = ["formHistory", i];
}
else {
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));
}
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));
},
_speculativeConnect: function () {
if (this.defaultEngine) {
this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
}
},
_makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
let row = document.createElementNS(HTML_NS, "tr");
row.dir = "auto";
row.classList.add("contentSearchSuggestionRow");
row.classList.add(type);
row.setAttribute("role", "presentation");
row.addEventListener("mousemove", this);
row.addEventListener("mouseup", 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.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");
if (searchWords.has(word)) {
wordSpan.classList.add("typed");
}
wordSpan.textContent = word;
entry.appendChild(wordSpan);
if (i < suggestionWords.length - 1) {
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) {
this._sendMsg("GetSuggestions", {
engineName: this.defaultEngine.name,
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;
},
_indexOfTableItem: function (elt) {
if (elt.classList.contains("contentSearchOneOffItem")) {
return this.numSuggestions + this._oneOffButtons.indexOf(elt);
}
if (elt.classList.contains("contentSearchSettingsButton")) {
return this.numSuggestions + this._oneOffButtons.length;
}
while (elt && elt.localName != "tr") {
elt = elt.parentNode;
}
if (!elt) {
throw new Error("Element is not a row");
}
return elt.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);
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;
})();

Просмотреть файл

@ -330,6 +330,7 @@ input[type=button] {
#newtab-search-container {
display: -moz-box;
position: relative;
-moz-box-align: center;
-moz-box-pack: center;
}
@ -340,76 +341,104 @@ input[type=button] {
#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,
@ -420,16 +449,15 @@ input[type=button] {
}
#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),
@ -516,11 +544,14 @@ input[type=button] {
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;
@ -529,12 +560,22 @@ input[type=button] {
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;
@ -584,7 +625,8 @@ input[type=button] {
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;
@ -629,7 +671,7 @@ input[type=button] {
border-top: 1px solid threedshadow;
}
.contentSearchSuggestionTable {
.searchSuggestionTable {
font: message-box;
font-size: 16px;
}

Просмотреть файл

@ -5,13 +5,15 @@
- 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;
]>
@ -20,6 +22,13 @@
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>
@ -99,13 +108,13 @@
</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">
@ -124,7 +133,7 @@
</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>

Просмотреть файл

@ -5,11 +5,257 @@
#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;
},
};

Просмотреть файл

@ -0,0 +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/. */
.searchSuggestionTable {
background-color: hsla(0,0%,100%,.99);
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;
text-align: start;
z-index: 1001;
}
.searchSuggestionRow {
cursor: default;
margin: 0;
max-width: inherit;
padding: 0;
}
.searchSuggestionRow.formHistory + .searchSuggestionRow.remote > td {
border-top: 1px solid GrayText;
}
.searchSuggestionRow.selected {
background-color: hsl(210,100%,40%);
color: hsl(0,0%,100%);
}
.searchSuggestionEntry {
margin: 0;
max-width: inherit;
overflow: hidden;
padding: 6px 8px;
text-overflow: ellipsis;
white-space: nowrap;
}

Просмотреть файл

@ -0,0 +1,400 @@
/* 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.SearchSuggestionUIController = (function () {
const MAX_DISPLAYED_SUGGESTIONS = 6;
const SUGGESTION_ID_PREFIX = "searchSuggestion";
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
* text box and styled so that it appears as a dropdown below the text box.
*
* @param inputElement
* Search suggestions will be based on the text in this text box.
* 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 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 SearchSuggestionUIController(inputElement, tableParent, onClick=null,
idPrefix="") {
this.input = inputElement;
this.onClick = onClick;
this._idPrefix = idPrefix;
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._ignoreInputEvent = false;
}
SearchSuggestionUIController.prototype = {
// The timeout (ms) of the remote suggestions. Corresponds to
// SearchSuggestionController.remoteTimeout. Uses
// SearchSuggestionController's default timeout if falsey.
remoteTimeout: undefined,
get engineName() {
return this._engineName;
},
set engineName(val) {
this._engineName = val;
if (val && document.activeElement == this.input) {
this._speculativeConnect();
}
},
get selectedIndex() {
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");
for (let i = 0; i < this._table.children.length; i++) {
let row = this._table.children[i];
if (i == idx) {
row.classList.add("selected");
row.firstChild.setAttribute("aria-selected", "true");
this._table.setAttribute("aria-activedescendant", row.firstChild.id);
}
else {
row.classList.remove("selected");
row.firstChild.setAttribute("aria-selected", "false");
}
}
},
get numSuggestions() {
return this._table.children.length;
},
selectAndUpdateInput: function (idx) {
this.selectedIndex = idx;
this.input.value = idx >= 0 ? this.suggestionAtIndex(idx) :
this._stickyInputValue;
},
suggestionAtIndex: function (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._table.children[idx].remove();
this.selectAndUpdateInput(-1);
}
},
isFormHistorySuggestionAtIndex: function (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);
},
_onInput: function (event) {
if (this._ignoreInputEvent) {
return;
}
if (this.input.value) {
this._getSuggestions();
}
else {
this._stickyInputValue = "";
this._hideSuggestions();
}
this.selectedIndex = -1;
},
_onKeypress: function (event) {
let selectedIndexDelta = 0;
switch (event.keyCode) {
case event.DOM_VK_UP:
if (this.numSuggestions) {
selectedIndexDelta = -1;
}
break;
case event.DOM_VK_DOWN:
if (this.numSuggestions) {
selectedIndexDelta = 1;
}
else {
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;
}
// 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_DELETE:
if (this.selectedIndex >= 0) {
this.deleteSuggestionAtIndex(this.selectedIndex);
}
break;
default:
return;
}
if (selectedIndexDelta) {
// Update the selection.
let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
if (newSelectedIndex < -1) {
newSelectedIndex = this.numSuggestions - 1;
}
else if (this.numSuggestions <= newSelectedIndex) {
newSelectedIndex = -1;
}
this.selectAndUpdateInput(newSelectedIndex);
// Prevent the input's caret from moving.
event.preventDefault();
}
},
_onFocus: function () {
this._speculativeConnect();
},
_onBlur: function () {
this._hideSuggestions();
},
_onMousemove: function (event) {
this.selectedIndex = this._indexOfTableRowOrDescendent(event.target);
},
_onMousedown: function (event) {
if (event.button == 2) {
return;
}
let idx = this._indexOfTableRowOrDescendent(event.target);
let suggestion = this.suggestionAtIndex(idx);
this._stickyInputValue = suggestion;
// 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);
}
},
_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.engineName != suggestions.engineName) {
return;
}
// Empty the table.
while (this._table.firstElementChild) {
this._table.firstElementChild.remove();
}
// Position and size the table.
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;
if (i < suggestions.formHistory.length) {
[type, idx] = ["formHistory", i];
}
else {
let j = i - suggestions.formHistory.length;
if (j < suggestions.remote.length) {
[type, idx] = ["remote", j];
}
else {
break;
}
}
this._table.appendChild(this._makeTableRow(type, suggestions[type][idx],
i, searchWords));
}
this._table.hidden = false;
this.input.setAttribute("aria-expanded", "true");
},
_speculativeConnect: function () {
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("searchSuggestionRow");
row.classList.add(type);
row.setAttribute("role", "presentation");
row.addEventListener("mousemove", this);
row.addEventListener("mousedown", this);
let entry = document.createElementNS(HTML_NS, "td");
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");
if (searchWords.has(word)) {
wordSpan.classList.add("typed");
}
wordSpan.textContent = word;
entry.appendChild(wordSpan);
if (i < suggestionWords.length - 1) {
entry.appendChild(document.createTextNode(" "));
}
}
row.appendChild(entry);
return row;
},
_getSuggestions: function () {
this._stickyInputValue = this.input.value;
if (this.engineName) {
this._sendMsg("GetSuggestions", {
engineName: this.engineName,
searchString: this.input.value,
remoteTimeout: this.remoteTimeout,
});
}
},
_hideSuggestions: function () {
this.input.setAttribute("aria-expanded", "false");
this._table.hidden = true;
while (this._table.firstElementChild) {
this._table.firstElementChild.remove();
}
this.selectAndUpdateInput(-1);
},
_indexOfTableRowOrDescendent: function (row) {
while (row && row.localName != "tr") {
row = row.parentNode;
}
if (!row) {
throw new Error("Element is not a row");
}
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("searchSuggestionTable");
this._table.setAttribute("role", "listbox");
return this._table;
},
_sendMsg: function (type, data=null) {
dispatchEvent(new CustomEvent("ContentSearchClient", {
detail: {
type: type,
data: data,
},
}));
},
};
return SearchSuggestionUIController;
})();

Просмотреть файл

@ -114,6 +114,12 @@ let AboutHomeListener = {
case "AboutHomeLoad":
this.onPageLoad();
break;
case "AboutHomeSearchEvent":
this.onSearch(aEvent);
break;
case "AboutHomeSearchPanel":
this.onOpenSearchPanel(aEvent);
break;
case "click":
this.onClick(aEvent);
break;
@ -131,6 +137,9 @@ let AboutHomeListener = {
case "AboutHome:Update":
this.onUpdate(aMessage.data);
break;
case "AboutHome:FocusInput":
this.onFocusInput();
break;
}
},
@ -141,11 +150,13 @@ let AboutHomeListener = {
// 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() {
@ -156,6 +167,7 @@ let AboutHomeListener = {
doc.documentElement.setAttribute("hasBrowserHandlers", "true");
addMessageListener("AboutHome:Update", this);
addMessageListener("AboutHome:FocusInput", this);
addEventListener("click", this, true);
addEventListener("pagehide", this, true);
@ -164,6 +176,8 @@ let AboutHomeListener = {
}
sendAsyncMessage("AboutHome:RequestUpdate");
doc.addEventListener("AboutHomeSearchEvent", this, true, true);
doc.addEventListener("AboutHomeSearchPanel", this, true, true);
},
onClick: function(aEvent) {
@ -214,6 +228,10 @@ let AboutHomeListener = {
case "settings":
sendAsyncMessage("AboutHome:Settings");
break;
case "searchIcon":
sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget });
break;
}
},
@ -228,6 +246,21 @@ let AboutHomeListener = {
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);

Просмотреть файл

@ -80,7 +80,6 @@ support-files =
redirect_bug623155.sjs
searchSuggestionEngine.sjs
searchSuggestionEngine.xml
searchSuggestionEngine2.xml
subtst_contextmenu.html
test-mixedcontent-securityerrors.html
test_bug435035.html
@ -370,10 +369,10 @@ skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synt
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]

Просмотреть файл

@ -101,15 +101,28 @@ let gTests = [
// 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";
@ -124,12 +137,7 @@ let gTests = [
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]);
@ -228,11 +236,11 @@ let gTests = [
{
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);
@ -247,15 +255,24 @@ let gTests = [
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;
});
@ -269,7 +286,7 @@ let gTests = [
"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");
@ -318,7 +335,8 @@ let gTests = [
},
{
// 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()
@ -326,12 +344,12 @@ let gTests = [
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");
@ -383,11 +401,9 @@ let gTests = [
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") {
@ -408,14 +424,9 @@ let gTests = [
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");
});
}
},
@ -466,6 +477,26 @@ let gTests = [
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;
})
}
];
@ -545,6 +576,38 @@ function promiseSetupSnippetsMap(aTab, aSetupFn)
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.
*
@ -648,18 +711,6 @@ let promisePrefsOpen = Task.async(function*() {
}
});
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();

Просмотреть файл

@ -1,529 +0,0 @@
/* 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_ENGINE_BASENAME = "searchSuggestionEngine.xml";
const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
const TEST_MSG = "ContentSearchUIControllerTest";
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");
checkState(state, "", [], -1);
yield msg("reset");
});
add_task(function* blur() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("blur");
checkState(state, "x", [], -1);
yield msg("reset");
});
add_task(function* upDownKeys() {
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);
state = yield msg("key", "VK_LEFT");
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("key", "VK_LEFT");
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", { 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", "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", "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, "xfoo", ["xfoofoo", "xfoobar"], 2);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
state = yield msg("key", "VK_DOWN");
checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
yield msg("reset");
});
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);
}
state = yield msg("mousemove", -1);
checkState(state, "x", ["xfoo", "xbar"], -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);
// 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]);
// 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);
// 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();
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);
// Mouse over the first suggestion.
state = yield msg("mousemove", 0);
checkState(state, "x", [{ str: "x", type: "formHistory" },
{ str: "xfoo", type: "formHistory" }, "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);
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");
yield promiseTab();
yield setUp();
// 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);
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);
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;
yield msg("reset");
});
let gDidInitialSetUp = false;
function setUp(aNoEngine) {
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 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);
});
return deferred.promise;
}
function checkState(actualState, expectedInputVal, expectedSuggestions,
expectedSelectedIdx) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof(sugg) == "object" ? sugg : {
str: sugg,
type: "remote",
};
});
let expectedState = {
selectedIndex: expectedSelectedIdx,
numSuggestions: expectedSuggestions.length,
suggestionAtIndex: expectedSuggestions.map(s => s.str),
isFormHistorySuggestionAtIndex:
expectedSuggestions.map(s => s.type == "formHistory"),
tableHidden: expectedSuggestions.length == 0,
inputValue: expectedInputVal,
ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
};
SimpleTest.isDeeply(actualState, expectedState, "State");
}
var gMsgMan;
function promiseTab() {
let deferred = Promise.defer();
let tab = gBrowser.addTab();
registerCleanupFunction(() => gBrowser.removeTab(tab));
gBrowser.selectedTab = tab;
let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
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"));
});
}, true, true);
openUILinkIn(pageURL, "current");
return deferred.promise;
}
function promiseMsg(name, type, msgMan) {
let deferred = Promise.defer();
info("Waiting for " + name + " message " + type + "...");
msgMan.addMessageListener(name, function onMsg(msg) {
info("Received " + name + " message " + msg.data.type + "\n");
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);
addDeferred.resolve(engine);
},
onError: function (errCode) {
ok(false, "addEngine failed with error code " + errCode);
addDeferred.reject();
},
});
return addDeferred.promise;
}

Просмотреть файл

@ -0,0 +1,345 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const TEST_PAGE_BASENAME = "searchSuggestionUI.html";
const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
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");
checkState(state, "", [], -1);
yield msg("reset");
});
add_task(function* blur() {
yield setUp();
let state = yield msg("key", { key: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
state = yield msg("blur");
checkState(state, "x", [], -1);
yield msg("reset");
});
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"], -1);
// Cycle up starting from no selection.
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");
});
// The right arrow and return key function the same.
function rightArrowOrReturn(keyName) {
return function* rightArrowOrReturnTest() {
yield setUp();
let state = yield msg("key", { key: "x", 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);
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, "xfoobar", ["xfoofoo", "xfoobar"], 1);
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);
// 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);
// 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 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);
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* composition() {
yield setUp();
let state = yield msg("startComposition", { data: "" });
checkState(state, "", [], -1);
state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
checkState(state, "x", ["xfoo", "xbar"], -1);
// 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);
// 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");
});
let gDidInitialSetUp = false;
function setUp() {
return Task.spawn(function* () {
if (!gDidInitialSetUp) {
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) {
gMsgMan.removeMessageListener(TEST_MSG, onMsg);
deferred.resolve(msg.data);
});
return deferred.promise;
}
function checkState(actualState, expectedInputVal, expectedSuggestions,
expectedSelectedIdx) {
expectedSuggestions = expectedSuggestions.map(sugg => {
return typeof(sugg) == "object" ? sugg : {
str: sugg,
type: "remote",
};
});
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");
}
var gMsgMan;
function promiseTab() {
let deferred = Promise.defer();
let tab = gBrowser.addTab();
registerCleanupFunction(() => gBrowser.removeTab(tab));
gBrowser.selectedTab = tab;
let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
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();
});
}, true, true);
openUILinkIn(pageURL, "current");
return deferred.promise;
}
function promiseMsg(name, type, msgMan) {
let deferred = Promise.defer();
info("Waiting for " + name + " message " + type + "...");
msgMan.addMessageListener(name, function onMsg(msg) {
info("Received " + name + " message " + msg.data.type + "\n");
if (msg.data.type == type) {
msgMan.removeMessageListener(name, onMsg);
deferred.resolve(msg);
}
});
return deferred.promise;
}
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;
}

Просмотреть файл

@ -1,189 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
(function () {
const TEST_MSG = "ContentSearchUIControllerTest";
const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
var gController;
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 || {});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack.bind(null, "key"));
},
startComposition: function (arg) {
content.synthesizeComposition({ type: "compositionstart", data: "" });
ack("startComposition");
},
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");
},
focus: function () {
gController.input.focus();
ack("focus");
},
blur: function () {
gController.input.blur();
ack("blur");
},
waitForSearch: function () {
waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
},
waitForSearchSettings: function () {
waitForContentSearchEvent("ManageEngines",
aData => ack("waitForSearchSettings", aData));
},
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");
},
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");
},
addInputValueToFormHistory: function () {
gController.addInputValueToFormHistory();
ack("addInputValueToFormHistory");
},
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");
},
};
function ack(aType, aData) {
sendAsyncMessage(TEST_MSG, { type: aType, data: aData || 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,
suggestionAtIndex: [],
isFormHistorySuggestionAtIndex: [],
tableHidden: gController._table.hidden,
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));
}
}
return state;
}
})();

Просмотреть файл

@ -5,5 +5,5 @@
<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&amp;terms={searchTerms}" rel="searchform"/>
<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine" rel="searchform"/>
</SearchPlugin>

Просмотреть файл

@ -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&amp;terms={searchTerms}" rel="searchform"/>
</SearchPlugin>

Просмотреть файл

@ -9,13 +9,12 @@
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>

Просмотреть файл

@ -0,0 +1,158 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
(function () {
const TEST_MSG = "SearchSuggestionUIControllerTest";
const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
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 = {
key: function (arg) {
let keyName = typeof(arg) == "string" ? arg : arg.key;
content.synthesizeKey(keyName, {});
let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
wait(ack);
},
startComposition: function (arg) {
content.synthesizeComposition({ type: "compositionstart", data: "" });
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);
},
focus: function () {
gController.input.focus();
ack();
},
blur: function () {
gController.input.blur();
ack();
},
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);
},
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();
},
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();
},
};
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 currentState() {
let state = {
selectedIndex: gController.selectedIndex,
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"),
};
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;
}
})();

Просмотреть файл

@ -76,6 +76,13 @@ let runTaskifiedTests = Task.async(function* () {
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;
@ -106,6 +113,19 @@ let runTaskifiedTests = Task.async(function* () {
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;
@ -113,7 +133,7 @@ let runTaskifiedTests = Task.async(function* () {
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
@ -289,10 +309,21 @@ let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, lo
" 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();
@ -303,12 +334,16 @@ function promiseClick(node) {
return deferred.promise;
}
function searchPanel() {
return $("panel");
}
function logoImg() {
return $("logo");
}
function gSearch() {
return getContentWindow().gSearch._contentSearchController;
return getContentWindow().gSearch;
}
/**

Просмотреть файл

@ -722,23 +722,14 @@ function whenPagesUpdated(aCallback = TestRunner.next) {
*/
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;

Просмотреть файл

@ -144,8 +144,8 @@ browser.jar:
* 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)

Просмотреть файл

@ -30,9 +30,7 @@ add_task(function* test_mozLoop_getSelectedTabMetadata() {
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);
});

Просмотреть файл

@ -11,7 +11,7 @@
<!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

Просмотреть файл

@ -421,12 +421,6 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!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:" -->

Просмотреть файл

@ -32,15 +32,3 @@ cmd_addFoundEngine=Add "%S"
# 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:

Просмотреть файл

@ -100,6 +100,8 @@ let AboutHome = {
"AboutHome:Sync",
"AboutHome:Settings",
"AboutHome:RequestUpdate",
"AboutHome:Search",
"AboutHome:OpenSearchPanel",
],
init: function() {
@ -108,6 +110,16 @@ let AboutHome = {
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) {
@ -167,6 +179,65 @@ let AboutHome = {
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;
}
},
@ -179,11 +250,24 @@ let AboutHome = {
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) {
@ -201,5 +285,14 @@ let AboutHome = {
}).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");
}
};

Просмотреть файл

@ -45,9 +45,6 @@ const MAX_SUGGESTIONS = 6;
* 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
@ -56,7 +53,7 @@ const MAX_SUGGESTIONS = 6;
* 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
@ -75,9 +72,6 @@ const MAX_SUGGESTIONS = 6;
* 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
@ -104,24 +98,9 @@ this.ContentSearch = {
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;
@ -174,7 +153,6 @@ this.ContentSearch = {
observe: function (subj, topic, data) {
switch (topic) {
case "nsPref:changed":
case "browser-search-engine-modified":
this._eventQueue.push({
type: "Observe",
@ -223,19 +201,14 @@ this.ContentSearch = {
});
},
_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 {
@ -266,7 +239,7 @@ this.ContentSearch = {
};
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();
},
@ -441,12 +414,7 @@ this.ContentSearch = {
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,

Просмотреть файл

@ -673,7 +673,7 @@ let DirectoryLinksProvider = {
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);

Просмотреть файл

@ -94,8 +94,7 @@ add_task(function* search() {
let data = {
engineName: engine.name,
searchString: "ContentSearchTest",
healthReportKey: "ContentSearchTest",
searchPurpose: "ContentSearchTest",
whence: "ContentSearchTest",
};
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: "Search",
@ -117,8 +116,7 @@ add_task(function* searchInBackgroundTab() {
let data = {
engineName: engine.name,
searchString: "ContentSearchTest",
healthReportKey: "ContentSearchTest",
searchPurpose: "ContentSearchTest",
whence: "ContentSearchTest",
};
gMsgMan.sendAsyncMessage(TEST_MSG, {
type: "Search",

Просмотреть файл

@ -93,8 +93,6 @@ browser.jar:
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

Просмотреть файл

@ -124,8 +124,6 @@ browser.jar:
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)

Просмотреть файл

@ -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>

До

Ширина:  |  Высота:  |  Размер: 926 B

Просмотреть файл

@ -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>

До

Ширина:  |  Высота:  |  Размер: 561 B

Просмотреть файл

@ -117,8 +117,6 @@ browser.jar:
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)