%searchBarDTD; %browserDTD; ]> false false document.getAnonymousElementByAttribute(this, "anonid", "searchbar-stringbundle"); false document.getAnonymousElementByAttribute(this, "anonid", "searchbar-textbox"); null (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory; = 0 && newIndex < this.engines.length) { this.currentEngine = this.engines[newIndex]; } aEvent.preventDefault(); aEvent.stopPropagation(); this.openSuggestionsPanel(); ]]> this.currentEngine = engine } Services.search.addEngine(target.getAttribute("uri"), null, target.getAttribute("src"), false, installCallback); } else return; this.focus(); this.select(); ]]> { 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); ]]> 100 ? width : 100); var yOffset = outerRect.bottom - innerRect.bottom; popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false); } ]]> { return button.getAttribute("selected") == "true" }); if (visuallySelectedButton) visuallySelectedButton.removeAttribute("selected"); let textbox = document.getBindingParent(this).textbox; let header = document.getAnonymousElementByAttribute(this.popup, "anonid", "search-panel-one-offs-header"); // Avoid selecting dummy buttons. if (val && !val.classList.contains("dummy")) { val.setAttribute("selected", "true"); if (aUpdateLogicallySelectedButton) this._selectedButton = val; if (val.classList.contains("searchbar-engine-one-off-item")) { let headerEngineText = document.getAnonymousElementByAttribute(this.popup, "anonid", "searchbar-oneoffheader-engine"); header.selectedIndex = 2; headerEngineText.value = val.engine.name; } else { header.selectedIndex = textbox.value ? 1 : 0; } this.setAttribute("aria-activedescendant", val.id); } else { header.selectedIndex = textbox.value ? 1 : 0; this.removeAttribute("aria-activedescendant"); if (aUpdateLogicallySelectedButton) this._selectedButton = null; } ]]> = 0 && index < buttons.length) this.selectedButton = buttons[index]; else this.selectedButton = null; if (this.selectedButton || aCycleEngines || suggestionsHidden) return true; // Set the selectedIndex to something that will make // handleKeyNavigation (called by autocomplete.xml's onKeyPress // method) reset the text field value to what the user typed. // Doesn't work when aSkipSuggestions=true, see bug 1124747. if (aForward) popup.selectedIndex = popup.view.rowCount - 1; else popup.selectedIndex = popup.view.rowCount; return false; } if (!selectedButton) { // If no selection, select the first button or ... if (aForward && aSkipSuggestions) { this.selectedButton = buttons[0]; return true; } if (!aForward && (aCycleEngines || suggestionsHidden || (!aSkipSuggestions && popup.selectedIndex == -1))) { // the last button. this.selectedButton = buttons[buttons.length - 1]; return true; } } return false; ]]> false null null 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(); }); ]]> = 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(); // Update the 'Search for with:" header. 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"); let textbox = searchbar.textbox; let self = this; let inputHandler = function() { headerSearchText.setAttribute("value", textbox.value); 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 (textbox.value) { self.removeAttribute("showonlysettings"); groupText = headerSearchText.previousSibling.value + '"' + headerSearchText.value + '"' + headerSearchText.nextSibling.value; if (!isOneOffSelected) headerPanel.selectedIndex = 1; } else { let noSearchHeader = document.getAnonymousElementByAttribute(self, "anonid", "searchbar-oneoffheader-search"); groupText = noSearchHeader.value; if (!isOneOffSelected) headerPanel.selectedIndex = 0; } list.setAttribute("aria-label", groupText); }; textbox.addEventListener("input", inputHandler); this.addEventListener("popuphiding", function hiding() { textbox.removeEventListener("input", inputHandler); this.removeEventListener("popuphiding", hiding); }); inputHandler(); // 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"; let addEngines = gBrowser.selectedBrowser.engines; if (addEngines && addEngines.length > 0) { for (let engine of addEngines) { let button = document.createElementNS(kXULNS, "button"); let label = this.bundle.formatStringFromName("cmd_addFoundEngine", [engine.title], 1); button.id = "searchbar-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); } } // Finally, build the list of one-off buttons. while (list.firstChild) list.firstChild.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 engines = Services.search.getVisibleEngines() .filter(e => e.name != currentEngineName && hiddenList.indexOf(e.name) == -1); 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; // 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. const ENGINE_WIDTH = 49; let panel = document.getElementById("PopupSearchAutoComplete"); // 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(panel)[ ltr ? "marginLeft" : "marginRight" ]) * -1; let minWidth = parseInt(panel.width) + magnifierWidth; if (engines.length) { // Ensure the panel is wide enough to fit at least 3 engines. minWidth = Math.max(minWidth, ENGINE_WIDTH * 3); } panel.style.minWidth = minWidth + "px"; if (!engines.length) return; let panelWidth = parseInt(panel.clientWidth); // The + 1 is because the last button doesn't have a right border. let enginesPerRow = Math.floor((panelWidth + 1) / ENGINE_WIDTH); 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 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 to be hidden. let rowCount = Math.ceil(engines.length / enginesPerRow); let height = rowCount * 33; // 32px per row, 1px border. list.setAttribute("height", height + "px"); // Ensure we can refer to the settings button by ID: let settingsEl = document.getAnonymousElementByAttribute(this, "anonid", "search-settings"); settingsEl.id = this.id + "-anon-search-settings"; let dummyItems = enginesPerRow - (engines.length % enginesPerRow || enginesPerRow); for (let i = 0; i < engines.length; ++i) { let engine = engines[i]; let button = document.createElementNS(kXULNS, "button"); button.id = "searchbar-engine-one-off-item-" + engine.name.replace(/ /g, '-'); 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 >= engines.length + dummyItems - enginesPerRow) button.classList.add("last-row"); list.appendChild(button); } 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.appendChild(button); } ]]> { this._isHiding = false; }, 0); ]]>