diff options
Diffstat (limited to 'browser/components/search/content/search.xml')
-rw-r--r-- | browser/components/search/content/search.xml | 2090 |
1 files changed, 0 insertions, 2090 deletions
diff --git a/browser/components/search/content/search.xml b/browser/components/search/content/search.xml deleted file mode 100644 index 5c67bc649..000000000 --- a/browser/components/search/content/search.xml +++ /dev/null @@ -1,2090 +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/. --> - -<!DOCTYPE bindings [ -<!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" > -%searchBarDTD; -<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> -%browserDTD; -]> - -<bindings id="SearchBindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - xmlns:xbl="http://www.mozilla.org/xbl"> - - <binding id="searchbar"> - <resources> - <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/> - <stylesheet src="chrome://browser/skin/searchbar.css"/> - </resources> - <content> - <xul:stringbundle src="chrome://browser/locale/search.properties" - anonid="searchbar-stringbundle"/> - <!-- - There is a dependency between "maxrows" attribute and - "SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing - one of them requires changing the other one. - --> - <xul:textbox class="searchbar-textbox" - anonid="searchbar-textbox" - type="autocomplete" - inputtype="search" - flex="1" - autocompletepopup="PopupSearchAutoComplete" - autocompletesearch="search-autocomplete" - autocompletesearchparam="searchbar-history" - maxrows="10" - completeselectedindex="true" - minresultsforpopup="0" - xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines"> - <!-- - Empty <box> to properly position the icon within the autocomplete - binding's anonymous children (the autocomplete binding positions <box> - children differently) - --> - <xul:box> - <xul:hbox class="searchbar-search-button-container"> - <xul:image class="searchbar-search-button" - anonid="searchbar-search-button" - xbl:inherits="addengines" - tooltiptext="&searchEndCap.label;"/> - </xul:hbox> - </xul:box> - <xul:hbox class="search-go-container"> - <xul:image class="search-go-button" hidden="true" - anonid="search-go-button" - onclick="handleSearchCommand(event);" - tooltiptext="&searchEndCap.label;"/> - </xul:hbox> - </xul:textbox> - </content> - - <implementation implements="nsIObserver"> - <constructor><![CDATA[ - if (this.parentNode.parentNode.localName == "toolbarpaletteitem") - return; - // Make sure we rebuild the popup in onpopupshowing - this._needToBuildPopup = true; - - Services.obs.addObserver(this, "browser-search-engine-modified", false); - - this._initialized = true; - - Services.search.init((function search_init_cb(aStatus) { - // Bail out if the binding's been destroyed - if (!this._initialized) - return; - - if (Components.isSuccessCode(aStatus)) { - // Refresh the display (updating icon, etc) - this.updateDisplay(); - BrowserSearch.updateOpenSearchBadge(); - } else { - Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus); - } - }).bind(this)); - ]]></constructor> - - <destructor><![CDATA[ - this.destroy(); - ]]></destructor> - - <method name="destroy"> - <body><![CDATA[ - if (this._initialized) { - this._initialized = false; - - Services.obs.removeObserver(this, "browser-search-engine-modified"); - } - - // Make sure to break the cycle from _textbox to us. Otherwise we leak - // the world. But make sure it's actually pointing to us. - // Also make sure the textbox has ever been constructed, otherwise the - // _textbox getter will cause the textbox constructor to run, add an - // observer, and leak the world too. - if (this._textboxInitialized && this._textbox.mController.input == this) - this._textbox.mController.input = null; - ]]></body> - </method> - - <field name="_ignoreFocus">false</field> - <field name="_clickClosedPopup">false</field> - <field name="_stringBundle">document.getAnonymousElementByAttribute(this, - "anonid", "searchbar-stringbundle");</field> - <field name="_textboxInitialized">false</field> - <field name="_textbox">document.getAnonymousElementByAttribute(this, - "anonid", "searchbar-textbox");</field> - <field name="_engines">null</field> - <field name="FormHistory" readonly="true"> - (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; - </field> - - <property name="engines" readonly="true"> - <getter><![CDATA[ - if (!this._engines) - this._engines = Services.search.getVisibleEngines(); - return this._engines; - ]]></getter> - </property> - - <property name="currentEngine"> - <setter><![CDATA[ - Services.search.currentEngine = val; - return val; - ]]></setter> - <getter><![CDATA[ - var currentEngine = Services.search.currentEngine; - // Return a dummy engine if there is no currentEngine - return currentEngine || {name: "", uri: null}; - ]]></getter> - </property> - - <!-- textbox is used by sanitize.js to clear the undo history when - clearing form information. --> - <property name="textbox" readonly="true" - onget="return this._textbox;"/> - - <property name="value" onget="return this._textbox.value;" - onset="return this._textbox.value = val;"/> - - <method name="focus"> - <body><![CDATA[ - this._textbox.focus(); - ]]></body> - </method> - - <method name="select"> - <body><![CDATA[ - this._textbox.select(); - ]]></body> - </method> - - <method name="observe"> - <parameter name="aEngine"/> - <parameter name="aTopic"/> - <parameter name="aVerb"/> - <body><![CDATA[ - if (aTopic == "browser-search-engine-modified") { - switch (aVerb) { - case "engine-removed": - this.offerNewEngine(aEngine); - break; - case "engine-added": - this.hideNewEngine(aEngine); - break; - case "engine-changed": - // An engine was removed (or hidden) or added, or an icon was - // changed. Do nothing special. - } - - // Make sure the engine list is refetched next time it's needed - this._engines = null; - - // Update the popup header and update the display after any modification. - this._textbox.popup.updateHeader(); - this.updateDisplay(); - } - ]]></body> - </method> - - <!-- There are two seaprate lists of search engines, whose uses intersect - in this file. The search service (nsIBrowserSearchService and - nsSearchService.js) maintains a list of Engine objects which is used to - populate the searchbox list of available engines and to perform queries. - That list is accessed here via this.SearchService, and it's that sort of - Engine that is passed to this binding's observer as aEngine. - - In addition, browser.js fills two lists of autodetected search engines - (browser.engines and browser.hiddenEngines) as properties of - mCurrentBrowser. Those lists contain unnamed JS objects of the form - { uri:, title:, icon: }, and that's what the searchbar uses to determine - whether to show any "Add <EngineName>" menu items in the drop-down. - - The two types of engines are currently related by their identifying - titles (the Engine object's 'name'), although that may change; see bug - 335102. --> - - <!-- If the engine that was just removed from the searchbox list was - autodetected on this page, move it to each browser's active list so it - will be offered to be added again. --> - <method name="offerNewEngine"> - <parameter name="aEngine"/> - <body><![CDATA[ - for (let browser of gBrowser.browsers) { - if (browser.hiddenEngines) { - // XXX This will need to be changed when engines are identified by - // URL rather than title; see bug 335102. - var removeTitle = aEngine.wrappedJSObject.name; - for (var i = 0; i < browser.hiddenEngines.length; i++) { - if (browser.hiddenEngines[i].title == removeTitle) { - if (!browser.engines) - browser.engines = []; - browser.engines.push(browser.hiddenEngines[i]); - browser.hiddenEngines.splice(i, 1); - break; - } - } - } - } - BrowserSearch.updateOpenSearchBadge(); - ]]></body> - </method> - - <!-- If the engine that was just added to the searchbox list was - autodetected on this page, move it to each browser's hidden list so it is - no longer offered to be added. --> - <method name="hideNewEngine"> - <parameter name="aEngine"/> - <body><![CDATA[ - for (let browser of gBrowser.browsers) { - if (browser.engines) { - // XXX This will need to be changed when engines are identified by - // URL rather than title; see bug 335102. - var removeTitle = aEngine.wrappedJSObject.name; - for (var i = 0; i < browser.engines.length; i++) { - if (browser.engines[i].title == removeTitle) { - if (!browser.hiddenEngines) - browser.hiddenEngines = []; - browser.hiddenEngines.push(browser.engines[i]); - browser.engines.splice(i, 1); - break; - } - } - } - } - BrowserSearch.updateOpenSearchBadge(); - ]]></body> - </method> - - <method name="setIcon"> - <parameter name="element"/> - <parameter name="uri"/> - <body><![CDATA[ - element.setAttribute("src", uri); - ]]></body> - </method> - - <method name="updateDisplay"> - <body><![CDATA[ - var uri = this.currentEngine.iconURI; - this.setIcon(this, uri ? uri.spec : ""); - - var name = this.currentEngine.name; - var text = this._stringBundle.getFormattedString("searchtip", [name]); - - this._textbox.placeholder = this._stringBundle.getString("searchPlaceholder"); - this._textbox.label = text; - this._textbox.tooltipText = text; - ]]></body> - </method> - - <method name="updateGoButtonVisibility"> - <body><![CDATA[ - document.getAnonymousElementByAttribute(this, "anonid", - "search-go-button") - .hidden = !this._textbox.value; - ]]></body> - </method> - - <method name="openSuggestionsPanel"> - <parameter name="aShowOnlySettingsIfEmpty"/> - <body><![CDATA[ - if (this._textbox.open) - return; - - this._textbox.showHistoryPopup(); - - if (this._textbox.value) { - // showHistoryPopup does a startSearch("") call, ensure the - // controller handles the text from the input box instead: - this._textbox.mController.handleText(); - } - else if (aShowOnlySettingsIfEmpty) { - this.setAttribute("showonlysettings", "true"); - } - ]]></body> - </method> - - <method name="selectEngine"> - <parameter name="aEvent"/> - <parameter name="isNextEngine"/> - <body><![CDATA[ - // Find the new index - var newIndex = this.engines.indexOf(this.currentEngine); - newIndex += isNextEngine ? 1 : -1; - - if (newIndex >= 0 && newIndex < this.engines.length) { - this.currentEngine = this.engines[newIndex]; - } - - aEvent.preventDefault(); - aEvent.stopPropagation(); - - this.openSuggestionsPanel(); - ]]></body> - </method> - - <method name="handleSearchCommand"> - <parameter name="aEvent"/> - <parameter name="aEngine"/> - <parameter name="aForceNewTab"/> - <body><![CDATA[ - var where = "current"; - let params; - - // Open ctrl/cmd clicks on one-off buttons in a new background tab. - if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") { - if (aEvent.button == 2) - return; - where = whereToOpenLink(aEvent, false, true); - } - else if (aForceNewTab) { - where = "tab"; - if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) - where += "-background"; - } - else { - var newTabPref = Services.prefs.getBoolPref("browser.search.openintab"); - if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref) - where = "tab"; - if ((aEvent instanceof MouseEvent) && - (aEvent.button == 1 || aEvent.getModifierState("Accel"))) { - where = "tab"; - params = { - inBackground: true, - }; - } - } - - this.handleSearchCommandWhere(aEvent, aEngine, where, params); - ]]></body> - </method> - - <method name="handleSearchCommandWhere"> - <parameter name="aEvent"/> - <parameter name="aEngine"/> - <parameter name="aWhere"/> - <parameter name="aParams"/> - <body><![CDATA[ - var textBox = this._textbox; - var textValue = textBox.value; - - let selection = this.telemetrySearchDetails; - let oneOffRecorded = false; - - if (!selection || (selection.index == -1)) { - oneOffRecorded = this.textbox.popup.oneOffButtons - .maybeRecordTelemetry(aEvent, aWhere, aParams); - if (!oneOffRecorded) { - let source = "unknown"; - let type = "unknown"; - let target = aEvent.originalTarget; - if (aEvent instanceof KeyboardEvent) { - type = "key"; - } else if (aEvent instanceof MouseEvent) { - type = "mouse"; - if (target.classList.contains("search-panel-header") || - target.parentNode.classList.contains("search-panel-header")) { - source = "header"; - } - } else if (aEvent instanceof XULCommandEvent) { - if (target.getAttribute("anonid") == "paste-and-search") { - source = "paste"; - } - } - if (!aEngine) { - aEngine = this.currentEngine; - } - BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type, - aWhere); - } - } - - // This is a one-off search only if oneOffRecorded is true. - this.doSearch(textValue, aWhere, aEngine, aParams, oneOffRecorded); - - if (aWhere == "tab" && aParams && aParams.inBackground) - this.focus(); - ]]></body> - </method> - - <method name="doSearch"> - <parameter name="aData"/> - <parameter name="aWhere"/> - <parameter name="aEngine"/> - <parameter name="aParams"/> - <parameter name="aOneOff"/> - <body><![CDATA[ - var textBox = this._textbox; - - // Save the current value in the form history - if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) { - this.FormHistory.update( - { op : "bump", - fieldname : textBox.getAttribute("autocompletesearchparam"), - value : aData }, - { handleError : function(aError) { - Components.utils.reportError("Saving search to form history failed: " + aError.message); - }}); - } - - let engine = aEngine || this.currentEngine; - var submission = engine.getSubmission(aData, null, "searchbar"); - let telemetrySearchDetails = this.telemetrySearchDetails; - this.telemetrySearchDetails = null; - if (telemetrySearchDetails && telemetrySearchDetails.index == -1) { - telemetrySearchDetails = null; - } - // If we hit here, we come either from a one-off, a plain search or a suggestion. - const details = { - isOneOff: aOneOff, - isSuggestion: (!aOneOff && telemetrySearchDetails), - selection: telemetrySearchDetails - }; - BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details); - // null parameter below specifies HTML response for search - let params = { - postData: submission.postData, - }; - if (aParams) { - for (let key in aParams) { - params[key] = aParams[key]; - } - } - openUILinkIn(submission.uri.spec, aWhere, params); - ]]></body> - </method> - </implementation> - - <handlers> - <handler event="command"><![CDATA[ - const target = event.originalTarget; - if (target.engine) { - this.currentEngine = target.engine; - } else if (target.classList.contains("addengine-item")) { - // Select the installed engine if the installation succeeds - var installCallback = { - onSuccess: engine => this.currentEngine = engine - } - Services.search.addEngine(target.getAttribute("uri"), null, - target.getAttribute("src"), false, - installCallback); - } - else - return; - - this.focus(); - this.select(); - ]]></handler> - - <handler event="DOMMouseScroll" - phase="capturing" - modifiers="accel" - action="this.selectEngine(event, (event.detail > 0));"/> - - <handler event="input" action="this.updateGoButtonVisibility();"/> - <handler event="drop" action="this.updateGoButtonVisibility();"/> - - <handler event="blur"> - <![CDATA[ - // If the input field is still focused then a different window has - // received focus, ignore the next focus event. - this._ignoreFocus = (document.activeElement == this._textbox.inputField); - ]]></handler> - - <handler event="focus"> - <![CDATA[ - // Speculatively connect to the current engine's search URI (and - // suggest URI, if different) to reduce request latency - this.currentEngine.speculativeConnect({window: window}); - - if (this._ignoreFocus) { - // This window has been re-focused, don't show the suggestions - this._ignoreFocus = false; - return; - } - - // Don't open the suggestions if there is no text in the textbox. - if (!this._textbox.value) - return; - - // Don't open the suggestions if the mouse was used to focus the - // textbox, that will be taken care of in the click handler. - if (Services.focus.getLastFocusMethod(window) & Services.focus.FLAG_BYMOUSE) - return; - - this.openSuggestionsPanel(); - ]]></handler> - - <handler event="mousedown" phase="capturing"> - <![CDATA[ - if (event.originalTarget.getAttribute("anonid") == "searchbar-search-button") { - this._clickClosedPopup = this._textbox.popup._isHiding; - } - ]]></handler> - - <handler event="click" button="0"> - <![CDATA[ - // Ignore clicks on the search go button. - if (event.originalTarget.getAttribute("anonid") == "search-go-button") { - return; - } - - let isIconClick = event.originalTarget.getAttribute("anonid") == "searchbar-search-button"; - - // Ignore clicks on the icon if they were made to close the popup - if (isIconClick && this._clickClosedPopup) { - return; - } - - // Open the suggestions whenever clicking on the search icon or if there - // is text in the textbox. - if (isIconClick || this._textbox.value) { - this.openSuggestionsPanel(true); - } - ]]></handler> - - </handlers> - </binding> - - <binding id="searchbar-textbox" - extends="chrome://global/content/bindings/autocomplete.xml#autocomplete"> - <implementation implements="nsIObserver"> - <constructor><![CDATA[ - const kXULNS = - "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - - if (document.getBindingParent(this).parentNode.parentNode.localName == - "toolbarpaletteitem") - return; - - // Initialize fields - this._stringBundle = document.getBindingParent(this)._stringBundle; - this._suggestEnabled = - Services.prefs.getBoolPref("browser.search.suggest.enabled"); - - if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll")) - this.setAttribute("clickSelectsAll", true); - - // Add items to context menu and attach controller to handle them - var textBox = document.getAnonymousElementByAttribute(this, - "anonid", "textbox-input-box"); - var cxmenu = document.getAnonymousElementByAttribute(textBox, - "anonid", "input-box-contextmenu"); - var pasteAndSearch; - cxmenu.addEventListener("popupshowing", function() { - BrowserSearch.searchBar._textbox.closePopup(); - if (!pasteAndSearch) - return; - var controller = document.commandDispatcher.getControllerForCommand("cmd_paste"); - var enabled = controller.isCommandEnabled("cmd_paste"); - if (enabled) - pasteAndSearch.removeAttribute("disabled"); - else - pasteAndSearch.setAttribute("disabled", "true"); - }, false); - - var element, label, akey; - - element = document.createElementNS(kXULNS, "menuseparator"); - cxmenu.appendChild(element); - - this.setAttribute("aria-owns", this.popup.id); - - var insertLocation = cxmenu.firstChild; - while (insertLocation.nextSibling && - insertLocation.getAttribute("cmd") != "cmd_paste") - insertLocation = insertLocation.nextSibling; - if (insertLocation) { - element = document.createElementNS(kXULNS, "menuitem"); - label = this._stringBundle.getString("cmd_pasteAndSearch"); - element.setAttribute("label", label); - element.setAttribute("anonid", "paste-and-search"); - element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)"); - cxmenu.insertBefore(element, insertLocation.nextSibling); - pasteAndSearch = element; - } - - element = document.createElementNS(kXULNS, "menuitem"); - label = this._stringBundle.getString("cmd_clearHistory"); - akey = this._stringBundle.getString("cmd_clearHistory_accesskey"); - element.setAttribute("label", label); - element.setAttribute("accesskey", akey); - element.setAttribute("cmd", "cmd_clearhistory"); - cxmenu.appendChild(element); - - element = document.createElementNS(kXULNS, "menuitem"); - label = this._stringBundle.getString("cmd_showSuggestions"); - akey = this._stringBundle.getString("cmd_showSuggestions_accesskey"); - element.setAttribute("anonid", "toggle-suggest-item"); - element.setAttribute("label", label); - element.setAttribute("accesskey", akey); - element.setAttribute("cmd", "cmd_togglesuggest"); - element.setAttribute("type", "checkbox"); - element.setAttribute("checked", this._suggestEnabled); - element.setAttribute("autocheck", "false"); - this._suggestMenuItem = element; - cxmenu.appendChild(element); - - this.addEventListener("keypress", aEvent => { - if (navigator.platform.startsWith("Mac") && aEvent.keyCode == KeyEvent.VK_F4) - this.openSearch() - }, true); - - this.controllers.appendController(this.searchbarController); - document.getBindingParent(this)._textboxInitialized = true; - - // Add observer for suggest preference - Services.prefs.addObserver("browser.search.suggest.enabled", this, false); - ]]></constructor> - - <destructor><![CDATA[ - Services.prefs.removeObserver("browser.search.suggest.enabled", this); - - // Because XBL and the customize toolbar code interacts poorly, - // there may not be anything to remove here - try { - this.controllers.removeController(this.searchbarController); - } catch (ex) { } - ]]></destructor> - - <field name="_stringBundle"/> - <field name="_suggestMenuItem"/> - <field name="_suggestEnabled"/> - - <!-- - This overrides the searchParam property in autocomplete.xml. We're - hijacking this property as a vehicle for delivering the privacy - information about the window into the guts of nsSearchSuggestions. - - Note that the setter is the same as the parent. We were not sure whether - we can override just the getter. If that proves to be the case, the setter - can be removed. - --> - <property name="searchParam" - onget="return this.getAttribute('autocompletesearchparam') + - (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');" - onset="this.setAttribute('autocompletesearchparam', val); return val;"/> - - <!-- This is implemented so that when textbox.value is set directly (e.g., - by tests), the one-off query is updated. --> - <method name="onBeforeValueSet"> - <parameter name="aValue"/> - <body><![CDATA[ - this.popup.oneOffButtons.query = aValue; - return aValue; - ]]></body> - </method> - - <!-- - This method overrides the autocomplete binding's openPopup (essentially - duplicating the logic from the autocomplete popup binding's - openAutocompletePopup method), modifying it so that the popup is aligned with - the inner textbox, but sized to not extend beyond the search bar border. - --> - <method name="openPopup"> - <body><![CDATA[ - var popup = this.popup; - if (!popup.mPopupOpen) { - // Initially the panel used for the searchbar (PopupSearchAutoComplete - // in browser.xul) is hidden to avoid impacting startup / new - // window performance. The base binding's openPopup would normally - // call the overriden openAutocompletePopup in urlbarBindings.xml's - // browser-autocomplete-result-popup binding to unhide the popup, - // but since we're overriding openPopup we need to unhide the panel - // ourselves. - popup.hidden = false; - - // Don't roll up on mouse click in the anchor for the search UI. - if (popup.id == "PopupSearchAutoComplete") { - popup.setAttribute("norolluponanchor", "true"); - } - - popup.mInput = this; - popup.view = this.controller.QueryInterface(Ci.nsITreeView); - popup.invalidate(); - - popup.showCommentColumn = this.showCommentColumn; - popup.showImageColumn = this.showImageColumn; - - document.popupNode = null; - - const isRTL = getComputedStyle(this, "").direction == "rtl"; - - var outerRect = this.getBoundingClientRect(); - var innerRect = this.inputField.getBoundingClientRect(); - let width = isRTL ? - innerRect.right - outerRect.left : - outerRect.right - innerRect.left; - popup.setAttribute("width", width > 100 ? width : 100); - - var yOffset = outerRect.bottom - innerRect.bottom; - popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false); - } - ]]></body> - </method> - - <method name="observe"> - <parameter name="aSubject"/> - <parameter name="aTopic"/> - <parameter name="aData"/> - <body><![CDATA[ - if (aTopic == "nsPref:changed") { - this._suggestEnabled = - Services.prefs.getBoolPref("browser.search.suggest.enabled"); - this._suggestMenuItem.setAttribute("checked", this._suggestEnabled); - } - ]]></body> - </method> - - <method name="openSearch"> - <body> - <![CDATA[ - if (!this.popupOpen) { - document.getBindingParent(this).openSuggestionsPanel(); - return false; - } - return true; - ]]> - </body> - </method> - - <!-- override |onTextEntered| in autocomplete.xml --> - <method name="onTextEntered"> - <parameter name="aEvent"/> - <body><![CDATA[ - let engine; - let oneOff = this.selectedButton; - if (oneOff) { - if (!oneOff.engine) { - oneOff.doCommand(); - return; - } - engine = oneOff.engine; - } - if (this._selectionDetails && - this._selectionDetails.currentIndex != -1) { - BrowserSearch.searchBar.telemetrySearchDetails = this._selectionDetails; - this._selectionDetails = null; - } - document.getBindingParent(this).handleSearchCommand(aEvent, engine); - ]]></body> - </method> - - <property name="selectedButton"> - <getter><![CDATA[ - return this.popup.oneOffButtons.selectedButton; - ]]></getter> - <setter><![CDATA[ - return this.popup.oneOffButtons.selectedButton = val; - ]]></setter> - </property> - - <method name="handleKeyboardNavigation"> - <parameter name="aEvent"/> - <body><![CDATA[ - let popup = this.popup; - if (!popup.popupOpen) - return; - - // accel + up/down changes the default engine and shouldn't affect - // the selection on the one-off buttons. - if (aEvent.getModifierState("Accel")) - return; - - let suggestions = - document.getAnonymousElementByAttribute(popup, "anonid", "tree"); - let suggestionsHidden = - suggestions.getAttribute("collapsed") == "true"; - let numItems = suggestionsHidden ? 0 : this.popup.view.rowCount; - this.popup.oneOffButtons.handleKeyPress(aEvent, numItems, true); - ]]></body> - </method> - - <!-- nsIController --> - <field name="searchbarController" readonly="true"><![CDATA[({ - _self: this, - supportsCommand: function(aCommand) { - return aCommand == "cmd_clearhistory" || - aCommand == "cmd_togglesuggest"; - }, - - isCommandEnabled: function(aCommand) { - return true; - }, - - doCommand: function (aCommand) { - switch (aCommand) { - case "cmd_clearhistory": - var param = this._self.getAttribute("autocompletesearchparam"); - - BrowserSearch.searchBar.FormHistory.update({ op : "remove", fieldname : param }, null); - this._self.value = ""; - break; - case "cmd_togglesuggest": - // The pref observer will update _suggestEnabled and the menu - // checkmark. - Services.prefs.setBoolPref("browser.search.suggest.enabled", - !this._self._suggestEnabled); - break; - default: - // do nothing with unrecognized command - } - } - })]]></field> - </implementation> - - <handlers> - <handler event="input"><![CDATA[ - this.popup.removeAttribute("showonlysettings"); - ]]></handler> - - <handler event="keypress" phase="capturing" - action="return this.handleKeyboardNavigation(event);"/> - - <handler event="keypress" keycode="VK_UP" modifiers="accel" - phase="capturing" - action="document.getBindingParent(this).selectEngine(event, false);"/> - - <handler event="keypress" keycode="VK_DOWN" modifiers="accel" - phase="capturing" - action="document.getBindingParent(this).selectEngine(event, true);"/> - - <handler event="keypress" keycode="VK_DOWN" modifiers="alt" - phase="capturing" - action="return this.openSearch();"/> - - <handler event="keypress" keycode="VK_UP" modifiers="alt" - phase="capturing" - action="return this.openSearch();"/> - - <handler event="dragover"> - <![CDATA[ - var types = event.dataTransfer.types; - if (types.includes("text/plain") || types.includes("text/x-moz-text-internal")) - event.preventDefault(); - ]]> - </handler> - - <handler event="drop"> - <![CDATA[ - var dataTransfer = event.dataTransfer; - var data = dataTransfer.getData("text/plain"); - if (!data) - data = dataTransfer.getData("text/x-moz-text-internal"); - if (data) { - event.preventDefault(); - this.value = data; - document.getBindingParent(this).openSuggestionsPanel(); - } - ]]> - </handler> - - </handlers> - </binding> - - <binding id="browser-search-autocomplete-result-popup" extends="chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup"> - <resources> - <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/> - <stylesheet src="chrome://browser/skin/searchbar.css"/> - </resources> - <content ignorekeys="true" level="top" consumeoutsideclicks="never"> - <xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings" - class="search-panel-header search-panel-current-engine"> - <xul:image class="searchbar-engine-image" xbl:inherits="src"/> - <xul:label anonid="searchbar-engine-name" flex="1" crop="end" - role="presentation"/> - </xul:hbox> - <xul:tree anonid="tree" flex="1" - class="autocomplete-tree plain search-panel-tree" - hidecolumnpicker="true" seltype="single"> - <xul:treecols anonid="treecols"> - <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/> - </xul:treecols> - <xul:treechildren class="autocomplete-treebody"/> - </xul:tree> - <xul:vbox anonid="search-one-off-buttons" class="search-one-offs"/> - </content> - <implementation> - <!-- Popup rollup is triggered by native events before the mousedown event - reaches the DOM. The will be set to true by the popuphiding event and - false after the mousedown event has been triggered to detect what - caused rollup. --> - <field name="_isHiding">false</field> - <field name="_bundle">null</field> - <property name="bundle" readonly="true"> - <getter> - <![CDATA[ - if (!this._bundle) { - const kBundleURI = "chrome://browser/locale/search.properties"; - this._bundle = Services.strings.createBundle(kBundleURI); - } - return this._bundle; - ]]> - </getter> - </property> - - <field name="oneOffButtons" readonly="true"> - document.getAnonymousElementByAttribute(this, "anonid", - "search-one-off-buttons"); - </field> - - <method name="updateHeader"> - <body><![CDATA[ - let currentEngine = Services.search.currentEngine; - let uri = currentEngine.iconURI; - if (uri) { - this.setAttribute("src", uri.spec); - } - else { - // If the default has just been changed to a provider without icon, - // avoid showing the icon of the previous default provider. - this.removeAttribute("src"); - } - - let headerText = this.bundle.formatStringFromName("searchHeader", - [currentEngine.name], 1); - document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine-name") - .setAttribute("value", headerText); - document.getAnonymousElementByAttribute(this, "anonid", "searchbar-engine") - .engine = currentEngine; - ]]></body> - </method> - - <!-- This is called when a one-off is clicked and when "search in new tab" - is selected from a one-off context menu. --> - <method name="handleOneOffSearch"> - <parameter name="event"/> - <parameter name="engine"/> - <parameter name="where"/> - <parameter name="params"/> - <body><![CDATA[ - let searchbar = document.getElementById("searchbar"); - searchbar.handleSearchCommandWhere(event, engine, where, params); - ]]></body> - </method> - </implementation> - - <handlers> - <handler event="popupshowing"><![CDATA[ - if (!this.oneOffButtons.popup) { - // The panel width only spans to the textbox size, but we also want it - // to include the magnifier icon's width. - let ltr = getComputedStyle(this).direction == "ltr"; - let magnifierWidth = parseInt(getComputedStyle(this)[ - ltr ? "marginLeft" : "marginRight" - ]) * -1; - // Ensure the panel is wide enough to fit at least 3 engines. - let minWidth = Math.max( - parseInt(this.width) + magnifierWidth, - this.oneOffButtons.buttonWidth * 3 - ); - this.style.minWidth = minWidth + "px"; - - // Set the origin before assigning the popup, as the assignment does - // a rebuild and would miss the origin. - this.oneOffButtons.telemetryOrigin = "searchbar"; - // Set popup after setting the minWidth since it builds the buttons. - this.oneOffButtons.popup = this; - this.oneOffButtons.textbox = this.input; - } - - // First handle deciding if we are showing the reduced version of the - // popup containing only the preferences button. We do this if the - // glass icon has been clicked if the text field is empty. - let searchbar = document.getElementById("searchbar"); - let tree = document.getAnonymousElementByAttribute(this, "anonid", - "tree") - if (searchbar.hasAttribute("showonlysettings")) { - searchbar.removeAttribute("showonlysettings"); - this.setAttribute("showonlysettings", "true"); - - // Setting this with an xbl-inherited attribute gets overridden the - // second time the user clicks the glass icon for some reason... - tree.collapsed = true; - } - else { - this.removeAttribute("showonlysettings"); - // Uncollapse as long as we have a tree with a view which has >= 1 row. - // The autocomplete binding itself will take care of uncollapsing later, - // if we currently have no rows but end up having some in the future - // when the search string changes - tree.collapsed = !tree.view || !tree.view.rowCount; - } - - // Show the current default engine in the top header of the panel. - this.updateHeader(); - ]]></handler> - - <handler event="popuphiding"><![CDATA[ - this._isHiding = true; - setTimeout(() => { - this._isHiding = false; - }, 0); - ]]></handler> - - <!-- This handles clicks on the topmost "Foo Search" header in the - popup (hbox[anonid="searchbar-engine"]). --> - <handler event="click"><![CDATA[ - if (event.button == 2) { - // Ignore right clicks. - return; - } - let button = event.originalTarget; - let engine = button.parentNode.engine; - if (!engine) { - return; - } - this.oneOffButtons.handleSearchCommand(event, engine); - ]]></handler> - </handlers> - - </binding> - - <!-- Used for additional open search providers in the search panel. --> - <binding id="addengine-icon" extends="xul:box"> - <content> - <xul:image class="addengine-icon" xbl:inherits="src"/> - <xul:image class="addengine-badge"/> - </content> - </binding> - - <binding id="search-one-offs"> - <content context="_child"> - <xul:deck anonid="search-panel-one-offs-header" - selectedIndex="0" - class="search-panel-header search-panel-current-input"> - <xul:label anonid="searchbar-oneoffheader-search" - value="&searchWithHeader.label;"/> - <xul:hbox anonid="search-panel-searchforwith" - class="search-panel-current-input"> - <xul:label anonid="searchbar-oneoffheader-before" - value="&searchFor.label;"/> - <xul:label anonid="searchbar-oneoffheader-searchtext" - class="search-panel-input-value" - flex="1" - crop="end"/> - <xul:label anonid="searchbar-oneoffheader-after" - flex="10000" - value="&searchWith.label;"/> - </xul:hbox> - <xul:hbox anonid="search-panel-searchonengine" - class="search-panel-current-input"> - <xul:label anonid="searchbar-oneoffheader-beforeengine" - value="&search.label;"/> - <xul:label anonid="searchbar-oneoffheader-engine" - class="search-panel-input-value" - flex="1" - crop="end"/> - <xul:label anonid="searchbar-oneoffheader-afterengine" - flex="10000" - value="&searchAfter.label;"/> - </xul:hbox> - </xul:deck> - <xul:description anonid="search-panel-one-offs" - role="group" - class="search-panel-one-offs" - xbl:inherits="compact"> - <xul:button anonid="search-settings-compact" - oncommand="showSettings();" - class="searchbar-engine-one-off-item search-setting-button-compact" - tooltiptext="&changeSearchSettings.tooltip;" - xbl:inherits="compact"/> - </xul:description> - <xul:vbox anonid="add-engines"/> - <xul:button anonid="search-settings" - oncommand="showSettings();" - class="search-setting-button search-panel-header" - label="&changeSearchSettings.button;" - xbl:inherits="compact"/> - <xul:menupopup anonid="search-one-offs-context-menu"> - <xul:menuitem anonid="search-one-offs-context-open-in-new-tab" - label="&searchInNewTab.label;" - accesskey="&searchInNewTab.accesskey;"/> - <xul:menuitem anonid="search-one-offs-context-set-default" - label="&searchSetAsDefault.label;" - accesskey="&searchSetAsDefault.accesskey;"/> - </xul:menupopup> - </content> - - <implementation implements="nsIDOMEventListener"> - - <!-- Width in pixels of the one-off buttons. 49px is the min-width of - each search engine button, adapt this const when changing the css. - It's actually 48px + 1px of right border. --> - <property name="buttonWidth" readonly="true" onget="return 49;"/> - - <field name="_popup">null</field> - - <!-- The popup that contains the one-offs. This is required, so it should - never be null or undefined, except possibly before the one-offs are - used. --> - <property name="popup"> - <getter><![CDATA[ - return this._popup; - ]]></getter> - <setter><![CDATA[ - if (this._popup == val) { - return val; - } - - let events = [ - "popupshowing", - "popuphidden", - ]; - if (this._popup) { - for (let event of events) { - this._popup.removeEventListener(event, this); - } - } - if (val) { - for (let event of events) { - val.addEventListener(event, this); - } - } - this._popup = val; - - // If the popup is already open, rebuild the one-offs now. The - // popup may be opening, so check that the state is not closed - // instead of checking popupOpen. - if (val && val.state != "closed") { - this._rebuild(); - } - return val; - ]]></setter> - </property> - - <field name="_textbox">null</field> - - <!-- The textbox associated with the one-offs. Set this to a textbox to - automatically keep the related one-offs UI up to date. Otherwise you - can leave it null/undefined, and in that case you should update the - query property manually. --> - <property name="textbox"> - <getter><![CDATA[ - return this._textbox; - ]]></getter> - <setter><![CDATA[ - if (this._textbox == val) { - return val; - } - if (this._textbox) { - this._textbox.removeEventListener("input", this); - } - if (val) { - val.addEventListener("input", this); - } - return this._textbox = val; - ]]></setter> - </property> - - <!-- Set this to a string that identifies your one-offs consumer. It'll - be appended to telemetry recorded with maybeRecordTelemetry(). --> - <field name="telemetryOrigin">""</field> - - <field name="_query">""</field> - - <!-- The query string currently shown in the one-offs. If the textbox - property is non-null, then this is automatically updated on - input. --> - <property name="query"> - <getter><![CDATA[ - return this._query; - ]]></getter> - <setter><![CDATA[ - this._query = val; - if (this.popup && this.popup.popupOpen) { - this._updateAfterQueryChanged(); - } - return val; - ]]></setter> - </property> - - <field name="_selectedButton">null</field> - - <!-- The selected one-off, a xul:button, including the add-engine button - and the search-settings button. Null if no one-off is selected. --> - <property name="selectedButton"> - <getter><![CDATA[ - return this._selectedButton; - ]]></getter> - <setter><![CDATA[ - this._changeVisuallySelectedButton(val, true); - return val; - ]]></setter> - </property> - - <!-- The index of the selected one-off, including the add-engine button - and the search-settings button. -1 if no one-off is selected. --> - <property name="selectedButtonIndex"> - <getter><![CDATA[ - let buttons = this.getSelectableButtons(true); - for (let i = 0; i < buttons.length; i++) { - if (buttons[i] == this._selectedButton) { - return i; - } - } - return -1; - ]]></getter> - <setter><![CDATA[ - let buttons = this.getSelectableButtons(true); - this.selectedButton = buttons[val]; - return val; - ]]></setter> - </property> - - <!-- The visually selected one-off is the same as the selected one-off - unless a one-off is moused over. In that case, the visually selected - one-off is the moused-over one-off, which may be different from the - selected one-off. The visually selected one-off is always the one - that is visually highlighted. Includes the add-engine button and the - search-settings button. A xul:button. --> - <property name="visuallySelectedButton" readonly="true"> - <getter><![CDATA[ - return this.getSelectableButtons(true).find(button => { - return button.getAttribute("selected") == "true"; - }); - ]]></getter> - </property> - - <property name="compact" readonly="true"> - <getter><![CDATA[ - return this.getAttribute("compact") == "true"; - ]]></getter> - </property> - - <property name="settingsButton" readonly="true"> - <getter><![CDATA[ - let id = this.compact ? "search-settings-compact" : "search-settings"; - return document.getAnonymousElementByAttribute(this, "anonid", id); - ]]></getter> - </property> - - <field name="_bundle">null</field> - - <property name="bundle" readonly="true"> - <getter><![CDATA[ - if (!this._bundle) { - const kBundleURI = "chrome://browser/locale/search.properties"; - this._bundle = Services.strings.createBundle(kBundleURI); - } - return this._bundle; - ]]></getter> - </property> - - <!-- When a context menu is opened on a one-off button, this is set to the - engine of that button for use with the context menu actions. --> - <field name="_contextEngine">null</field> - - <constructor><![CDATA[ - // Prevent popup events from the context menu from reaching the autocomplete - // binding (or other listeners). - let menu = document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-menu"); - let listener = aEvent => aEvent.stopPropagation(); - menu.addEventListener("popupshowing", listener); - menu.addEventListener("popuphiding", listener); - menu.addEventListener("popupshown", aEvent => { - this._ignoreMouseEvents = true; - aEvent.stopPropagation(); - }); - menu.addEventListener("popuphidden", aEvent => { - this._ignoreMouseEvents = false; - aEvent.stopPropagation(); - }); - ]]></constructor> - - <!-- This handles events outside the one-off buttons, like on the popup - and textbox. --> - <method name="handleEvent"> - <parameter name="event"/> - <body><![CDATA[ - switch (event.type) { - case "input": - // Allow the consumer's input to override its value property with - // a oneOffSearchQuery property. That way if the value is not - // actually what the user typed (e.g., it's autofilled, or it's a - // mozaction URI), the consumer has some way of providing it. - this.query = event.target.oneOffSearchQuery || event.target.value; - break; - case "popupshowing": - this._rebuild(); - break; - case "popuphidden": - Services.tm.mainThread.dispatch(() => { - this.selectedButton = null; - this._contextEngine = null; - }, Ci.nsIThread.DISPATCH_NORMAL); - break; - } - ]]></body> - </method> - - <method name="showSettings"> - <body><![CDATA[ - BrowserUITelemetry.countSearchSettingsEvent(this.telemetryOrigin); - openPreferences("paneSearch"); - // If the preference tab was already selected, the panel doesn't - // close itself automatically. - this.popup.hidePopup(); - ]]></body> - </method> - - <!-- Updates the parts of the UI that show the query string. --> - <method name="_updateAfterQueryChanged"> - <body><![CDATA[ - let headerSearchText = - document.getAnonymousElementByAttribute(this, "anonid", - "searchbar-oneoffheader-searchtext"); - let headerPanel = - document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs-header"); - let list = document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs"); - headerSearchText.setAttribute("value", this.query); - let groupText; - let isOneOffSelected = - this.selectedButton && - this.selectedButton.classList.contains("searchbar-engine-one-off-item"); - // Typing de-selects the settings or opensearch buttons at the bottom - // of the search panel, as typing shows the user intends to search. - if (this.selectedButton && !isOneOffSelected) - this.selectedButton = null; - if (this.query) { - groupText = headerSearchText.previousSibling.value + - '"' + headerSearchText.value + '"' + - headerSearchText.nextSibling.value; - if (!isOneOffSelected) - headerPanel.selectedIndex = 1; - } - else { - let noSearchHeader = - document.getAnonymousElementByAttribute(this, "anonid", - "searchbar-oneoffheader-search"); - groupText = noSearchHeader.value; - if (!isOneOffSelected) - headerPanel.selectedIndex = 0; - } - list.setAttribute("aria-label", groupText); - ]]></body> - </method> - - <!-- Builds all the UI. --> - <method name="_rebuild"> - <body><![CDATA[ - // Update the 'Search for <keywords> with:" header. - this._updateAfterQueryChanged(); - - let list = document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs"); - - // Handle opensearch items. This needs to be done before building the - // list of one off providers, as that code will return early if all the - // alternative engines are hidden. - let addEngineList = - document.getAnonymousElementByAttribute(this, "anonid", "add-engines"); - while (addEngineList.firstChild) - addEngineList.firstChild.remove(); - - const kXULNS = - "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - - // Add a button for each engine that the page in the selected browser - // offers. But not when the one-offs are compact. Compact one-offs - // are shown in the urlbar, and the add-engine buttons span the width - // of the popup, so if we added all the engines that a site offers, it - // could effectively break the urlbar popup by offering a ton of - // engines. We should probably make a smaller version of the buttons - // for compact one-offs. - if (!this.compact) { - for (let engine of gBrowser.selectedBrowser.engines || []) { - let button = document.createElementNS(kXULNS, "button"); - let label = this.bundle.formatStringFromName("cmd_addFoundEngine", - [engine.title], 1); - button.id = this.telemetryOrigin + "-add-engine-" + - engine.title.replace(/ /g, '-'); - button.setAttribute("class", "addengine-item"); - button.setAttribute("label", label); - button.setAttribute("pack", "start"); - - button.setAttribute("crop", "end"); - button.setAttribute("tooltiptext", engine.uri); - button.setAttribute("uri", engine.uri); - if (engine.icon) { - button.setAttribute("image", engine.icon); - } - button.setAttribute("title", engine.title); - addEngineList.appendChild(button); - } - } - - let settingsButton = - document.getAnonymousElementByAttribute(this, "anonid", - "search-settings-compact"); - // Finally, build the list of one-off buttons. - while (list.firstChild != settingsButton) - list.firstChild.remove(); - // Remove the trailing empty text node introduced by the binding's - // content markup above. - if (settingsButton.nextSibling) - settingsButton.nextSibling.remove(); - - let Preferences = - Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences; - let pref = Preferences.get("browser.search.hiddenOneOffs"); - let hiddenList = pref ? pref.split(",") : []; - - let currentEngineName = Services.search.currentEngine.name; - let includeCurrentEngine = this.getAttribute("includecurrentengine"); - let engines = Services.search.getVisibleEngines().filter(e => { - return (includeCurrentEngine || e.name != currentEngineName) && - !hiddenList.includes(e.name); - }); - - let header = document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs-header") - // header is a xul:deck so collapsed doesn't work on it, see bug 589569. - header.hidden = list.collapsed = !engines.length; - - if (!engines.length) - return; - - let panelWidth = parseInt(this.popup.clientWidth); - // The + 1 is because the last button doesn't have a right border. - let enginesPerRow = Math.floor((panelWidth + 1) / this.buttonWidth); - let buttonWidth = Math.floor(panelWidth / enginesPerRow); - // There will be an emtpy area of: - // panelWidth - enginesPerRow * buttonWidth px - // at the end of each row. - - // If the <description> tag with the list of search engines doesn't have - // a fixed height, the panel will be sized incorrectly, causing the bottom - // of the suggestion <tree> to be hidden. - let oneOffCount = engines.length; - if (this.compact) - ++oneOffCount; - let rowCount = Math.ceil(oneOffCount / enginesPerRow); - let height = rowCount * 33; // 32px per row, 1px border. - list.setAttribute("height", height + "px"); - - // Ensure we can refer to the settings buttons by ID: - let settingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings"); - settingsEl.id = this.telemetryOrigin + "-anon-search-settings"; - let compactSettingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings-compact"); - compactSettingsEl.id = this.telemetryOrigin + - "-anon-search-settings-compact"; - - let dummyItems = enginesPerRow - (oneOffCount % enginesPerRow || enginesPerRow); - for (let i = 0; i < engines.length; ++i) { - let engine = engines[i]; - let button = document.createElementNS(kXULNS, "button"); - button.id = this._buttonIDForEngine(engine); - let uri = "chrome://browser/skin/search-engine-placeholder.png"; - if (engine.iconURI) { - uri = engine.iconURI.spec; - } - button.setAttribute("image", uri); - button.setAttribute("class", "searchbar-engine-one-off-item"); - button.setAttribute("tooltiptext", engine.name); - button.setAttribute("width", buttonWidth); - button.engine = engine; - - if ((i + 1) % enginesPerRow == 0) - button.classList.add("last-of-row"); - - if (i + 1 == engines.length) - button.classList.add("last-engine"); - - if (i >= oneOffCount + dummyItems - enginesPerRow) - button.classList.add("last-row"); - - list.insertBefore(button, settingsButton); - } - - let hasDummyItems = !!dummyItems; - while (dummyItems) { - let button = document.createElementNS(kXULNS, "button"); - button.setAttribute("class", "searchbar-engine-one-off-item dummy last-row"); - button.setAttribute("width", buttonWidth); - - if (!--dummyItems) - button.classList.add("last-of-row"); - - list.insertBefore(button, settingsButton); - } - - if (this.compact) { - this.settingsButton.setAttribute("width", buttonWidth); - if (rowCount == 1 && hasDummyItems) { - // When there's only one row, make the compact settings button - // hug the right edge of the panel. It may not due to the panel's - // width not being an integral multiple of the button width. (See - // the "There will be an emtpy area" comment above.) Increase the - // width of the last dummy item by the remainder. - // - // There's one weird thing to guard against. When layout pixels - // aren't an integral multiple of device pixels, the calculated - // remainder can end up being ~1px too big, at least on Windows, - // which pushes the settings button to a new row. The remainder - // is integral, not a fraction, so that's not the problem. To - // work around that, unscale the remainder, floor it, scale it - // back, and then floor that. - let scale = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .screenPixelsPerCSSPixel; - let remainder = panelWidth - (enginesPerRow * buttonWidth); - remainder = Math.floor(Math.floor(remainder * scale) / scale); - let width = remainder + buttonWidth; - let lastDummyItem = this.settingsButton.previousSibling; - lastDummyItem.setAttribute("width", width); - } - } - ]]></body> - </method> - - <method name="_buttonIDForEngine"> - <parameter name="engine"/> - <body><![CDATA[ - return this.telemetryOrigin + "-engine-one-off-item-" + - engine.name.replace(/ /g, '-'); - ]]></body> - </method> - - <method name="_buttonForEngine"> - <parameter name="engine"/> - <body><![CDATA[ - return document.getElementById(this._buttonIDForEngine(engine)); - ]]></body> - </method> - - <method name="_changeVisuallySelectedButton"> - <parameter name="val"/> - <parameter name="aUpdateLogicallySelectedButton"/> - <body><![CDATA[ - let visuallySelectedButton = this.visuallySelectedButton; - if (visuallySelectedButton) - visuallySelectedButton.removeAttribute("selected"); - - let header = - document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs-header"); - // Avoid selecting dummy buttons. - if (val && !val.classList.contains("dummy")) { - val.setAttribute("selected", "true"); - if (val.classList.contains("searchbar-engine-one-off-item") && - val.engine) { - let headerEngineText = - document.getAnonymousElementByAttribute(this, "anonid", - "searchbar-oneoffheader-engine"); - header.selectedIndex = 2; - headerEngineText.value = val.engine.name; - } - else { - header.selectedIndex = this.query ? 1 : 0; - } - if (this.textbox) { - this.textbox.setAttribute("aria-activedescendant", val.id); - } - } else { - val = null; - header.selectedIndex = this.query ? 1 : 0; - if (this.textbox) { - this.textbox.removeAttribute("aria-activedescendant"); - } - } - - if (aUpdateLogicallySelectedButton) { - this._selectedButton = val; - if (val && !val.engine) { - // If the button doesn't have an engine, then clear the popup's - // selection to indicate that pressing Return while the button is - // selected will do the button's command, not search. - this.popup.selectedIndex = -1; - } - let event = document.createEvent("Events"); - event.initEvent("SelectedOneOffButtonChanged", true, false); - this.dispatchEvent(event); - } - ]]></body> - </method> - - <method name="getSelectableButtons"> - <parameter name="aIncludeNonEngineButtons"/> - <body><![CDATA[ - let buttons = []; - let oneOff = document.getAnonymousElementByAttribute(this, "anonid", - "search-panel-one-offs"); - for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) { - // oneOff may be a text node since the list xul:description contains - // whitespace and the compact settings button. See the markup - // above. _rebuild removes text nodes, but it may not have been - // called yet (because e.g. the popup hasn't been opened yet). - if (oneOff.nodeType == Node.ELEMENT_NODE) { - if (oneOff.classList.contains("dummy") || - oneOff.classList.contains("search-setting-button-compact")) - break; - buttons.push(oneOff); - } - } - - if (!aIncludeNonEngineButtons) - return buttons; - - let addEngine = - document.getAnonymousElementByAttribute(this, "anonid", "add-engines"); - for (addEngine = addEngine.firstChild; addEngine; addEngine = addEngine.nextSibling) - buttons.push(addEngine); - - buttons.push(this.settingsButton); - return buttons; - ]]></body> - </method> - - <method name="handleSearchCommand"> - <parameter name="aEvent"/> - <parameter name="aEngine"/> - <parameter name="aForceNewTab"/> - <body><![CDATA[ - let where = "current"; - let params; - - // Open ctrl/cmd clicks on one-off buttons in a new background tab. - if (aForceNewTab) { - where = "tab"; - if (Services.prefs.getBoolPref("browser.tabs.loadInBackground")) { - params = { - inBackground: true, - }; - } - } - else { - var newTabPref = Services.prefs.getBoolPref("browser.search.openintab"); - if (((aEvent instanceof KeyboardEvent) && aEvent.altKey) ^ newTabPref) - where = "tab"; - if ((aEvent instanceof MouseEvent) && - (aEvent.button == 1 || aEvent.getModifierState("Accel"))) { - where = "tab"; - params = { - inBackground: true, - }; - } - } - - this.popup.handleOneOffSearch(aEvent, aEngine, where, params); - ]]></body> - </method> - - <!-- - Increments or decrements the index of the currently selected one-off. - - @param aForward - If true, the index is incremented, and if false, the index is - decremented. - @param aWrapAround - This has a couple of effects, depending on whether there is - currently a selection. - (1) If true and the last one-off is currently selected, - incrementing the index will cause the selection to be cleared and - this method to return true. Calling advanceSelection again after - that (again with aForward=true) will select the first one-off. - Likewise if decrementing the index when the first one-off is - selected, except in the opposite direction of course. - (2) If true and there currently is no selection, decrementing the - index will cause the last one-off to become selected and this - method to return true. Only the aForward=false case is affected - because it is always the case that if aForward=true and there - currently is no selection, the first one-off becomes selected and - this method returns true. - @param aCycleEngines - If true, only engine buttons are included. - @return True if the selection can continue to advance after this method - returns and false if not. - --> - <method name="advanceSelection"> - <parameter name="aForward"/> - <parameter name="aWrapAround"/> - <parameter name="aCycleEngines"/> - <body><![CDATA[ - let selectedButton = this.selectedButton; - let buttons = this.getSelectableButtons(aCycleEngines); - - if (selectedButton) { - // cycle through one-off buttons. - let index = buttons.indexOf(selectedButton); - if (aForward) - ++index; - else - --index; - - if (index >= 0 && index < buttons.length) - this.selectedButton = buttons[index]; - else - this.selectedButton = null; - - if (this.selectedButton || aWrapAround) - return true; - - return false; - } - - // If no selection, select the first button or ... - if (aForward) { - this.selectedButton = buttons[0]; - return true; - } - - if (!aForward && aWrapAround) { - // the last button. - this.selectedButton = buttons[buttons.length - 1]; - return true; - } - - return false; - ]]></body> - </method> - - <!-- - This handles key presses specific to the one-off buttons like Tab and - Alt-Up/Down, and Up/Down keys within the buttons. Since one-off buttons - are always used in conjunction with a list of some sort (in this.popup), - it also handles Up/Down keys that cross the boundaries between list - items and the one-off buttons. - - @param event - The key event. - @param numListItems - The number of items in the list. The reason that this is a - parameter at all is that the list may contain items at the end - that should be ignored, depending on the consumer. That's true - for the urlbar for example. - @param allowEmptySelection - Pass true if it's OK that neither the list nor the one-off - buttons contains a selection. Pass false if either the list or - the one-off buttons (or both) should always contain a selection. - @param textboxUserValue - When the last list item is selected and the user presses Down, - the first one-off becomes selected and the textbox value is - restored to the value that the user typed. Pass that value here. - However, if you pass true for allowEmptySelection, you don't need - to pass anything for this parameter. (Pass undefined or null.) - @return True if this method handled the keypress and false if not. If - false, then you should let the autocomplete controller handle - the keypress. The value of event.defaultPrevented will be the - same as this return value. - --> - <method name="handleKeyPress"> - <parameter name="event"/> - <parameter name="numListItems"/> - <parameter name="allowEmptySelection"/> - <parameter name="textboxUserValue"/> - <body><![CDATA[ - if (!this.popup) { - return false; - } - - let stopEvent = false; - - // Tab cycles through the one-offs and moves the focus out at the end. - // But only if non-Shift modifiers aren't also pressed, to avoid - // clobbering other shortcuts. - if (event.keyCode == KeyEvent.DOM_VK_TAB && - !event.altKey && - !event.ctrlKey && - !event.metaKey && - this.getAttribute("disabletab") != "true") { - stopEvent = this.advanceSelection(!event.shiftKey, false, true); - } - - // Alt + up/down is very similar to (shift +) tab but differs in that - // it loops through the list, whereas tab will move the focus out. - else if (event.altKey && - (event.keyCode == KeyEvent.DOM_VK_DOWN || - event.keyCode == KeyEvent.DOM_VK_UP)) { - stopEvent = - this.advanceSelection(event.keyCode == KeyEvent.DOM_VK_DOWN, - true, false); - } - - else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP) { - if (numListItems > 0) { - if (this.popup.selectedIndex > 0) { - // The autocomplete controller should handle this case. - } else if (this.popup.selectedIndex == 0) { - if (!allowEmptySelection) { - // Wrap around the selection to the last one-off. - this.selectedButton = null; - this.popup.selectedIndex = -1; - // Call advanceSelection after setting selectedIndex so that - // screen readers see the newly selected one-off. Both trigger - // accessibility events. - this.advanceSelection(false, true, true); - stopEvent = true; - } - } else { - let firstButtonSelected = - this.selectedButton && - this.selectedButton == this.getSelectableButtons(true)[0]; - if (firstButtonSelected) { - this.selectedButton = null; - } else { - stopEvent = this.advanceSelection(false, true, true); - } - } - } else { - stopEvent = this.advanceSelection(false, true, true); - } - } - - else if (event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) { - if (numListItems > 0) { - if (this.popup.selectedIndex >= 0 && - this.popup.selectedIndex < numListItems - 1) { - // The autocomplete controller should handle this case. - } else if (this.popup.selectedIndex == numListItems - 1) { - this.selectedButton = null; - if (!allowEmptySelection) { - this.popup.selectedIndex = -1; - stopEvent = true; - } - if (this.textbox && typeof(textboxUserValue) == "string") { - this.textbox.value = textboxUserValue; - } - // Call advanceSelection after setting selectedIndex so that - // screen readers see the newly selected one-off. Both trigger - // accessibility events. - this.advanceSelection(true, true, true); - } else { - let buttons = this.getSelectableButtons(true); - let lastButtonSelected = - this.selectedButton && - this.selectedButton == buttons[buttons.length - 1]; - if (lastButtonSelected) { - this.selectedButton = null; - stopEvent = allowEmptySelection; - } else if (this.selectedButton) { - stopEvent = this.advanceSelection(true, true, true); - } else { - // The autocomplete controller should handle this case. - } - } - } else { - stopEvent = this.advanceSelection(true, true, true); - } - } - - if (stopEvent) { - event.preventDefault(); - event.stopPropagation(); - return true; - } - return false; - ]]></body> - </method> - - <!-- - If the given event is related to the one-offs, this method records - one-off telemetry for it. this.telemetryOrigin will be appended to the - computed source, so make sure you set that first. - - @param aEvent - An event, like a click on a one-off button. - @param aOpenUILinkWhere - The "where" passed to openUILink. - @param aOpenUILinkParams - The "params" passed to openUILink. - @return True if telemetry was recorded and false if not. - --> - <method name="maybeRecordTelemetry"> - <parameter name="aEvent"/> - <parameter name="aOpenUILinkWhere"/> - <parameter name="aOpenUILinkParams"/> - <body><![CDATA[ - if (!aEvent) { - return false; - } - - let source = null; - let type = "unknown"; - let engine = null; - let target = aEvent.originalTarget; - - if (aEvent instanceof KeyboardEvent) { - type = "key"; - if (this.selectedButton) { - source = "oneoff"; - engine = this.selectedButton.engine; - } - } else if (aEvent instanceof MouseEvent) { - type = "mouse"; - if (target.classList.contains("searchbar-engine-one-off-item")) { - source = "oneoff"; - engine = target.engine; - } - } else if ((aEvent instanceof XULCommandEvent) && - target.getAttribute("anonid") == - "search-one-offs-context-open-in-new-tab") { - source = "oneoff-context"; - engine = this._contextEngine; - } - - if (!source) { - return false; - } - - if (this.telemetryOrigin) { - source += "-" + this.telemetryOrigin; - } - - let tabBackground = aOpenUILinkWhere == "tab" && - aOpenUILinkParams && - aOpenUILinkParams.inBackground; - let where = tabBackground ? "tab-background" : aOpenUILinkWhere; - BrowserSearch.recordOneoffSearchInTelemetry(engine, source, type, - where); - return true; - ]]></body> - </method> - - </implementation> - - <handlers> - - <handler event="mousedown"><![CDATA[ - // Required to receive click events from the buttons on Linux. - event.preventDefault(); - ]]></handler> - - <handler event="mousemove"><![CDATA[ - let target = event.originalTarget; - if (target.localName != "button") - return; - - // Ignore mouse events when the context menu is open. - if (this._ignoreMouseEvents) - return; - - if ((target.classList.contains("searchbar-engine-one-off-item") && - !target.classList.contains("dummy")) || - target.classList.contains("addengine-item") || - target.classList.contains("search-setting-button")) { - this._changeVisuallySelectedButton(target); - } - ]]></handler> - - <handler event="mouseout"><![CDATA[ - let target = event.originalTarget; - if (target.localName != "button") { - return; - } - - // Don't deselect the current button if the context menu is open. - if (this._ignoreMouseEvents) - return; - - // Unfortunately this will fire before mouseover hits another item. - // If this button is selected, we replace that selection only if - // we're not moving to a different one-off item: - if (target.getAttribute("selected") == "true" && - (!event.relatedTarget || - !event.relatedTarget.classList.contains("searchbar-engine-one-off-item") || - event.relatedTarget.classList.contains("dummy"))) { - this._changeVisuallySelectedButton(this.selectedButton); - } - ]]></handler> - - <handler event="click"><![CDATA[ - if (event.button == 2) - return; // ignore right clicks. - - let button = event.originalTarget; - let engine = button.engine; - - if (!engine) - return; - - // Select the clicked button so that consumers can easily tell which - // button was acted on. - this.selectedButton = button; - this.handleSearchCommand(event, engine); - ]]></handler> - - <handler event="command"><![CDATA[ - let target = event.originalTarget; - if (target.classList.contains("addengine-item")) { - // On success, hide the panel and tell event listeners to reshow it to - // show the new engine. - let installCallback = { - onSuccess: engine => { - this._rebuild(); - }, - onError: function(errorCode) { - if (errorCode != Ci.nsISearchInstallCallback.ERROR_DUPLICATE_ENGINE) { - // Download error is shown by the search service - return; - } - const kSearchBundleURI = "chrome://global/locale/search/search.properties"; - let searchBundle = Services.strings.createBundle(kSearchBundleURI); - let brandBundle = document.getElementById("bundle_brand"); - let brandName = brandBundle.getString("brandShortName"); - let title = searchBundle.GetStringFromName("error_invalid_engine_title"); - let text = searchBundle.formatStringFromName("error_duplicate_engine_msg", - [brandName, target.getAttribute("uri")], 2); - Services.prompt.QueryInterface(Ci.nsIPromptFactory); - let prompt = Services.prompt.getPrompt(gBrowser.contentWindow, Ci.nsIPrompt); - prompt.QueryInterface(Ci.nsIWritablePropertyBag2); - prompt.setPropertyAsBool("allowTabModal", true); - prompt.alert(title, text); - } - } - Services.search.addEngine(target.getAttribute("uri"), null, - target.getAttribute("image"), false, - installCallback); - } - let anonid = target.getAttribute("anonid"); - if (anonid == "search-one-offs-context-open-in-new-tab") { - // Select the context-clicked button so that consumers can easily - // tell which button was acted on. - this.selectedButton = this._buttonForEngine(this._contextEngine); - this.handleSearchCommand(event, this._contextEngine, true); - } - if (anonid == "search-one-offs-context-set-default") { - let currentEngine = Services.search.currentEngine; - - if (!this.getAttribute("includecurrentengine")) { - // Make the target button of the context menu reflect the current - // search engine first. Doing this as opposed to rebuilding all the - // one-off buttons avoids flicker. - let button = this._buttonForEngine(this._contextEngine); - button.id = this._buttonIDForEngine(currentEngine); - let uri = "chrome://browser/skin/search-engine-placeholder.png"; - if (currentEngine.iconURI) - uri = currentEngine.iconURI.spec; - button.setAttribute("image", uri); - button.setAttribute("tooltiptext", currentEngine.name); - button.engine = currentEngine; - } - - Services.search.currentEngine = this._contextEngine; - } - ]]></handler> - - <handler event="contextmenu"><![CDATA[ - let target = event.originalTarget; - // Prevent the context menu from appearing except on the one off buttons. - if (!target.classList.contains("searchbar-engine-one-off-item") || - target.classList.contains("dummy")) { - event.preventDefault(); - return; - } - document.getAnonymousElementByAttribute(this, "anonid", "search-one-offs-context-set-default") - .setAttribute("disabled", target.engine == Services.search.currentEngine); - - this._contextEngine = target.engine; - ]]></handler> - </handlers> - - </binding> - -</bindings> |