%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); } ]]> false null document.getAnonymousElementByAttribute(this, "anonid", "search-one-off-buttons"); = 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(); ]]> { this._isHiding = false; }, 0); ]]> null null "" "" null { return button.getAttribute("selected") == "true"; }); ]]> 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(); }); ]]> { this.selectedButton = null; this._contextEngine = null; }, Ci.nsIThread.DISPATCH_NORMAL); break; } ]]> 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. this._rebuildAddEngineList(); 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 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 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"; const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 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); } } ]]> 5 this._addEngineMenuThreshold; if (tooManyEngines) { // Make the top-level menu button. let button = document.createElementNS(kXULNS, "button"); list.appendChild(button); button.classList.add("addengine-item"); button.setAttribute("anonid", "addengine-menu-button"); button.setAttribute("type", "menu"); button.setAttribute("label", this.bundle.GetStringFromName("cmd_addFoundEngineMenu")); button.setAttribute("crop", "end"); button.setAttribute("pack", "start"); // Set the menu button's image to the image of the first engine. The // offered engines may have differing images, so there's no perfect // choice here. let engine = engines[0]; if (engine.icon) { button.setAttribute("image", engine.icon); } // Now make the button's child menupopup. list = document.createElementNS(kXULNS, "menupopup"); button.appendChild(list); list.setAttribute("anonid", "addengine-menu"); list.setAttribute("position", "topright topleft"); // Events from child menupopups bubble up to the autocomplete binding, // which breaks it, so prevent these events from propagating. let suppressEventTypes = [ "popupshowing", "popuphiding", "popupshown", "popuphidden", ]; for (let type of suppressEventTypes) { list.addEventListener(type, event => { event.stopPropagation(); }); } } // Finally, add the engines to the list. If there aren't too many // engines, the list is the add-engines vbox. Otherwise it's the // menupopup created earlier. In the latter case, create menuitem // elements instead of buttons, because buttons don't get keyboard // handling for free inside menupopups. let eltType = tooManyEngines ? "menuitem" : "button"; for (let engine of engines) { let button = document.createElementNS(kXULNS, eltType); button.classList.add("addengine-item"); button.id = this.telemetryOrigin + "-add-engine-" + this._fixUpEngineNameForID(engine.title); let label = this.bundle.formatStringFromName("cmd_addFoundEngine", [engine.title], 1); button.setAttribute("label", label); button.setAttribute("crop", "end"); button.setAttribute("tooltiptext", engine.uri); button.setAttribute("uri", engine.uri); button.setAttribute("title", engine.title); if (engine.icon) { button.setAttribute("image", engine.icon); } if (tooManyEngines) { button.classList.add("menuitem-iconic"); } else { button.setAttribute("pack", "start"); } list.appendChild(button); } ]]> = 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; ]]> 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); } } else if (this.selectedButton && this.selectedButton.getAttribute("anonid") == "addengine-menu-button" && event.keyCode == KeyEvent.DOM_VK_RIGHT) { // If the add-engine overflow menu item is selected and the user // presses the right arrow key, open the submenu. Unfortunately // handling the left arrow key -- to close the popup -- isn't // straightforward. Once the popup is open, it consumes all key // events. Setting ignorekeys=handled on it doesn't help, since the // popup handles all arrow keys. Setting ignorekeys=true on it does // mean that the popup no longer consumes the left arrow key, but then // it no longer handles up/down keys to select items in the popup. this.selectedButton.open = true; stopEvent = true; } if (stopEvent) { event.preventDefault(); event.stopPropagation(); return true; } return false; ]]> 200 null false { delete this._addEngineMenuTimeout; let button = document.getAnonymousElementByAttribute( this, "anonid", "addengine-menu-button" ); button.open = this._addEngineMenuShouldBeOpen; }, this._addEngineMenuTimeoutMs); ]]> { this._rebuild(); }, onError(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; } ]]>