diff options
Diffstat (limited to 'browser/base/content/contentSearchUI.js')
-rw-r--r-- | browser/base/content/contentSearchUI.js | 915 |
1 files changed, 0 insertions, 915 deletions
diff --git a/browser/base/content/contentSearchUI.js b/browser/base/content/contentSearchUI.js deleted file mode 100644 index 9136ea8f2..000000000 --- a/browser/base/content/contentSearchUI.js +++ /dev/null @@ -1,915 +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 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: [], - // Setting up the one off buttons causes an uninterruptible reflow. If we - // receive the list of engines while the newtab page is loading, this reflow - // may regress performance - so we set this flag and only set up the buttons - // if it's set when the suggestions table is actually opened. - _pendingOneOffRefresh: undefined, - - get defaultEngine() { - return this._defaultEngine; - }, - - set defaultEngine(engine) { - if (this._defaultEngine && this._defaultEngine.icon) { - URL.revokeObjectURL(this._defaultEngine.icon); - } - let icon; - if (engine.iconBuffer) { - icon = this._getFaviconURIFromBuffer(engine.iconBuffer); - } - else { - icon = this._getImageURIForCurrentResolution( - "chrome://mozapps/skin/places/defaultFavicon.png"); - } - this._defaultEngine = { - name: engine.name, - icon: icon, - }; - this._updateDefaultEngineHeader(); - - if (engine && document.activeElement == this.input) { - this._speculativeConnect(); - } - }, - - get engines() { - return this._engines; - }, - - set engines(val) { - this._engines = val; - this._pendingOneOffRefresh = true; - }, - - // 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")]; - // If we are selecting a suggestion and a one-off is selected, don't deselect it. - let excludeIndex = idx < this.numSuggestions && this.selectedButtonIndex > -1 ? - this.numSuggestions + this.selectedButtonIndex : -1; - 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 if (i != excludeIndex) { - elt.classList.remove("selected"); - ariaSelectedElt.setAttribute("aria-selected", "false"); - } - } - }, - - get selectedButtonIndex() { - let elts = [...this._oneOffButtons, - document.getElementById("contentSearchSettingsButton")]; - for (let i = 0; i < elts.length; ++i) { - if (elts[i].classList.contains("selected")) { - return i; - } - } - return -1; - }, - - set selectedButtonIndex(idx) { - let elts = [...this._oneOffButtons, - document.getElementById("contentSearchSettingsButton")]; - for (let i = 0; i < elts.length; ++i) { - let elt = elts[i]; - if (i == idx) { - elt.classList.add("selected"); - elt.setAttribute("aria-selected", "true"); - } - else { - elt.classList.remove("selected"); - elt.setAttribute("aria-selected", "false"); - } - } - }, - - get selectedEngineName() { - let selectedElt = this._oneOffsTable.querySelector(".selected"); - if (selectedElt) { - 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.selectedButtonIndex == 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" || - aEvent instanceof KeyboardEvent) { - 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, - }, - }; - if ("button" in aEvent) { - eventData.originalEvent.button = aEvent.button; - } - - if (this.suggestionAtIndex(this.selectedIndex)) { - eventData.selection = { - index: this.selectedIndex, - kind: undefined, - }; - if (aEvent instanceof MouseEvent) { - eventData.selection.kind = "mouse"; - } else if (aEvent instanceof KeyboardEvent) { - eventData.selection.kind = "key"; - } - } - - 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; - let selectedSuggestionDelta = 0; - let selectedOneOffDelta = 0; - - switch (event.keyCode) { - case event.DOM_VK_UP: - if (this._table.hidden) { - return; - } - if (event.getModifierState("Accel")) { - if (event.shiftKey) { - selectedSuggestionDelta = -1; - break; - } - this._cycleCurrentEngine(true); - break; - } - if (event.altKey) { - selectedOneOffDelta = -1; - break; - } - selectedIndexDelta = -1; - break; - case event.DOM_VK_DOWN: - if (this._table.hidden) { - this._getSuggestions(); - return; - } - if (event.getModifierState("Accel")) { - if (event.shiftKey) { - selectedSuggestionDelta = 1; - break; - } - this._cycleCurrentEngine(false); - break; - } - if (event.altKey) { - selectedOneOffDelta = 1; - break; - } - selectedIndexDelta = 1; - break; - case event.DOM_VK_TAB: - if (this._table.hidden) { - return; - } - // Shift+tab when either the first or no one-off is selected, as well as - // tab when the settings button is selected, should change focus as normal. - if ((this.selectedButtonIndex <= 0 && event.shiftKey) || - this.selectedButtonIndex == this._oneOffButtons.length && !event.shiftKey) { - return; - } - selectedOneOffDelta = event.shiftKey ? -1 : 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(); - return; - case event.DOM_VK_RETURN: - this._onCommand(event); - return; - case event.DOM_VK_DELETE: - if (this.selectedIndex >= 0) { - this.deleteSuggestionAtIndex(this.selectedIndex); - } - return; - case event.DOM_VK_ESCAPE: - if (!this._table.hidden) { - this._hideSuggestions(); - } - return; - default: - return; - } - - let currentIndex = this.selectedIndex; - if (selectedIndexDelta) { - let newSelectedIndex = currentIndex + selectedIndexDelta; - if (newSelectedIndex < -1) { - newSelectedIndex = this.numSuggestions + this._oneOffButtons.length; - } - // If are moving up from the first one off, we have to deselect the one off - // manually because the selectedIndex setter tries to exclude the selected - // one-off (which is desirable for accel+shift+up/down). - if (currentIndex == this.numSuggestions && selectedIndexDelta == -1) { - this.selectedButtonIndex = -1; - } - this.selectAndUpdateInput(newSelectedIndex); - } - - else if (selectedSuggestionDelta) { - let newSelectedIndex; - if (currentIndex >= this.numSuggestions || currentIndex == -1) { - // No suggestion already selected, select the first/last one appropriately. - newSelectedIndex = selectedSuggestionDelta == 1 ? - 0 : this.numSuggestions - 1; - } - else { - newSelectedIndex = currentIndex + selectedSuggestionDelta; - } - if (newSelectedIndex >= this.numSuggestions) { - newSelectedIndex = -1; - } - this.selectAndUpdateInput(newSelectedIndex); - } - - else if (selectedOneOffDelta) { - let newSelectedIndex; - let currentButton = this.selectedButtonIndex; - if (currentButton == -1 || currentButton == this._oneOffButtons.length) { - // No one-off already selected, select the first/last one appropriately. - newSelectedIndex = selectedOneOffDelta == 1 ? - 0 : this._oneOffButtons.length - 1; - } - else { - newSelectedIndex = currentButton + selectedOneOffDelta; - } - // Allow selection of the settings button via the tab key. - if (newSelectedIndex == this._oneOffButtons.length && - event.keyCode != event.DOM_VK_TAB) { - newSelectedIndex = -1; - } - this.selectedButtonIndex = newSelectedIndex; - } - - // Prevent the input's caret from moving. - event.preventDefault(); - }, - - _currentEngineIndex: -1, - _cycleCurrentEngine: function (aReverse) { - if ((this._currentEngineIndex == this._engines.length - 1 && !aReverse) || - (this._currentEngineIndex == 0 && aReverse)) { - return; - } - this._currentEngineIndex += aReverse ? -1 : 1; - let engineName = this._engines[this._currentEngineIndex].name; - this._sendMsg("SetCurrentEngine", engineName); - }, - - _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) { - let idx = this._indexOfTableItem(event.target); - if (idx >= this.numSuggestions) { - this.selectedButtonIndex = idx - this.numSuggestions; - return; - } - this.selectedIndex = idx; - }, - - _onMouseup: function (event) { - if (event.button == 2) { - return; - } - this._onCommand(event); - }, - - _onMouseout: function (event) { - // We only deselect one-off buttons and the settings button when they are - // moused out. - let idx = this._indexOfTableItem(event.originalTarget); - if (idx >= this.numSuggestions) { - this.selectedButtonIndex = -1; - } - }, - - _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; - if (this._pendingOneOffRefresh) { - this._setUpOneOffButtons(); - delete this._pendingOneOffRefresh; - } - this._currentEngineIndex = - this._engines.findIndex(aEngine => aEngine.name == this.defaultEngine.name); - this._table.hidden = false; - this.input.setAttribute("aria-expanded", "true"); - this._originalDefaultEngine = { - name: this.defaultEngine.name, - icon: this.defaultEngine.icon, - }; - } - }, - - _onMsgSuggestionsCancelled: function () { - if (!this._table.hidden) { - this._hideSuggestions(); - } - }, - - _onMsgState: function (state) { - this.engines = state.engines; - // No point updating the default engine (and the header) if there's no change. - if (this.defaultEngine && - this.defaultEngine.name == state.currentEngine.name && - this.defaultEngine.icon == state.currentEngine.icon) { - return; - } - this.defaultEngine = state.currentEngine; - }, - - _onMsgCurrentState: function (state) { - this._onMsgState(state); - }, - - _onMsgCurrentEngine: function (engine) { - this.defaultEngine = engine; - this._pendingOneOffRefresh = true; - }, - - _onMsgStrings: function (strings) { - this._strings = strings; - this._updateDefaultEngineHeader(); - this._updateSearchWithHeader(); - document.getElementById("contentSearchSettingsButton").textContent = - this._strings.searchSettings; - this.input.setAttribute("placeholder", this._strings.searchPlaceholder); - }, - - _updateDefaultEngineHeader: function () { - let header = document.getElementById("contentSearchDefaultEngineHeader"); - 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"); - if (this.input.value) { - searchWithHeader.innerHTML = this._strings.searchForSomethingWith; - searchWithHeader.querySelector('.contentSearchSearchWithHeaderSearchText').textContent = this.input.value; - } else { - searchWithHeader.textContent = 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 a data URI. - _getFaviconURIFromBuffer: function (buffer) { - let blob = new Blob([buffer]); - return URL.createObjectURL(blob); - }, - - // Adds "@2x" to the name of the given PNG url for "retina" screens. - _getImageURIForCurrentResolution: function (uri) { - if (window.devicePixelRatio > 1) { - return uri.replace(/\.png$/, "@2x.png"); - } - return uri; - }, - - _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.selectedIndex = -1; - this.selectedButtonIndex = -1; - this._currentEngineIndex = -1; - 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", this); - - // 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 iconImg = document.createElementNS(HTML_NS, "img"); - header.appendChild(iconImg); - 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.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) - .filter(aEngine => !aEngine.hidden); - 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; - if (engine.iconBuffer) { - uri = this._getFaviconURIFromBuffer(engine.iconBuffer); - } - else { - uri = this._getImageURIForCurrentResolution( - "chrome://browser/skin/search-engine-placeholder.png"); - } - img.setAttribute("src", uri); - img.addEventListener("load", function imgLoad() { - img.removeEventListener("load", imgLoad); - URL.revokeObjectURL(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; -})(); |