diff options
Diffstat (limited to 'application/palemoon/base/content/autocomplete.xml')
-rw-r--r-- | application/palemoon/base/content/autocomplete.xml | 2128 |
1 files changed, 0 insertions, 2128 deletions
diff --git a/application/palemoon/base/content/autocomplete.xml b/application/palemoon/base/content/autocomplete.xml deleted file mode 100644 index bd0928436..000000000 --- a/application/palemoon/base/content/autocomplete.xml +++ /dev/null @@ -1,2128 +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/. --> - -<bindings id="privateAutocompleteBindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - xmlns:xbl="http://www.mozilla.org/xbl"> - - <binding id="private-autocomplete" role="xul:combobox" - extends="chrome://global/content/bindings/textbox.xml#textbox"> - <resources> - <stylesheet src="chrome://browser/content/autocomplete.css"/> - <stylesheet src="chrome://browser/skin/autocomplete.css"/> - </resources> - - <content sizetopopup="pref"> - <xul:hbox class="private-autocomplete-textbox-container" flex="1" xbl:inherits="focused"> - <children includes="image|deck|stack|box"> - <xul:image class="private-autocomplete-icon" allowevents="true"/> - </children> - - <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext"> - <children/> - <html:input anonid="input" class="private-autocomplete-textbox textbox-input" - allowevents="true" - xbl:inherits="tooltiptext=inputtooltiptext,value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/> - </xul:hbox> - <children includes="hbox"/> - </xul:hbox> - - <xul:dropmarker anonid="historydropmarker" class="private-autocomplete-history-dropmarker" - allowevents="true" - xbl:inherits="open,enablehistory,parentfocused=focused"/> - - <xul:popupset anonid="popupset" class="private-autocomplete-result-popupset"/> - - <children includes="toolbarbutton"/> - </content> - - <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement"> - <field name="mController">null</field> - <field name="mSearchNames">null</field> - <field name="mIgnoreInput">false</field> - <field name="mEnterEvent">null</field> - - <field name="_searchBeginHandler">null</field> - <field name="_searchCompleteHandler">null</field> - <field name="_textEnteredHandler">null</field> - <field name="_textRevertedHandler">null</field> - - <constructor><![CDATA[ - this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"]. - getService(Components.interfaces.nsIAutoCompleteController); - - this._searchBeginHandler = this.initEventHandler("searchbegin"); - this._searchCompleteHandler = this.initEventHandler("searchcomplete"); - this._textEnteredHandler = this.initEventHandler("textentered"); - this._textRevertedHandler = this.initEventHandler("textreverted"); - - // For security reasons delay searches on pasted values. - this.inputField.controllers.insertControllerAt(0, this._pasteController); - ]]></constructor> - - <destructor><![CDATA[ - this.inputField.controllers.removeController(this._pasteController); - ]]></destructor> - - <!-- =================== nsIAutoCompleteInput =================== --> - - <field name="popup"><![CDATA[ - // Wrap in a block so that the let statements don't - // create properties on 'this' (bug 635252). - { - let popup = null; - let popupId = this.getAttribute("autocompletepopup"); - if (popupId) - popup = document.getElementById(popupId); - if (!popup) { - popup = document.createElement("panel"); - popup.setAttribute("type", "autocomplete"); - popup.setAttribute("noautofocus", "true"); - - let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset"); - popupset.appendChild(popup); - } - popup.mInput = this; - popup; - } - ]]></field> - - <property name="controller" onget="return this.mController;" readonly="true"/> - - <property name="popupOpen" - onget="return this.popup.popupOpen;" - onset="if (val) this.openPopup(); else this.closePopup();"/> - - <property name="disableAutoComplete" - onset="this.setAttribute('disableautocomplete', val); return val;" - onget="return this.getAttribute('disableautocomplete') == 'true';"/> - - <property name="completeDefaultIndex" - onset="this.setAttribute('completedefaultindex', val); return val;" - onget="return this.getAttribute('completedefaultindex') == 'true';"/> - - <property name="completeSelectedIndex" - onset="this.setAttribute('completeselectedindex', val); return val;" - onget="return this.getAttribute('completeselectedindex') == 'true';"/> - - <property name="forceComplete" - onset="this.setAttribute('forcecomplete', val); return val;" - onget="return this.getAttribute('forcecomplete') == 'true';"/> - - <property name="minResultsForPopup" - onset="this.setAttribute('minresultsforpopup', val); return val;" - onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/> - - <property name="showCommentColumn" - onset="this.setAttribute('showcommentcolumn', val); return val;" - onget="return this.getAttribute('showcommentcolumn') == 'true';"/> - - <property name="showImageColumn" - onset="this.setAttribute('showimagecolumn', val); return val;" - onget="return this.getAttribute('showimagecolumn') == 'true';"/> - - <property name="timeout" - onset="this.setAttribute('timeout', val); return val;"> - <getter><![CDATA[ - // For security reasons delay searches on pasted values. - if (this._valueIsPasted) { - let t = parseInt(this.getAttribute('pastetimeout')); - return isNaN(t) ? 1000 : t; - } - - let t = parseInt(this.getAttribute('timeout')); - return isNaN(t) ? 50 : t; - ]]></getter> - </property> - - <property name="searchParam" - onget="return this.getAttribute('autocompletesearchparam') || '';" - onset="this.setAttribute('autocompletesearchparam', val); return val;"/> - - <property name="searchCount" readonly="true" - onget="this.initSearchNames(); return this.mSearchNames.length;"/> - - <field name="shrinkDelay" readonly="true"> - parseInt(this.getAttribute("shrinkdelay")) || 0 - </field> - - <field name="PrivateBrowsingUtils" readonly="true"> - { - let utils = {}; - Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils); - utils.PrivateBrowsingUtils - } - </field> - - <property name="inPrivateContext" readonly="true" - onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/> - - <property name="noRollupOnCaretMove" readonly="true" - onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/> - - <!-- This is the maximum number of drop-down rows we get when we - hit the drop marker beside fields that have it (like the URLbar).--> - <field name="maxDropMarkerRows" readonly="true">14</field> - - <method name="getSearchAt"> - <parameter name="aIndex"/> - <body><![CDATA[ - this.initSearchNames(); - return this.mSearchNames[aIndex]; - ]]></body> - </method> - - <property name="textValue" - onget="return this.value;"> - <setter><![CDATA[ - // Completing a result should simulate the user typing the result, - // so fire an input event. - // Trim popup selected values, but never trim results coming from - // autofill. - if (this.popup.selectedIndex == -1) - this._disableTrim = true; - this.value = val; - this._disableTrim = false; - - var evt = document.createEvent("UIEvents"); - evt.initUIEvent("input", true, false, window, 0); - this.mIgnoreInput = true; - this.dispatchEvent(evt); - this.mIgnoreInput = false; - return this.value; - ]]></setter> - </property> - - <method name="selectTextRange"> - <parameter name="aStartIndex"/> - <parameter name="aEndIndex"/> - <body><![CDATA[ - this.inputField.setSelectionRange(aStartIndex, aEndIndex); - ]]></body> - </method> - - <method name="onSearchBegin"> - <body><![CDATA[ - if (this.popup && typeof this.popup.onSearchBegin == "function") - this.popup.onSearchBegin(); - if (this._searchBeginHandler) - this._searchBeginHandler(); - ]]></body> - </method> - - <method name="onSearchComplete"> - <body><![CDATA[ - if (this.mController.matchCount == 0) - this.setAttribute("nomatch", "true"); - else - this.removeAttribute("nomatch"); - - if (this.ignoreBlurWhileSearching && !this.focused) { - this.handleEnter(); - this.detachController(); - } - - if (this._searchCompleteHandler) - this._searchCompleteHandler(); - ]]></body> - </method> - - <method name="onTextEntered"> - <body><![CDATA[ - let rv = false; - if (this._textEnteredHandler) - rv = this._textEnteredHandler(this.mEnterEvent); - this.mEnterEvent = null; - return rv; - ]]></body> - </method> - - <method name="onTextReverted"> - <body><![CDATA[ - if (this._textRevertedHandler) - return this._textRevertedHandler(); - return false; - ]]></body> - </method> - - <!-- =================== nsIDOMXULMenuListElement =================== --> - - <property name="editable" readonly="true" - onget="return true;" /> - - <property name="crop" - onset="this.setAttribute('crop',val); return val;" - onget="return this.getAttribute('crop');"/> - - <property name="open" - onget="return this.getAttribute('open') == 'true';"> - <setter><![CDATA[ - if (val) - this.showHistoryPopup(); - else - this.closePopup(); - ]]></setter> - </property> - - <!-- =================== PUBLIC MEMBERS =================== --> - - <field name="valueIsTyped">false</field> - <field name="_disableTrim">false</field> - <property name="value"> - <getter><![CDATA[ - if (typeof this.onBeforeValueGet == "function") { - var result = this.onBeforeValueGet(); - if (result) - return result.value; - } - return this.inputField.value; - ]]></getter> - <setter><![CDATA[ - this.mIgnoreInput = true; - - if (typeof this.onBeforeValueSet == "function") - val = this.onBeforeValueSet(val); - - if (typeof this.trimValue == "function" && !this._disableTrim) - val = this.trimValue(val); - - this.valueIsTyped = false; - this.inputField.value = val; - - if (typeof this.formatValue == "function") - this.formatValue(); - - this.mIgnoreInput = false; - var event = document.createEvent('Events'); - event.initEvent('ValueChange', true, true); - this.inputField.dispatchEvent(event); - return val; - ]]></setter> - </property> - - <property name="focused" readonly="true" - onget="return this.getAttribute('focused') == 'true';"/> - - <!-- maximum number of rows to display at a time --> - <property name="maxRows" - onset="this.setAttribute('maxrows', val); return val;" - onget="return parseInt(this.getAttribute('maxrows')) || 0;"/> - - <!-- option to allow scrolling through the list via the tab key, rather than - tab moving focus out of the textbox --> - <property name="tabScrolling" - onset="this.setAttribute('tabscrolling', val); return val;" - onget="return this.getAttribute('tabscrolling') == 'true';"/> - - <!-- option to completely ignore any blur events while searches are - still going on. --> - <property name="ignoreBlurWhileSearching" - onset="this.setAttribute('ignoreblurwhilesearching', val); return val;" - onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/> - - <!-- disable key navigation handling in the popup results --> - <property name="disableKeyNavigation" - onset="this.setAttribute('disablekeynavigation', val); return val;" - onget="return this.getAttribute('disablekeynavigation') == 'true';"/> - - <!-- option to highlight entries that don't have any matches --> - <property name="highlightNonMatches" - onset="this.setAttribute('highlightnonmatches', val); return val;" - onget="return this.getAttribute('highlightnonmatches') == 'true';"/> - - <!-- =================== PRIVATE MEMBERS =================== --> - - <!-- ::::::::::::: autocomplete controller ::::::::::::: --> - - <method name="attachController"> - <body><![CDATA[ - this.mController.input = this; - ]]></body> - </method> - - <method name="detachController"> - <body><![CDATA[ - if (this.mController.input == this) - this.mController.input = null; - ]]></body> - </method> - - <!-- ::::::::::::: popup opening ::::::::::::: --> - - <method name="openPopup"> - <body><![CDATA[ - if (this.focused) - this.popup.openAutocompletePopup(this, this); - ]]></body> - </method> - - <method name="closePopup"> - <body><![CDATA[ - this.popup.closePopup(); - ]]></body> - </method> - - <method name="showHistoryPopup"> - <body><![CDATA[ - // history dropmarker pushed state - function cleanup(popup) { - popup.removeEventListener("popupshowing", onShow, false); - } - function onShow(event) { - var popup = event.target, input = popup.input; - cleanup(popup); - input.setAttribute("open", "true"); - function onHide() { - input.removeAttribute("open"); - popup.removeEventListener("popuphiding", onHide, false); - } - popup.addEventListener("popuphiding", onHide, false); - } - this.popup.addEventListener("popupshowing", onShow, false); - setTimeout(cleanup, 1000, this.popup); - - // Store our "normal" maxRows on the popup, so that it can reset the - // value when the popup is hidden. - this.popup._normalMaxRows = this.maxRows; - - // Increase our maxRows temporarily, since we want the dropdown to - // be bigger in this case. The popup's popupshowing/popuphiding - // handlers will take care of resetting this. - this.maxRows = this.maxDropMarkerRows; - - // Ensure that we have focus. - if (!this.focused) - this.focus(); - this.attachController(); - this.mController.startSearch(""); - ]]></body> - </method> - - <method name="toggleHistoryPopup"> - <body><![CDATA[ - // If this method is called on the same event tick as the popup gets - // hidden, do nothing to avoid re-opening the popup when the drop - // marker is clicked while the popup is still open. - if (!this.popup.isPopupHidingTick && !this.popup.popupOpen) - this.showHistoryPopup(); - else - this.closePopup(); - ]]></body> - </method> - - <!-- ::::::::::::: event dispatching ::::::::::::: --> - - <method name="initEventHandler"> - <parameter name="aEventType"/> - <body><![CDATA[ - let handlerString = this.getAttribute("on" + aEventType); - if (handlerString) { - return (new Function("eventType", "param", handlerString)).bind(this, aEventType); - } - return null; - ]]></body> - </method> - - <!-- ::::::::::::: key handling ::::::::::::: --> - - <field name="_selectionDetails">null</field> - <method name="onKeyPress"> - <parameter name="aEvent"/> - <body><![CDATA[ - return this.handleKeyPress(aEvent); - ]]></body> - </method> - - <method name="handleKeyPress"> - <parameter name="aEvent"/> - <body><![CDATA[ - if (aEvent.target.localName != "textbox") - return true; // Let child buttons of autocomplete take input - - //XXXpch this is so bogus... - if (aEvent.defaultPrevented) - return false; - - var cancel = false; - - let { AppConstants } = - Components.utils.import("resource://gre/modules/AppConstants.jsm", {}); - // Catch any keys that could potentially move the caret. Ctrl can be - // used in combination with these keys on Windows and Linux; and Alt - // can be used on OS X, so make sure the unused one isn't used. - let metaKey = AppConstants.platform == "macosx" ? aEvent.ctrlKey : aEvent.altKey; - if (!this.disableKeyNavigation && !metaKey) { - switch (aEvent.keyCode) { - case KeyEvent.DOM_VK_LEFT: - case KeyEvent.DOM_VK_RIGHT: - case KeyEvent.DOM_VK_HOME: - cancel = this.mController.handleKeyNavigation(aEvent.keyCode); - break; - } - } - - // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt) - if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) { - switch (aEvent.keyCode) { - case KeyEvent.DOM_VK_TAB: - if (this.tabScrolling && this.popup.popupOpen) - cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ? - KeyEvent.DOM_VK_UP : - KeyEvent.DOM_VK_DOWN); - else if (this.forceComplete && this.mController.matchCount >= 1) - this.mController.handleTab(); - break; - case KeyEvent.DOM_VK_UP: - case KeyEvent.DOM_VK_DOWN: - case KeyEvent.DOM_VK_PAGE_UP: - case KeyEvent.DOM_VK_PAGE_DOWN: - cancel = this.mController.handleKeyNavigation(aEvent.keyCode); - break; - } - } - - // Handle keys we know aren't part of a shortcut, even with Alt or - // Ctrl. - switch (aEvent.keyCode) { - case KeyEvent.DOM_VK_ESCAPE: - cancel = this.mController.handleEscape(); - break; - case KeyEvent.DOM_VK_RETURN: - if (AppConstants.platform == "macosx") { - // Prevent the default action, since it will beep on Mac - if (aEvent.metaKey) - aEvent.preventDefault(); - } - this.mEnterEvent = aEvent; - if (this.mController.selection) { - this._selectionDetails = { - index: this.mController.selection.currentIndex, - kind: "key" - }; - } - cancel = this.handleEnter(); - break; - case KeyEvent.DOM_VK_DELETE: - if (AppConstants.platform == "macosx" && !aEvent.shiftKey) { - break; - } - cancel = this.handleDelete(); - break; - case KeyEvent.DOM_VK_BACK_SPACE: - if (AppConstants.platform == "macosx" && aEvent.shiftKey) { - cancel = this.handleDelete(); - } - break; - case KeyEvent.DOM_VK_DOWN: - case KeyEvent.DOM_VK_UP: - if (aEvent.altKey) - this.toggleHistoryPopup(); - break; - case KeyEvent.DOM_VK_F4: - if (AppConstants.platform != "macosx") { - this.toggleHistoryPopup(); - } - break; - } - - if (cancel) { - aEvent.stopPropagation(); - aEvent.preventDefault(); - } - - return true; - ]]></body> - </method> - - <method name="handleEnter"> - <body><![CDATA[ - return this.mController.handleEnter(false); - ]]></body> - </method> - - <method name="handleDelete"> - <body><![CDATA[ - return this.mController.handleDelete(); - ]]></body> - </method> - - <!-- ::::::::::::: miscellaneous ::::::::::::: --> - - <method name="initSearchNames"> - <body><![CDATA[ - if (!this.mSearchNames) { - var names = this.getAttribute("autocompletesearch"); - if (!names) - this.mSearchNames = []; - else - this.mSearchNames = names.split(" "); - } - ]]></body> - </method> - - <method name="_focus"> - <!-- doesn't reset this.mController --> - <body><![CDATA[ - this._dontBlur = true; - this.focus(); - this._dontBlur = false; - ]]></body> - </method> - - <method name="resetActionType"> - <body><![CDATA[ - if (this.mIgnoreInput) - return; - this.removeAttribute("actiontype"); - ]]></body> - </method> - - <field name="_valueIsPasted">false</field> - <field name="_pasteController"><![CDATA[ - ({ - _autocomplete: this, - _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard, - supportsCommand: aCommand => aCommand == "cmd_paste", - doCommand: function(aCommand) { - this._autocomplete._valueIsPasted = true; - this._autocomplete.editor.paste(this._kGlobalClipboard); - this._autocomplete._valueIsPasted = false; - }, - isCommandEnabled: function(aCommand) { - return this._autocomplete.editor.isSelectionEditable && - this._autocomplete.editor.canPaste(this._kGlobalClipboard); - }, - onEvent: function() {} - }) - ]]></field> - - <method name="onInput"> - <parameter name="aEvent"/> - <body><![CDATA[ - if (!this.mIgnoreInput && this.mController.input == this) { - this.valueIsTyped = true; - this.mController.handleText(); - } - this.resetActionType(); - ]]></body> - </method> - </implementation> - - <handlers> - <handler event="input"><![CDATA[ - this.onInput(event); - ]]></handler> - - <handler event="keypress" phase="capturing" - action="return this.onKeyPress(event);"/> - - <handler event="compositionstart" phase="capturing" - action="if (this.mController.input == this) this.mController.handleStartComposition();"/> - - <handler event="compositionend" phase="capturing" - action="if (this.mController.input == this) this.mController.handleEndComposition();"/> - - <handler event="focus" phase="capturing" - action="this.attachController();"/> - - <handler event="blur" phase="capturing"><![CDATA[ - if (!this._dontBlur) { - if (this.forceComplete && this.mController.matchCount >= 1) { - // mousemove sets selected index. Don't blindly use that selected - // index in this blur handler since if the popup is open you can - // easily "select" another match just by moving the mouse over it. - let filledVal = this.value.replace(/.+ >> /, "").toLowerCase(); - let selectedVal = null; - if (this.popup.selectedIndex >= 0) { - selectedVal = this.mController.getFinalCompleteValueAt( - this.popup.selectedIndex); - } - if (selectedVal && filledVal != selectedVal.toLowerCase()) { - for (let i = 0; i < this.mController.matchCount; i++) { - let matchVal = this.mController.getFinalCompleteValueAt(i); - if (matchVal.toLowerCase() == filledVal) { - this.popup.selectedIndex = i; - break; - } - } - } - this.mController.handleEnter(false); - } - if (!this.ignoreBlurWhileSearching) - this.detachController(); - } - ]]></handler> - </handlers> - </binding> - - <binding id="private-autocomplete-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup"> - <resources> - <stylesheet src="chrome://browser/content/autocomplete.css"/> - <stylesheet src="chrome://global/skin/tree.css"/> - <stylesheet src="chrome://browser/skin/autocomplete.css"/> - </resources> - - <content ignorekeys="true" level="top" consumeoutsideclicks="never"> - <xul:tree anonid="tree" class="private-autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single"> - <xul:treecols anonid="treecols"> - <xul:treecol id="treecolAutoCompleteValue" class="private-autocomplete-treecol" flex="1" overflow="true"/> - </xul:treecols> - <xul:treechildren class="private-autocomplete-treebody"/> - </xul:tree> - </content> - - <implementation> - <field name="mShowCommentColumn">false</field> - <field name="mShowImageColumn">false</field> - - <property name="showCommentColumn" - onget="return this.mShowCommentColumn;"> - <setter> - <![CDATA[ - if (!val && this.mShowCommentColumn) { - // reset the flex on the value column and remove the comment column - document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1); - this.removeColumn("treecolAutoCompleteComment"); - } else if (val && !this.mShowCommentColumn) { - // reset the flex on the value column and add the comment column - document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2); - this.addColumn({id: "treecolAutoCompleteComment", flex: 1}); - } - this.mShowCommentColumn = val; - return val; - ]]> - </setter> - </property> - - <property name="showImageColumn" - onget="return this.mShowImageColumn;"> - <setter> - <![CDATA[ - if (!val && this.mShowImageColumn) { - // remove the image column - this.removeColumn("treecolAutoCompleteImage"); - } else if (val && !this.mShowImageColumn) { - // add the image column - this.addColumn({id: "treecolAutoCompleteImage", flex: 1}); - } - this.mShowImageColumn = val; - return val; - ]]> - </setter> - </property> - - - <method name="addColumn"> - <parameter name="aAttrs"/> - <body> - <![CDATA[ - var col = document.createElement("treecol"); - col.setAttribute("class", "private-autocomplete-treecol"); - for (var name in aAttrs) - col.setAttribute(name, aAttrs[name]); - this.treecols.appendChild(col); - return col; - ]]> - </body> - </method> - - <method name="removeColumn"> - <parameter name="aColId"/> - <body> - <![CDATA[ - return this.treecols.removeChild(document.getElementById(aColId)); - ]]> - </body> - </method> - - <property name="selectedIndex" - onget="return this.tree.currentIndex;"> - <setter> - <![CDATA[ - this.tree.view.selection.select(val); - if (this.tree.treeBoxObject.height > 0) - this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val); - // Fire select event on xul:tree so that accessibility API - // support layer can fire appropriate accessibility events. - var event = document.createEvent('Events'); - event.initEvent("select", true, true); - this.tree.dispatchEvent(event); - return val; - ]]></setter> - </property> - - <method name="adjustHeight"> - <body> - <![CDATA[ - // detect the desired height of the tree - var bx = this.tree.treeBoxObject; - var view = this.tree.view; - if (!view) - return; - var rows = this.maxRows; - if (!view.rowCount || (rows && view.rowCount < rows)) - rows = view.rowCount; - - var height = rows * bx.rowHeight; - - if (height == 0) { - this.tree.setAttribute("collapsed", "true"); - } else { - if (this.tree.hasAttribute("collapsed")) - this.tree.removeAttribute("collapsed"); - - this.tree.setAttribute("height", height); - } - this.tree.setAttribute("hidescrollbar", view.rowCount <= rows); - ]]> - </body> - </method> - - <method name="openAutocompletePopup"> - <parameter name="aInput"/> - <parameter name="aElement"/> - <body><![CDATA[ - // until we have "baseBinding", (see bug #373652) this allows - // us to override openAutocompletePopup(), but still call - // the method on the base class - this._openAutocompletePopup(aInput, aElement); - ]]></body> - </method> - - <method name="_openAutocompletePopup"> - <parameter name="aInput"/> - <parameter name="aElement"/> - <body><![CDATA[ - if (!this.mPopupOpen) { - this.mInput = aInput; - this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView); - this.invalidate(); - - this.showCommentColumn = this.mInput.showCommentColumn; - this.showImageColumn = this.mInput.showImageColumn; - - var rect = aElement.getBoundingClientRect(); - var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsIWebNavigation); - var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell); - var docViewer = docShell.contentViewer; - var width = (rect.right - rect.left) * docViewer.fullZoom; - this.setAttribute("width", width > 100 ? width : 100); - - // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840 - var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction; - this.style.direction = popupDirection; - - this.openPopup(aElement, "after_start", 0, 0, false, false); - } - ]]></body> - </method> - - <method name="invalidate"> - <body><![CDATA[ - this.adjustHeight(); - this.tree.treeBoxObject.invalidate(); - ]]></body> - </method> - - <method name="selectBy"> - <parameter name="aReverse"/> - <parameter name="aPage"/> - <body><![CDATA[ - try { - var amount = aPage ? 5 : 1; - this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1); - if (this.selectedIndex == -1) { - this.input._focus(); - } - } catch (ex) { - // do nothing - occasionally timer-related js errors happen here - // e.g. "this.selectedIndex has no properties", when you type fast and hit a - // navigation key before this popup has opened - } - ]]></body> - </method> - - <!-- =================== PUBLIC MEMBERS =================== --> - - <field name="tree"> - document.getAnonymousElementByAttribute(this, "anonid", "tree"); - </field> - - <field name="treecols"> - document.getAnonymousElementByAttribute(this, "anonid", "treecols"); - </field> - - <property name="view" - onget="return this.mView;"> - <setter><![CDATA[ - // We must do this by hand because the tree binding may not be ready yet - this.mView = val; - this.tree.boxObject.view = val; - ]]></setter> - </property> - - </implementation> - </binding> - - <binding id="private-autocomplete-base-popup" role="none" -extends="chrome://global/content/bindings/popup.xml#popup"> - <implementation implements="nsIAutoCompletePopup"> - <field name="mInput">null</field> - <field name="mPopupOpen">false</field> - <field name="mIsPopupHidingTick">false</field> - - <!-- =================== nsIAutoCompletePopup =================== --> - - <property name="input" readonly="true" - onget="return this.mInput"/> - - <property name="overrideValue" readonly="true" - onget="return null;"/> - - <property name="popupOpen" readonly="true" - onget="return this.mPopupOpen;"/> - - <property name="isPopupHidingTick" readonly="true" - onget="return this.mIsPopupHidingTick;"/> - - <method name="closePopup"> - <body> - <![CDATA[ - if (this.mPopupOpen) { - this.hidePopup(); - this.removeAttribute("width"); - } - ]]> - </body> - </method> - - <!-- This is the default number of rows that we give the autocomplete - popup when the textbox doesn't have a "maxrows" attribute - for us to use. --> - <field name="defaultMaxRows" readonly="true">6</field> - - <!-- In some cases (e.g. when the input's dropmarker button is clicked), - the input wants to display a popup with more rows. In that case, it - should increase its maxRows property and store the "normal" maxRows - in this field. When the popup is hidden, we restore the input's - maxRows to the value stored in this field. - - This field is set to -1 between uses so that we can tell when it's - been set by the input and when we need to set it in the popupshowing - handler. --> - <field name="_normalMaxRows">-1</field> - - <property name="maxRows" readonly="true"> - <getter> - <![CDATA[ - return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows; - ]]> - </getter> - </property> - - <method name="getNextIndex"> - <parameter name="aReverse"/> - <parameter name="aAmount"/> - <parameter name="aIndex"/> - <parameter name="aMaxRow"/> - <body><![CDATA[ - if (aMaxRow < 0) - return -1; - - var newIdx = aIndex + (aReverse?-1:1)*aAmount; - if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow) - newIdx = aMaxRow; - else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0) - newIdx = 0; - - if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow) - aIndex = -1; - else - aIndex = newIdx; - - return aIndex; - ]]></body> - </method> - - <method name="onPopupClick"> - <parameter name="aEvent"/> - <body><![CDATA[ - var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController); - controller.handleEnter(true); - ]]></body> - </method> - </implementation> - - <handlers> - <handler event="popupshowing"><![CDATA[ - // If normalMaxRows wasn't already set by the input, then set it here - // so that we restore the correct number when the popup is hidden. - - // Null-check this.mInput; see bug 1017914 - if (this._normalMaxRows < 0 && this.mInput) { - this._normalMaxRows = this.mInput.maxRows; - } - - // Set an attribute for styling the popup based on the input. - let inputID = ""; - if (this.mInput && this.mInput.ownerDocument && - this.mInput.ownerDocument.documentURIObject.schemeIs("chrome")) { - inputID = this.mInput.id; - // Take care of elements with no id that are inside xbl bindings - if (!inputID) { - let bindingParent = this.mInput.ownerDocument.getBindingParent(this.mInput); - if (bindingParent) { - inputID = bindingParent.id; - } - } - } - this.setAttribute("autocompleteinput", inputID); - - this.mPopupOpen = true; - ]]></handler> - - <handler event="popuphiding"><![CDATA[ - var isListActive = true; - if (this.selectedIndex == -1) - isListActive = false; - var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController); - controller.stopSearch(); - - this.removeAttribute("autocompleteinput"); - this.mPopupOpen = false; - - // Prevent opening popup from historydropmarker mousedown handler - // on the same event tick the popup is hidden by the same mousedown - // event. - this.mIsPopupHidingTick = true; - setTimeout(() => { - this.mIsPopupHidingTick = false; - }, 0); - - // Reset the maxRows property to the cached "normal" value, and reset - // _normalMaxRows so that we can detect whether it was set by the input - // when the popupshowing handler runs. - - // Null-check this.mInput; see bug 1017914 - if (this.mInput) - this.mInput.maxRows = this._normalMaxRows; - this._normalMaxRows = -1; - // If the list was being navigated and then closed, make sure - // we fire accessible focus event back to textbox - - // Null-check this.mInput; see bug 1017914 - if (isListActive && this.mInput) { - this.mInput.mIgnoreFocus = true; - this.mInput._focus(); - this.mInput.mIgnoreFocus = false; - } - ]]></handler> - </handlers> - </binding> - - <binding id="private-autocomplete-rich-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup"> - <resources> - <stylesheet src="chrome://browser/content/autocomplete.css"/> - <stylesheet src="chrome://browser/skin/autocomplete.css"/> - </resources> - - <content ignorekeys="true" level="top" consumeoutsideclicks="never"> - <xul:richlistbox anonid="richlistbox" class="private-autocomplete-richlistbox" flex="1"/> - <xul:hbox> - <children/> - </xul:hbox> - </content> - - <implementation implements="nsIAutoCompletePopup"> - <field name="_currentIndex">0</field> - <field name="_rowHeight">0</field> - <field name="_rlbAnimated">false</field> - - <!-- =================== nsIAutoCompletePopup =================== --> - - <property name="selectedIndex" - onget="return this.richlistbox.selectedIndex;"> - <setter> - <![CDATA[ - this.richlistbox.selectedIndex = val; - - // when clearing the selection (val == -1, so selectedItem will be - // null), we want to scroll back to the top. see bug #406194 - this.richlistbox.ensureElementIsVisible( - this.richlistbox.selectedItem || this.richlistbox.firstChild); - - return val; - ]]> - </setter> - </property> - - <method name="onSearchBegin"> - <body><![CDATA[ - this.richlistbox.mouseSelectedIndex = -1; - ]]></body> - </method> - - <method name="openAutocompletePopup"> - <parameter name="aInput"/> - <parameter name="aElement"/> - <body> - <![CDATA[ - // until we have "baseBinding", (see bug #373652) this allows - // us to override openAutocompletePopup(), but still call - // the method on the base class - this._openAutocompletePopup(aInput, aElement); - ]]> - </body> - </method> - - <method name="_openAutocompletePopup"> - <parameter name="aInput"/> - <parameter name="aElement"/> - <body> - <![CDATA[ - if (!this.mPopupOpen) { - this.mInput = aInput; - // clear any previous selection, see bugs 400671 and 488357 - this.selectedIndex = -1; - - var width = aElement.getBoundingClientRect().width; - this.setAttribute("width", width > 100 ? width : 100); - // invalidate() depends on the width attribute - this._invalidate(); - - this.openPopup(aElement, "after_start", 0, 0, false, false); - } - ]]> - </body> - </method> - - <method name="invalidate"> - <parameter name="reason"/> - <body> - <![CDATA[ - // Don't bother doing work if we're not even showing - if (!this.mPopupOpen) - return; - - this._invalidate(reason); - ]]> - </body> - </method> - - <method name="_invalidate"> - <parameter name="reason"/> - <body> - <![CDATA[ - // collapsed if no matches - this.richlistbox.collapsed = (this._matchCount == 0); - - // Update the richlistbox height. - if (this._adjustHeightTimeout) { - clearTimeout(this._adjustHeightTimeout); - } - if (this._shrinkTimeout) { - clearTimeout(this._shrinkTimeout); - } - this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0); - - this._currentIndex = 0; - if (this._appendResultTimeout) { - clearTimeout(this._appendResultTimeout); - } - this._appendCurrentResult(reason); - ]]> - </body> - </method> - - <property name="maxResults" readonly="true"> - <getter> - <![CDATA[ - // this is how many richlistitems will be kept around - // (note, this getter may be overridden) - return 20; - ]]> - </getter> - </property> - - <property name="_matchCount" readonly="true"> - <getter> - <![CDATA[ - return Math.min(this.mInput.controller.matchCount, this.maxResults); - ]]> - </getter> - </property> - - <method name="_collapseUnusedItems"> - <body> - <![CDATA[ - let existingItemsCount = this.richlistbox.childNodes.length; - for (let i = this._matchCount; i < existingItemsCount; ++i) { - this.richlistbox.childNodes[i].collapsed = true; - } - ]]> - </body> - </method> - - <method name="adjustHeight"> - <body> - <![CDATA[ - // Figure out how many rows to show - let rows = this.richlistbox.childNodes; - let numRows = Math.min(this._matchCount, this.maxRows, rows.length); - - this.removeAttribute("height"); - - // Default the height to 0 if we have no rows to show - let height = 0; - if (numRows) { - if (!this._rowHeight) { - let firstRowRect = rows[0].getBoundingClientRect(); - this._rowHeight = firstRowRect.height; - - let transition = - window.getComputedStyle(this.richlistbox).transitionProperty; - this._rlbAnimated = transition && transition != "none"; - - // Set a fixed max-height to avoid flicker when growing the panel. - this.richlistbox.style.maxHeight = (this._rowHeight * this.maxRows) + "px"; - } - - // Calculate the height to have the first row to last row shown - height = this._rowHeight * numRows; - } - - let animate = this._rlbAnimated && - this.getAttribute("dontanimate") != "true"; - let currentHeight = this.richlistbox.getBoundingClientRect().height; - if (height > currentHeight) { - // Grow immediately. - if (animate) { - this.richlistbox.removeAttribute("height"); - this.richlistbox.style.height = height + "px"; - } else { - this.richlistbox.style.removeProperty("height"); - this.richlistbox.height = height; - } - } else { - // Delay shrinking to avoid flicker. - this._shrinkTimeout = setTimeout(() => { - this._collapseUnusedItems(); - if (animate) { - this.richlistbox.removeAttribute("height"); - this.richlistbox.style.height = height + "px"; - } else { - this.richlistbox.style.removeProperty("height"); - this.richlistbox.height = height; - } - }, this.mInput.shrinkDelay); - } - ]]> - </body> - </method> - - <method name="_appendCurrentResult"> - <parameter name="invalidateReason"/> - <body> - <![CDATA[ - var controller = this.mInput.controller; - var matchCount = this._matchCount; - var existingItemsCount = this.richlistbox.childNodes.length; - - // Process maxRows per chunk to improve performance and user experience - for (let i = 0; i < this.maxRows; i++) { - if (this._currentIndex >= matchCount) - break; - - var item; - - // trim the leading/trailing whitespace - var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, ""); - - let url = controller.getValueAt(this._currentIndex); - - if (this._currentIndex < existingItemsCount) { - // re-use the existing item - item = this.richlistbox.childNodes[this._currentIndex]; - - // Completely reuse the existing richlistitem for invalidation - // due to new results, but only when: the item is the same, *OR* - // we are about to replace the currently mouse-selected item, to - // avoid surprising the user. - let iface = Components.interfaces.nsIAutoCompletePopup; - if (item.getAttribute("text") == trimmedSearchString && - invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT && - (item.getAttribute("url") == url || - this.richlistbox.mouseSelectedIndex === this._currentIndex)) { - item.collapsed = false; - this._currentIndex++; - continue; - } - } - else { - // need to create a new item - item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem"); - } - - // set these attributes before we set the class - // so that we can use them from the constructor - let iconURI = controller.getImageAt(this._currentIndex); - item.setAttribute("image", iconURI); - item.setAttribute("url", url); - item.setAttribute("title", controller.getCommentAt(this._currentIndex)); - item.setAttribute("type", controller.getStyleAt(this._currentIndex)); - item.setAttribute("text", trimmedSearchString); - - if (this._currentIndex < existingItemsCount) { - // re-use the existing item - item._adjustAcItem(); - item.collapsed = false; - } - else { - // set the class at the end so we can use the attributes - // in the xbl constructor - item.className = "private-autocomplete-richlistitem"; - this.richlistbox.appendChild(item); - } - - this._currentIndex++; - } - - if (typeof this.onResultsAdded == "function") - this.onResultsAdded(); - - if (this._currentIndex < matchCount) { - // yield after each batch of items so that typing the url bar is - // responsive - this._appendResultTimeout = setTimeout(() => this._appendCurrentResult(), 0); - } - ]]> - </body> - </method> - - <method name="selectBy"> - <parameter name="aReverse"/> - <parameter name="aPage"/> - <body> - <![CDATA[ - try { - var amount = aPage ? 5 : 1; - - // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount - this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1); - if (this.selectedIndex == -1) { - this.input._focus(); - } - } catch (ex) { - // do nothing - occasionally timer-related js errors happen here - // e.g. "this.selectedIndex has no properties", when you type fast and hit a - // navigation key before this popup has opened - } - ]]> - </body> - </method> - - <field name="richlistbox"> - document.getAnonymousElementByAttribute(this, "anonid", "richlistbox"); - </field> - - <property name="view" - onget="return this.mInput.controller;" - onset="return val;"/> - - </implementation> - </binding> - - <binding id="private-autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> - <content> - <xul:hbox align="center" class="ac-title-box"> - <xul:image xbl:inherits="src=image" class="ac-site-icon"/> - <xul:hbox anonid="title-box" class="ac-title" flex="1" - onunderflow="_doUnderflow('_title');" - onoverflow="_doOverflow('_title');"> - <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/> - </xul:hbox> - <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected" - class="ac-ellipsis-after ac-comment"/> - <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true"> - <xul:image class="ac-result-type-tag"/> - <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/> - <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/> - </xul:hbox> - <xul:image anonid="type-image" class="ac-type-icon" xbl:inherits="selected"/> - </xul:hbox> - <xul:hbox align="center" class="ac-url-box"> - <xul:spacer class="ac-site-icon"/> - <xul:image class="ac-action-icon"/> - <xul:hbox anonid="url-box" class="ac-url" flex="1" - onunderflow="_doUnderflow('_url');" - onoverflow="_doOverflow('_url');"> - <xul:description anonid="url" class="ac-normal-text ac-url-text" - xbl:inherits="selected type"/> - <xul:description anonid="action" class="ac-normal-text ac-action-text" - xbl:inherits="selected type"/> - </xul:hbox> - <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected" - class="ac-ellipsis-after ac-url-text"/> - <xul:spacer class="ac-type-icon"/> - </xul:hbox> - </content> - <implementation implements="nsIDOMXULSelectControlItemElement"> - <constructor> - <![CDATA[ - let ellipsis = "\u2026"; - try { - ellipsis = Components.classes["@mozilla.org/preferences-service;1"]. - getService(Components.interfaces.nsIPrefBranch). - getComplexValue("intl.ellipsis", - Components.interfaces.nsIPrefLocalizedString).data; - } catch (ex) { - // Do nothing.. we already have a default - } - - this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis"); - this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis"); - - this._urlOverflowEllipsis.value = ellipsis; - this._titleOverflowEllipsis.value = ellipsis; - - this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image"); - - this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box"); - this._url = document.getAnonymousElementByAttribute(this, "anonid", "url"); - this._action = document.getAnonymousElementByAttribute(this, "anonid", "action"); - - this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box"); - this._title = document.getAnonymousElementByAttribute(this, "anonid", "title"); - - this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box"); - this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra"); - - this._adjustAcItem(); - ]]> - </constructor> - - <property name="label" readonly="true"> - <getter> - <![CDATA[ - // This property is a string that is read aloud by screen readers, - // so it must not contain anything that should not be user-facing. - - let parts = [ - this.getAttribute("title"), - this.getAttribute("displayurl"), - ]; - let label = parts.filter(str => str).join(" ") - - // allow consumers that have extended popups to override - // the label values for the richlistitems - let panel = this.parentNode.parentNode; - if (panel.createResultLabel) { - return panel.createResultLabel(this, label); - } - - return label; - ]]> - </getter> - </property> - - <property name="_stringBundle"> - <getter><![CDATA[ - if (!this.__stringBundle) { - this.__stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties"); - } - return this.__stringBundle; - ]]></getter> - </property> - - <field name="_boundaryCutoff">null</field> - - <property name="boundaryCutoff" readonly="true"> - <getter> - <![CDATA[ - if (!this._boundaryCutoff) { - this._boundaryCutoff = - Components.classes["@mozilla.org/preferences-service;1"]. - getService(Components.interfaces.nsIPrefBranch). - getIntPref("toolkit.autocomplete.richBoundaryCutoff"); - } - return this._boundaryCutoff; - ]]> - </getter> - </property> - - <method name="_getBoundaryIndices"> - <parameter name="aText"/> - <parameter name="aSearchTokens"/> - <body> - <![CDATA[ - // Short circuit for empty search ([""] == "") - if (aSearchTokens == "") - return [0, aText.length]; - - // Find which regions of text match the search terms - let regions = []; - for (let search of Array.prototype.slice.call(aSearchTokens)) { - let matchIndex = -1; - let searchLen = search.length; - - // Find all matches of the search terms, but stop early for perf - let lowerText = aText.substr(0, this.boundaryCutoff).toLowerCase(); - while ((matchIndex = lowerText.indexOf(search, matchIndex + 1)) >= 0) { - regions.push([matchIndex, matchIndex + searchLen]); - } - } - - // Sort the regions by start position then end position - regions = regions.sort((a, b) => { - let start = a[0] - b[0]; - return (start == 0) ? a[1] - b[1] : start; - }); - - // Generate the boundary indices from each region - let start = 0; - let end = 0; - let boundaries = []; - let len = regions.length; - for (let i = 0; i < len; i++) { - // We have a new boundary if the start of the next is past the end - let region = regions[i]; - if (region[0] > end) { - // First index is the beginning of match - boundaries.push(start); - // Second index is the beginning of non-match - boundaries.push(end); - - // Track the new region now that we've stored the previous one - start = region[0]; - } - - // Push back the end index for the current or new region - end = Math.max(end, region[1]); - } - - // Add the last region - boundaries.push(start); - boundaries.push(end); - - // Put on the end boundary if necessary - if (end < aText.length) - boundaries.push(aText.length); - - // Skip the first item because it's always 0 - return boundaries.slice(1); - ]]> - </body> - </method> - - <method name="_getSearchTokens"> - <parameter name="aSearch"/> - <body> - <![CDATA[ - let search = aSearch.toLowerCase(); - return search.split(/\s+/); - ]]> - </body> - </method> - - <method name="_setUpDescription"> - <parameter name="aDescriptionElement"/> - <parameter name="aText"/> - <parameter name="aNoEmphasis"/> - <body> - <![CDATA[ - // Get rid of all previous text - while (aDescriptionElement.hasChildNodes()) - aDescriptionElement.removeChild(aDescriptionElement.firstChild); - - // If aNoEmphasis is specified, don't add any emphasis - if (aNoEmphasis) { - aDescriptionElement.appendChild(document.createTextNode(aText)); - return; - } - - // Get the indices that separate match and non-match text - let search = this.getAttribute("text"); - let tokens = this._getSearchTokens(search); - let indices = this._getBoundaryIndices(aText, tokens); - - let next; - let start = 0; - let len = indices.length; - // Even indexed boundaries are matches, so skip the 0th if it's empty - for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) { - next = indices[i]; - let text = aText.substr(start, next - start); - start = next; - - if (i % 2 == 0) { - // Emphasize the text for even indices - let span = aDescriptionElement.appendChild( - document.createElementNS("http://www.w3.org/1999/xhtml", "span")); - span.className = "ac-emphasize-text"; - span.textContent = text; - } else { - // Otherwise, it's plain text - aDescriptionElement.appendChild(document.createTextNode(text)); - } - } - ]]> - </body> - </method> - - <!-- - This will generate an array of emphasis pairs for use with - _setUpEmphasisedSections(). Each pair is a tuple (array) that - represents a block of text - containing the text of that block, and a - boolean for whether that block should have an emphasis styling applied - to it. - - These pairs are generated by parsing a localised string (aSourceString) - with parameters, in the format that is used by - nsIStringBundle.formatStringFromName(): - - "textA %1$S textB textC %2$S" - - Or: - - "textA %S" - - Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided - replacement strings. These are specified an array of tuples - (aReplacements), each containing the replacement text and a boolean for - whether that text should have an emphasis styling applied. This is used - as a 1-based array - ie, "%1$S" is replaced by the item in the first - index of aReplacements, "%2$S" by the second, etc. "%S" will always - match the first index. - --> - <method name="_generateEmphasisPairs"> - <parameter name="aSourceString"/> - <parameter name="aReplacements"/> - <body> - <![CDATA[ - let pairs = []; - - // Split on %S, %1$S, %2$S, etc. ie: - // "textA %S" - // becomes ["textA ", "%S"] - // "textA %1$S textB textC %2$S" - // becomes ["textA ", "%1$S", " textB textC ", "%2$S"] - let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/); - - for (let part of parts) { - // The above regex will actually give us an empty string at the - // end - we don't want that, as we don't want to later generate an - // empty text node for it. - if (part.length === 0) - continue; - - // Determine if this token is a replacement token or a normal text - // token. If it is a replacement token, we want to extract the - // numerical number. However, we still want to match on "$S". - let match = part.match(/^%(?:([0-9]+)\$)?S$/); - - if (match) { - // "%S" doesn't have a numerical number in it, but will always - // be assumed to be 1. Furthermore, the input string specifies - // these with a 1-based index, but we want a 0-based index. - let index = (match[1] || 1) - 1; - - if (index >= 0 && index < aReplacements.length) { - pairs.push([...aReplacements[index]]); - } - } else { - pairs.push([part]); - } - } - - return pairs; - ]]> - </body> - </method> - - <!-- - _setUpEmphasisedSections() has the same use as _setUpDescription, - except instead of taking a string and highlighting given tokens, it takes - an array of pairs generated by _generateEmphasisPairs(). This allows - control over emphasising based on specific blocks of text, rather than - search for substrings. - --> - <method name="_setUpEmphasisedSections"> - <parameter name="aDescriptionElement"/> - <parameter name="aTextPairs"/> - <body> - <![CDATA[ - // Get rid of all previous text - while (aDescriptionElement.hasChildNodes()) - aDescriptionElement.firstChild.remove(); - - for (let [text, emphasise] of aTextPairs) { - if (emphasise) { - let span = aDescriptionElement.appendChild( - document.createElementNS("http://www.w3.org/1999/xhtml", "span")); - span.textContent = text; - switch(emphasise) { - case "match": - span.className = "ac-emphasize-text"; - break; - case "selected": - span.className = "ac-selected-text"; - break; - } - } else { - aDescriptionElement.appendChild(document.createTextNode(text)); - } - } - ]]> - </body> - </method> - - <field name="_textToSubURI">null</field> - <method name="_unescapeUrl"> - <parameter name="url"/> - <body> - <![CDATA[ - if (!this._textToSubURI) { - this._textToSubURI = - Components.classes["@mozilla.org/intl/texttosuburi;1"] - .getService(Components.interfaces.nsITextToSubURI); - } - return this._textToSubURI.unEscapeURIForUI("UTF-8", url); - ]]> - </body> - </method> - - <method name="_adjustAcItem"> - <body> - <![CDATA[ - let originalUrl = this.getAttribute("url"); - let title = this.getAttribute("title"); - let type = this.getAttribute("type"); - - let displayUrl; - let emphasiseTitle = true; - let emphasiseUrl = true; - - // Hide the title's extra box by default, until we find out later if - // we need extra stuff. - this._extraBox.hidden = true; - this._titleBox.flex = 1; - this._typeImage.hidden = false; - - this.removeAttribute("actiontype"); - this.classList.remove("overridable-action"); - - // The ellipses are hidden via their visibility so that they always - // take up space and don't pop in on top of text when shown. For - // keyword searches, however, the title ellipsis should not take up - // space when hidden. Setting the hidden property accomplishes that. - this._titleOverflowEllipsis.hidden = false; - - let types = new Set(type.split(/\s+/)); - - // Remove types that should ultimately not be in the `type` string. - let initialTypes = new Set(types); - types.delete("action"); - types.delete("autofill"); - types.delete("heuristic"); - types.delete("search"); - - type = [...types][0] || ""; - - // If the type includes an action, set up the item appropriately. - if (initialTypes.has("action")) { - let action = this._parseActionUrl(originalUrl); - this.setAttribute("actiontype", action.type); - - if (action.type == "switchtab") { - this.classList.add("overridable-action"); - displayUrl = this._unescapeUrl(action.params.url); - let desc = this._stringBundle.GetStringFromName("switchToTab"); - this._setUpDescription(this._action, desc, true); - } else if (action.type == "remotetab") { - displayUrl = this._unescapeUrl(action.params.url); - let desc = action.params.deviceName; - this._setUpDescription(this._action, desc, true); - } else if (action.type == "searchengine") { - emphasiseUrl = false; - - // The order here is not localizable, we default to appending - // "- Search with Engine" to the search string, to be able to - // properly generate emphasis pairs. That said, no localization - // changed the order while it was possible, so doesn't look like - // there's a strong need for that. - let {engineName, searchSuggestion, searchQuery} = action.params; - let engineStr = " - " + - this._stringBundle.formatStringFromName("searchWithEngine", - [engineName], 1); - - // Make the title by generating an array of pairs and its - // corresponding interpolation string (e.g., "%1$S") to pass to - // _generateEmphasisPairs. - let pairs; - if (searchSuggestion) { - // Check if the search query appears in the suggestion. It may - // not. If it does, then emphasize the query in the suggestion - // and otherwise just include the suggestion without emphasis. - let idx = searchSuggestion.indexOf(searchQuery); - if (idx >= 0) { - pairs = [ - [searchSuggestion.substring(0, idx), ""], - [searchQuery, "match"], - [searchSuggestion.substring(idx + searchQuery.length), ""], - ]; - } else { - pairs = [ - [searchSuggestion, ""], - ]; - } - } else { - pairs = [ - [searchQuery, ""], - ]; - } - pairs.push([engineStr, "selected"]); - let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join(""); - title = this._generateEmphasisPairs(interpStr, pairs); - - // If this is a default search match, we remove the image so we - // can style it ourselves with a generic search icon. - // We don't do this when matching an aliased search engine, - // because the icon helps with recognising which engine will be - // used (when using the default engine, we don't need that - // recognition). - if (!action.params.alias) { - this.removeAttribute("image"); - } - } else if (action.type == "visiturl") { - emphasiseUrl = false; - displayUrl = this._unescapeUrl(action.params.url); - let sourceStr = this._stringBundle.GetStringFromName("visitURL"); - title = this._generateEmphasisPairs(sourceStr, [ - [displayUrl, "match"], - ]); - } - } - - // Check if we have a search engine name - if (initialTypes.has("search")) { - emphasiseUrl = false; - - const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 "; - - let searchEngine = ""; - [title, searchEngine] = title.split(TITLE_SEARCH_ENGINE_SEPARATOR); - displayUrl = this._stringBundle.formatStringFromName("searchWithEngine", [searchEngine], 1); - } - - if (!displayUrl) { - let input = this.parentNode.parentNode.input; - let url = typeof(input.trimValue) == "function" ? - input.trimValue(originalUrl) : - originalUrl; - displayUrl = this._unescapeUrl(url); - } - this.setAttribute("displayurl", displayUrl); - - // Check if we have an auto-fill URL - if (initialTypes.has("autofill")) { - emphasiseUrl = false; - - let sourceStr = this._stringBundle.GetStringFromName("visitURL"); - title = this._generateEmphasisPairs(sourceStr, [ - [displayUrl, "match"], - ]); - } - - // If we have a tag match, show the tags and icon - if (type == "tag" || type == "bookmark-tag") { - // Configure the extra box for tags display - this._extraBox.hidden = false; - this._extraBox.childNodes[0].hidden = false; - this._extraBox.childNodes[1].hidden = true; - this._extraBox.pack = "end"; - this._titleBox.flex = 1; - - // The title is separated from the tags by an endash - let tags; - [, title, tags] = title.match(/^(.+) \u2013 (.+)$/); - - // Each tag is split by a comma in an undefined order, so sort it - let sortedTags = tags.split(",").sort().join(", "); - - // Emphasize the matching text in the tags - this._setUpDescription(this._extra, sortedTags); - - // If we're suggesting bookmarks, then treat tagged matches as - // bookmarks for the star. - if (type == "bookmark-tag") { - type = "bookmark"; - } else { - this._typeImage.hidden = true; - } - // keyword and favicon type results for search engines - // have an extra magnifying glass icon after them - } else if (type == "keyword" || (initialTypes.has("search") && - initialTypes.has("favicon"))) { - // Configure the extra box for keyword display - this._extraBox.hidden = false; - this._extraBox.childNodes[0].hidden = true; - // The second child node is ":" and it should be hidden for non keyword types - this._extraBox.childNodes[1].hidden = type == "keyword" ? false : true; - this._extraBox.pack = "start"; - this._titleBox.flex = 0; - - // Hide the ellipsis so it doesn't take up space. - this._titleOverflowEllipsis.hidden = true; - - if (type == "keyword") { - // Put the parameters next to the title if we have any - let search = this.getAttribute("text"); - let params = ""; - let paramsIndex = search.indexOf(" "); - if (paramsIndex != -1) - params = search.substr(paramsIndex + 1); - - // Emphasize the keyword parameters - this._setUpDescription(this._extra, params); - - // Don't emphasize keyword searches in the title or url - emphasiseUrl = false; - emphasiseTitle = false; - } else { - // Don't show any description for non keyword types. - this._setUpDescription(this._extra, "", true); - } - // If the result has the type favicon and a known search provider, - // customize it the same way as a keyword result. - type = "keyword"; - } - - // Give the image the icon style and a special one for the type - this._typeImage.className = "ac-type-icon" + - (type ? " ac-result-type-" + type : ""); - - // Show the domain as the title if we don't have a title. - if (title == "") { - title = displayUrl; - try { - let uri = Services.io.newURI(originalUrl, null, null); - // Not all valid URLs have a domain. - if (uri.host) - title = uri.host; - } catch (e) {} - } - - // Emphasize the matching search terms for the description - if (Array.isArray(title)) - this._setUpEmphasisedSections(this._title, title); - else - this._setUpDescription(this._title, title, !emphasiseTitle); - - this._setUpDescription(this._url, displayUrl, !emphasiseUrl); - - // Set up overflow on a timeout because the contents of the box - // might not have a width yet even though we just changed them - setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis); - setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis); - ]]> - </body> - </method> - - <method name="_parseActionUrl"> - <parameter name="aUrl"/> - <body><![CDATA[ - if (!aUrl.startsWith("moz-action:")) - return null; - - // URL is in the format moz-action:ACTION,PARAMS - // Where PARAMS is a JSON encoded object. - let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/); - - let action = { - type: type, - }; - - try { - action.params = JSON.parse(params); - for (let key in action.params) { - action.params[key] = decodeURIComponent(action.params[key]); - } - } catch (e) { - // If this failed, we assume that params is not a JSON object, and - // is instead just a flat string. This will happen when - // UnifiedComplete is disabled - in which case, the param is always - // a URL. - action.params = { - url: params, - } - } - - return action; - ]]></body> - </method> - - <method name="_setUpOverflow"> - <parameter name="aParentBox"/> - <parameter name="aEllipsis"/> - <body> - <![CDATA[ - // Hide the ellipsis incase there's just enough to not underflow - aEllipsis.style.visibility = "hidden"; - - // Start with the parent's width and subtract off its children - let tooltip = []; - let children = aParentBox.childNodes; - let widthDiff = aParentBox.boxObject.width; - - for (let i = 0; i < children.length; i++) { - // Only consider a child if it actually takes up space - let childWidth = children[i].boxObject.width; - if (childWidth > 0) { - // Subtract a little less to account for subpixel rounding - widthDiff -= childWidth - .5; - - // Add to the tooltip if it's not hidden and has text - let childText = children[i].textContent; - if (childText) - tooltip.push(childText); - } - } - - // If the children take up more space than the parent.. overflow! - if (widthDiff < 0) { - // Re-show the ellipsis now that we know it's needed - aEllipsis.style.visibility = "visible"; - - // Separate text components with a ndash -- - aParentBox.tooltipText = tooltip.join(" \u2013 "); - } - ]]> - </body> - </method> - - <method name="_doUnderflow"> - <parameter name="aName"/> - <body> - <![CDATA[ - // Hide the ellipsis right when we know we're underflowing instead of - // waiting for the timeout to trigger the _setUpOverflow calculations - this[aName + "Box"].tooltipText = ""; - this[aName + "OverflowEllipsis"].style.visibility = "hidden"; - ]]> - </body> - </method> - - <method name="_doOverflow"> - <parameter name="aName"/> - <body> - <![CDATA[ - this._setUpOverflow(this[aName + "Box"], - this[aName + "OverflowEllipsis"]); - ]]> - </body> - </method> - - </implementation> - </binding> - - <binding id="private-autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree"> - <content> - <children includes="treecols"/> - <xul:treerows class="private-autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1"> - <children/> - </xul:treerows> - </content> - </binding> - - <binding id="private-autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox"> - <implementation> - <field name="mLastMoveTime">Date.now()</field> - <field name="mouseSelectedIndex">-1</field> - </implementation> - <handlers> - <handler event="mouseup"> - <![CDATA[ - // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc. - let item = event.originalTarget; - while (item && item.localName != "richlistitem") { - item = item.parentNode; - } - - if (!item) - return; - - this.parentNode.onPopupClick(event); - ]]> - </handler> - - <handler event="mousemove"> - <![CDATA[ - if (Date.now() - this.mLastMoveTime > 30) { - let item = event.target; - while (item && item.localName != "richlistitem") { - item = item.parentNode; - } - - if (!item) - return; - - let index = this.getIndexOfItem(item); - if (index != this.selectedIndex) { - this.mouseSelectedIndex = this.selectedIndex = index; - } - - this.mLastMoveTime = Date.now(); - } - ]]> - </handler> - </handlers> - </binding> - - <binding id="private-autocomplete-treebody"> - <implementation> - <field name="mLastMoveTime">Date.now()</field> - </implementation> - - <handlers> - <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/> - - <handler event="mousedown"><![CDATA[ - var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); - if (rc != this.parentNode.currentIndex) - this.parentNode.view.selection.select(rc); - ]]></handler> - - <handler event="mousemove"><![CDATA[ - if (Date.now() - this.mLastMoveTime > 30) { - var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); - if (rc != this.parentNode.currentIndex) - this.parentNode.view.selection.select(rc); - this.mLastMoveTime = Date.now(); - } - ]]></handler> - </handlers> - </binding> - - <binding id="private-autocomplete-treerows"> - <content> - <xul:hbox flex="1" class="tree-bodybox"> - <children/> - </xul:hbox> - <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/> - </content> - </binding> - - <binding id="private-history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker"> - <handlers> - <handler event="mousedown" button="0"><![CDATA[ - document.getBindingParent(this).toggleHistoryPopup(); - ]]></handler> - </handlers> - </binding> - -</bindings> |