1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/content/widgets/autocomplete.xml Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1675 @@ 1.4 +<?xml version="1.0"?> 1.5 + 1.6 +# -*- Mode: HTML -*- 1.7 +# This Source Code Form is subject to the terms of the Mozilla Public 1.8 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.9 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.10 + 1.11 +<!DOCTYPE bindings [ 1.12 +<!ENTITY % actionsDTD SYSTEM "chrome://global/locale/actions.dtd"> 1.13 +%actionsDTD; 1.14 +]> 1.15 + 1.16 +<bindings id="autocompleteBindings" 1.17 + xmlns="http://www.mozilla.org/xbl" 1.18 + xmlns:html="http://www.w3.org/1999/xhtml" 1.19 + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" 1.20 + xmlns:xbl="http://www.mozilla.org/xbl"> 1.21 + 1.22 + <binding id="autocomplete" role="xul:combobox" 1.23 + extends="chrome://global/content/bindings/textbox.xml#textbox"> 1.24 + <resources> 1.25 + <stylesheet src="chrome://global/skin/autocomplete.css"/> 1.26 + </resources> 1.27 + 1.28 + <content sizetopopup="pref"> 1.29 + <xul:hbox class="autocomplete-textbox-container" flex="1" xbl:inherits="focused"> 1.30 + <children includes="image|deck|stack|box"> 1.31 + <xul:image class="autocomplete-icon" allowevents="true"/> 1.32 + </children> 1.33 + 1.34 + <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext"> 1.35 + <children/> 1.36 + <html:input anonid="input" class="autocomplete-textbox textbox-input" 1.37 + allowevents="true" 1.38 + xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/> 1.39 + </xul:hbox> 1.40 + <children includes="hbox"/> 1.41 + </xul:hbox> 1.42 + 1.43 + <xul:dropmarker anonid="historydropmarker" class="autocomplete-history-dropmarker" 1.44 + allowevents="true" 1.45 + xbl:inherits="open,enablehistory,parentfocused=focused"/> 1.46 + 1.47 + <xul:popupset anonid="popupset" class="autocomplete-result-popupset"/> 1.48 + 1.49 + <children includes="toolbarbutton"/> 1.50 + </content> 1.51 + 1.52 + <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement"> 1.53 + <field name="mController">null</field> 1.54 + <field name="mSearchNames">null</field> 1.55 + <field name="mIgnoreInput">false</field> 1.56 + <field name="mEnterEvent">null</field> 1.57 + 1.58 + <field name="_searchBeginHandler">null</field> 1.59 + <field name="_searchCompleteHandler">null</field> 1.60 + <field name="_textEnteredHandler">null</field> 1.61 + <field name="_textRevertedHandler">null</field> 1.62 + 1.63 + <constructor><![CDATA[ 1.64 + this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"]. 1.65 + getService(Components.interfaces.nsIAutoCompleteController); 1.66 + 1.67 + this._searchBeginHandler = this.initEventHandler("searchbegin"); 1.68 + this._searchCompleteHandler = this.initEventHandler("searchcomplete"); 1.69 + this._textEnteredHandler = this.initEventHandler("textentered"); 1.70 + this._textRevertedHandler = this.initEventHandler("textreverted"); 1.71 + 1.72 + // For security reasons delay searches on pasted values. 1.73 + this.inputField.controllers.insertControllerAt(0, this._pasteController); 1.74 + ]]></constructor> 1.75 + 1.76 + <destructor><![CDATA[ 1.77 + this.inputField.controllers.removeController(this._pasteController); 1.78 + ]]></destructor> 1.79 + 1.80 + <!-- =================== nsIAutoCompleteInput =================== --> 1.81 + 1.82 + <field name="popup"><![CDATA[ 1.83 + // Wrap in a block so that the let statements don't 1.84 + // create properties on 'this' (bug 635252). 1.85 + { 1.86 + let popup = null; 1.87 + let popupId = this.getAttribute("autocompletepopup"); 1.88 + if (popupId) 1.89 + popup = document.getElementById(popupId); 1.90 + if (!popup) { 1.91 + popup = document.createElement("panel"); 1.92 + popup.setAttribute("type", "autocomplete"); 1.93 + popup.setAttribute("noautofocus", "true"); 1.94 + 1.95 + let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset"); 1.96 + popupset.appendChild(popup); 1.97 + } 1.98 + popup.mInput = this; 1.99 + popup; 1.100 + } 1.101 + ]]></field> 1.102 + 1.103 + <property name="controller" onget="return this.mController;" readonly="true"/> 1.104 + 1.105 + <property name="popupOpen" 1.106 + onget="return this.popup.popupOpen;" 1.107 + onset="if (val) this.openPopup(); else this.closePopup();"/> 1.108 + 1.109 + <property name="disableAutoComplete" 1.110 + onset="this.setAttribute('disableautocomplete', val); return val;" 1.111 + onget="return this.getAttribute('disableautocomplete') == 'true';"/> 1.112 + 1.113 + <property name="completeDefaultIndex" 1.114 + onset="this.setAttribute('completedefaultindex', val); return val;" 1.115 + onget="return this.getAttribute('completedefaultindex') == 'true';"/> 1.116 + 1.117 + <property name="completeSelectedIndex" 1.118 + onset="this.setAttribute('completeselectedindex', val); return val;" 1.119 + onget="return this.getAttribute('completeselectedindex') == 'true';"/> 1.120 + 1.121 + <property name="forceComplete" 1.122 + onset="this.setAttribute('forcecomplete', val); return val;" 1.123 + onget="return this.getAttribute('forcecomplete') == 'true';"/> 1.124 + 1.125 + <property name="minResultsForPopup" 1.126 + onset="this.setAttribute('minresultsforpopup', val); return val;" 1.127 + onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/> 1.128 + 1.129 + <property name="showCommentColumn" 1.130 + onset="this.setAttribute('showcommentcolumn', val); return val;" 1.131 + onget="return this.getAttribute('showcommentcolumn') == 'true';"/> 1.132 + 1.133 + <property name="showImageColumn" 1.134 + onset="this.setAttribute('showimagecolumn', val); return val;" 1.135 + onget="return this.getAttribute('showimagecolumn') == 'true';"/> 1.136 + 1.137 + <property name="timeout" 1.138 + onset="this.setAttribute('timeout', val); return val;"> 1.139 + <getter><![CDATA[ 1.140 + // For security reasons delay searches on pasted values. 1.141 + if (this._valueIsPasted) { 1.142 + let t = parseInt(this.getAttribute('pastetimeout')); 1.143 + return isNaN(t) ? 1000 : t; 1.144 + } 1.145 + 1.146 + let t = parseInt(this.getAttribute('timeout')); 1.147 + return isNaN(t) ? 50 : t; 1.148 + ]]></getter> 1.149 + </property> 1.150 + 1.151 + <property name="searchParam" 1.152 + onget="return this.getAttribute('autocompletesearchparam') || '';" 1.153 + onset="this.setAttribute('autocompletesearchparam', val); return val;"/> 1.154 + 1.155 + <property name="searchCount" readonly="true" 1.156 + onget="this.initSearchNames(); return this.mSearchNames.length;"/> 1.157 + 1.158 + <field name="PrivateBrowsingUtils" readonly="true"> 1.159 + let utils = {}; 1.160 + Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils); 1.161 + utils.PrivateBrowsingUtils 1.162 + </field> 1.163 + 1.164 + <property name="inPrivateContext" readonly="true" 1.165 + onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/> 1.166 + 1.167 + <!-- This is the maximum number of drop-down rows we get when we 1.168 + hit the drop marker beside fields that have it (like the URLbar).--> 1.169 + <field name="maxDropMarkerRows" readonly="true">14</field> 1.170 + 1.171 + <method name="getSearchAt"> 1.172 + <parameter name="aIndex"/> 1.173 + <body><![CDATA[ 1.174 + this.initSearchNames(); 1.175 + return this.mSearchNames[aIndex]; 1.176 + ]]></body> 1.177 + </method> 1.178 + 1.179 + <property name="textValue" 1.180 + onget="return this.value;"> 1.181 + <setter><![CDATA[ 1.182 + // Completing a result should simulate the user typing the result, 1.183 + // so fire an input event. 1.184 + // Trim popup selected values, but never trim results coming from 1.185 + // autofill. 1.186 + if (this.popup.selectedIndex == -1) 1.187 + this._disableTrim = true; 1.188 + this.value = val; 1.189 + this._disableTrim = false; 1.190 + 1.191 + var evt = document.createEvent("UIEvents"); 1.192 + evt.initUIEvent("input", true, false, window, 0); 1.193 + this.mIgnoreInput = true; 1.194 + this.dispatchEvent(evt); 1.195 + this.mIgnoreInput = false; 1.196 + return this.value; 1.197 + ]]></setter> 1.198 + </property> 1.199 + 1.200 + <method name="selectTextRange"> 1.201 + <parameter name="aStartIndex"/> 1.202 + <parameter name="aEndIndex"/> 1.203 + <body><![CDATA[ 1.204 + this.inputField.setSelectionRange(aStartIndex, aEndIndex); 1.205 + ]]></body> 1.206 + </method> 1.207 + 1.208 + <method name="onSearchBegin"> 1.209 + <body><![CDATA[ 1.210 + if (this._searchBeginHandler) 1.211 + this._searchBeginHandler(); 1.212 + ]]></body> 1.213 + </method> 1.214 + 1.215 + <method name="onSearchComplete"> 1.216 + <body><![CDATA[ 1.217 + if (this.mController.matchCount == 0) 1.218 + this.setAttribute("nomatch", "true"); 1.219 + else 1.220 + this.removeAttribute("nomatch"); 1.221 + 1.222 + if (this._searchCompleteHandler) 1.223 + this._searchCompleteHandler(); 1.224 + ]]></body> 1.225 + </method> 1.226 + 1.227 + <method name="onTextEntered"> 1.228 + <body><![CDATA[ 1.229 + let rv = false; 1.230 + if (this._textEnteredHandler) 1.231 + rv = this._textEnteredHandler(this.mEnterEvent); 1.232 + this.mEnterEvent = null; 1.233 + return rv; 1.234 + ]]></body> 1.235 + </method> 1.236 + 1.237 + <method name="onTextReverted"> 1.238 + <body><![CDATA[ 1.239 + if (this._textRevertedHandler) 1.240 + return this._textRevertedHandler(); 1.241 + return false; 1.242 + ]]></body> 1.243 + </method> 1.244 + 1.245 + <!-- =================== nsIDOMXULMenuListElement =================== --> 1.246 + 1.247 + <property name="editable" readonly="true" 1.248 + onget="return true;" /> 1.249 + 1.250 + <property name="crop" 1.251 + onset="this.setAttribute('crop',val); return val;" 1.252 + onget="return this.getAttribute('crop');"/> 1.253 + 1.254 + <property name="open" 1.255 + onget="return this.getAttribute('open') == 'true';"> 1.256 + <setter><![CDATA[ 1.257 + if (val) 1.258 + this.showHistoryPopup(); 1.259 + else 1.260 + this.closePopup(); 1.261 + ]]></setter> 1.262 + </property> 1.263 + 1.264 + <!-- =================== PUBLIC MEMBERS =================== --> 1.265 + 1.266 + <field name="valueIsTyped">false</field> 1.267 + <field name="_disableTrim">false</field> 1.268 + <property name="value"> 1.269 + <getter><![CDATA[ 1.270 + if (typeof this.onBeforeValueGet == "function") { 1.271 + var result = this.onBeforeValueGet(); 1.272 + if (result) 1.273 + return result.value; 1.274 + } 1.275 + return this.inputField.value; 1.276 + ]]></getter> 1.277 + <setter><![CDATA[ 1.278 + this.mIgnoreInput = true; 1.279 + 1.280 + if (typeof this.onBeforeValueSet == "function") 1.281 + val = this.onBeforeValueSet(val); 1.282 + 1.283 + if (typeof this.trimValue == "function" && !this._disableTrim) 1.284 + val = this.trimValue(val); 1.285 + 1.286 + this.valueIsTyped = false; 1.287 + this.inputField.value = val; 1.288 + 1.289 + if (typeof this.formatValue == "function") 1.290 + this.formatValue(); 1.291 + 1.292 + this.mIgnoreInput = false; 1.293 + var event = document.createEvent('Events'); 1.294 + event.initEvent('ValueChange', true, true); 1.295 + this.inputField.dispatchEvent(event); 1.296 + return val; 1.297 + ]]></setter> 1.298 + </property> 1.299 + 1.300 + <property name="focused" readonly="true" 1.301 + onget="return this.getAttribute('focused') == 'true';"/> 1.302 + 1.303 + <!-- maximum number of rows to display at a time --> 1.304 + <property name="maxRows" 1.305 + onset="this.setAttribute('maxrows', val); return val;" 1.306 + onget="return parseInt(this.getAttribute('maxrows')) || 0;"/> 1.307 + 1.308 + <!-- option to allow scrolling through the list via the tab key, rather than 1.309 + tab moving focus out of the textbox --> 1.310 + <property name="tabScrolling" 1.311 + onset="return this.setAttribute('tabscrolling', val); return val;" 1.312 + onget="return this.getAttribute('tabscrolling') == 'true';"/> 1.313 + 1.314 + <!-- disable key navigation handling in the popup results --> 1.315 + <property name="disableKeyNavigation" 1.316 + onset="this.setAttribute('disablekeynavigation', val); return val;" 1.317 + onget="return this.getAttribute('disablekeynavigation') == 'true';"/> 1.318 + 1.319 + <!-- option to completely ignore any blur events while 1.320 + searches are still going on. This is useful so that nothing 1.321 + gets autopicked if the window is required to lose focus for 1.322 + some reason (eg in LDAP autocomplete, another window may be 1.323 + brought up so that the user can enter a password to authenticate 1.324 + to an LDAP server). --> 1.325 + <property name="ignoreBlurWhileSearching" 1.326 + onset="this.setAttribute('ignoreblurwhilesearching', val); return val;" 1.327 + onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/> 1.328 + 1.329 + <!-- option to highlight entries that don't have any matches --> 1.330 + <property name="highlightNonMatches" 1.331 + onset="this.setAttribute('highlightnonmatches', val); return val;" 1.332 + onget="return this.getAttribute('highlightnonmatches') == 'true';"/> 1.333 + 1.334 + <!-- =================== PRIVATE MEMBERS =================== --> 1.335 + 1.336 + <!-- ::::::::::::: autocomplete controller ::::::::::::: --> 1.337 + 1.338 + <method name="attachController"> 1.339 + <body><![CDATA[ 1.340 + this.mController.input = this; 1.341 + ]]></body> 1.342 + </method> 1.343 + 1.344 + <method name="detachController"> 1.345 + <body><![CDATA[ 1.346 + try { 1.347 + if (this.mController.input == this) 1.348 + this.mController.input = null; 1.349 + } catch (ex) { 1.350 + // nothing really to do. 1.351 + } 1.352 + ]]></body> 1.353 + </method> 1.354 + 1.355 + <!-- ::::::::::::: popup opening ::::::::::::: --> 1.356 + 1.357 + <method name="openPopup"> 1.358 + <body><![CDATA[ 1.359 + this.popup.openAutocompletePopup(this, this); 1.360 + ]]></body> 1.361 + </method> 1.362 + 1.363 + <method name="closePopup"> 1.364 + <body><![CDATA[ 1.365 + this.popup.setAttribute("consumeoutsideclicks", "false"); 1.366 + this.popup.closePopup(); 1.367 + ]]></body> 1.368 + </method> 1.369 + 1.370 + <method name="showHistoryPopup"> 1.371 + <body><![CDATA[ 1.372 + // history dropmarker pushed state 1.373 + function cleanup(popup) { 1.374 + popup.removeEventListener("popupshowing", onShow, false); 1.375 + } 1.376 + function onShow(event) { 1.377 + var popup = event.target, input = popup.input; 1.378 + cleanup(popup); 1.379 + input.setAttribute("open", "true"); 1.380 + function onHide() { 1.381 + input.removeAttribute("open"); 1.382 + popup.setAttribute("consumeoutsideclicks", "false"); 1.383 + popup.removeEventListener("popuphiding", onHide, false); 1.384 + } 1.385 + popup.addEventListener("popuphiding", onHide, false); 1.386 + } 1.387 + this.popup.addEventListener("popupshowing", onShow, false); 1.388 + setTimeout(cleanup, 1000, this.popup); 1.389 + 1.390 + // Store our "normal" maxRows on the popup, so that it can reset the 1.391 + // value when the popup is hidden. 1.392 + this.popup._normalMaxRows = this.maxRows; 1.393 + 1.394 + // Increase our maxRows temporarily, since we want the dropdown to 1.395 + // be bigger in this case. The popup's popupshowing/popuphiding 1.396 + // handlers will take care of resetting this. 1.397 + this.maxRows = this.maxDropMarkerRows; 1.398 + 1.399 + // Ensure that we have focus. 1.400 + if (!this.focused) 1.401 + this.focus(); 1.402 + this.popup.setAttribute("consumeoutsideclicks", "true"); 1.403 + this.attachController(); 1.404 + this.mController.startSearch(""); 1.405 + ]]></body> 1.406 + </method> 1.407 + 1.408 + <method name="toggleHistoryPopup"> 1.409 + <body><![CDATA[ 1.410 + if (!this.popup.popupOpen) 1.411 + this.showHistoryPopup(); 1.412 + else 1.413 + this.closePopup(); 1.414 + ]]></body> 1.415 + </method> 1.416 + 1.417 + <!-- ::::::::::::: event dispatching ::::::::::::: --> 1.418 + 1.419 + <method name="initEventHandler"> 1.420 + <parameter name="aEventType"/> 1.421 + <body><![CDATA[ 1.422 + let handlerString = this.getAttribute("on" + aEventType); 1.423 + if (handlerString) { 1.424 + return (new Function("eventType", "param", handlerString)).bind(this, aEventType); 1.425 + } 1.426 + return null; 1.427 + ]]></body> 1.428 + </method> 1.429 + 1.430 + <!-- ::::::::::::: key handling ::::::::::::: --> 1.431 + 1.432 + <method name="onKeyPress"> 1.433 + <parameter name="aEvent"/> 1.434 + <body><![CDATA[ 1.435 + if (aEvent.target.localName != "textbox") 1.436 + return true; // Let child buttons of autocomplete take input 1.437 + 1.438 + //XXXpch this is so bogus... 1.439 + if (aEvent.defaultPrevented) 1.440 + return false; 1.441 + 1.442 + var cancel = false; 1.443 + 1.444 + // Catch any keys that could potentially move the caret. Ctrl can be 1.445 + // used in combination with these keys on Windows and Linux; and Alt 1.446 + // can be used on OS X, so make sure the unused one isn't used. 1.447 + if (!this.disableKeyNavigation && 1.448 +#ifdef XP_MACOSX 1.449 + !aEvent.ctrlKey) { 1.450 +#else 1.451 + !aEvent.altKey) { 1.452 +#endif 1.453 + switch (aEvent.keyCode) { 1.454 + case KeyEvent.DOM_VK_LEFT: 1.455 + case KeyEvent.DOM_VK_RIGHT: 1.456 + case KeyEvent.DOM_VK_HOME: 1.457 + cancel = this.mController.handleKeyNavigation(aEvent.keyCode); 1.458 + break; 1.459 + } 1.460 + } 1.461 + 1.462 + // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt) 1.463 + if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) { 1.464 + switch (aEvent.keyCode) { 1.465 + case KeyEvent.DOM_VK_TAB: 1.466 + if (this.tabScrolling && this.popup.popupOpen) 1.467 + cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ? 1.468 + KeyEvent.DOM_VK_UP : 1.469 + KeyEvent.DOM_VK_DOWN); 1.470 + else if (this.forceComplete && this.mController.matchCount >= 1) 1.471 + this.mController.handleTab(); 1.472 + break; 1.473 + case KeyEvent.DOM_VK_UP: 1.474 + case KeyEvent.DOM_VK_DOWN: 1.475 + case KeyEvent.DOM_VK_PAGE_UP: 1.476 + case KeyEvent.DOM_VK_PAGE_DOWN: 1.477 + cancel = this.mController.handleKeyNavigation(aEvent.keyCode); 1.478 + break; 1.479 + } 1.480 + } 1.481 + 1.482 + // Handle keys we know aren't part of a shortcut, even with Alt or 1.483 + // Ctrl. 1.484 + switch (aEvent.keyCode) { 1.485 + case KeyEvent.DOM_VK_ESCAPE: 1.486 + cancel = this.mController.handleEscape(); 1.487 + break; 1.488 + case KeyEvent.DOM_VK_RETURN: 1.489 +#ifdef XP_MACOSX 1.490 + // Prevent the default action, since it will beep on Mac 1.491 + if (aEvent.metaKey) 1.492 + aEvent.preventDefault(); 1.493 +#endif 1.494 + this.mEnterEvent = aEvent; 1.495 + cancel = this.mController.handleEnter(false); 1.496 + break; 1.497 + case KeyEvent.DOM_VK_DELETE: 1.498 +#ifdef XP_MACOSX 1.499 + case KeyEvent.DOM_VK_BACK_SPACE: 1.500 + if (aEvent.shiftKey) 1.501 +#endif 1.502 + cancel = this.mController.handleDelete(); 1.503 + break; 1.504 + case KeyEvent.DOM_VK_DOWN: 1.505 + case KeyEvent.DOM_VK_UP: 1.506 + if (aEvent.altKey) 1.507 + this.toggleHistoryPopup(); 1.508 + break; 1.509 +#ifndef XP_MACOSX 1.510 + case KeyEvent.DOM_VK_F4: 1.511 + this.toggleHistoryPopup(); 1.512 + break; 1.513 +#endif 1.514 + } 1.515 + 1.516 + if (cancel) { 1.517 + aEvent.stopPropagation(); 1.518 + aEvent.preventDefault(); 1.519 + } 1.520 + 1.521 + return true; 1.522 + ]]></body> 1.523 + </method> 1.524 + 1.525 + <!-- ::::::::::::: miscellaneous ::::::::::::: --> 1.526 + 1.527 + <method name="initSearchNames"> 1.528 + <body><![CDATA[ 1.529 + if (!this.mSearchNames) { 1.530 + var names = this.getAttribute("autocompletesearch"); 1.531 + if (!names) 1.532 + this.mSearchNames = []; 1.533 + else 1.534 + this.mSearchNames = names.split(" "); 1.535 + } 1.536 + ]]></body> 1.537 + </method> 1.538 + 1.539 + <method name="_focus"> 1.540 + <!-- doesn't reset this.mController --> 1.541 + <body><![CDATA[ 1.542 + this._dontBlur = true; 1.543 + this.focus(); 1.544 + this._dontBlur = false; 1.545 + ]]></body> 1.546 + </method> 1.547 + 1.548 + <method name="resetActionType"> 1.549 + <body><![CDATA[ 1.550 + if (this.mIgnoreInput) 1.551 + return; 1.552 + this.removeAttribute("actiontype"); 1.553 + ]]></body> 1.554 + </method> 1.555 + 1.556 + <field name="_valueIsPasted">false</field> 1.557 + <field name="_pasteController"><![CDATA[ 1.558 + ({ 1.559 + _autocomplete: this, 1.560 + _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard, 1.561 + supportsCommand: function(aCommand) aCommand == "cmd_paste", 1.562 + doCommand: function(aCommand) { 1.563 + this._autocomplete._valueIsPasted = true; 1.564 + this._autocomplete.editor.paste(this._kGlobalClipboard); 1.565 + this._autocomplete._valueIsPasted = false; 1.566 + }, 1.567 + isCommandEnabled: function(aCommand) { 1.568 + return this._autocomplete.editor.isSelectionEditable && 1.569 + this._autocomplete.editor.canPaste(this._kGlobalClipboard); 1.570 + }, 1.571 + onEvent: function() {} 1.572 + }) 1.573 + ]]></field> 1.574 + </implementation> 1.575 + 1.576 + <handlers> 1.577 + <handler event="input"><![CDATA[ 1.578 + if (!this.mIgnoreInput && this.mController.input == this) { 1.579 + this.valueIsTyped = true; 1.580 + this.mController.handleText(); 1.581 + } 1.582 + this.resetActionType(); 1.583 + ]]></handler> 1.584 + 1.585 + <handler event="keypress" phase="capturing" 1.586 + action="return this.onKeyPress(event);"/> 1.587 + 1.588 + <handler event="compositionstart" phase="capturing" 1.589 + action="if (this.mController.input == this) this.mController.handleStartComposition();"/> 1.590 + 1.591 + <handler event="compositionend" phase="capturing" 1.592 + action="if (this.mController.input == this) this.mController.handleEndComposition();"/> 1.593 + 1.594 + <handler event="focus" phase="capturing" 1.595 + action="this.attachController();"/> 1.596 + 1.597 + <handler event="blur" phase="capturing" 1.598 + action="if (!this._dontBlur) this.detachController();"/> 1.599 + </handlers> 1.600 + </binding> 1.601 + 1.602 + <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup"> 1.603 + <resources> 1.604 + <stylesheet src="chrome://global/skin/tree.css"/> 1.605 + <stylesheet src="chrome://global/skin/autocomplete.css"/> 1.606 + </resources> 1.607 + 1.608 + <content ignorekeys="true" level="top" consumeoutsideclicks="false"> 1.609 + <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single"> 1.610 + <xul:treecols anonid="treecols"> 1.611 + <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/> 1.612 + </xul:treecols> 1.613 + <xul:treechildren class="autocomplete-treebody"/> 1.614 + </xul:tree> 1.615 + </content> 1.616 + 1.617 + <implementation> 1.618 + <field name="mShowCommentColumn">false</field> 1.619 + <field name="mShowImageColumn">false</field> 1.620 + 1.621 + <property name="showCommentColumn" 1.622 + onget="return this.mShowCommentColumn;"> 1.623 + <setter> 1.624 + <![CDATA[ 1.625 + if (!val && this.mShowCommentColumn) { 1.626 + // reset the flex on the value column and remove the comment column 1.627 + document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1); 1.628 + this.removeColumn("treecolAutoCompleteComment"); 1.629 + } else if (val && !this.mShowCommentColumn) { 1.630 + // reset the flex on the value column and add the comment column 1.631 + document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2); 1.632 + this.addColumn({id: "treecolAutoCompleteComment", flex: 1}); 1.633 + } 1.634 + this.mShowCommentColumn = val; 1.635 + return val; 1.636 + ]]> 1.637 + </setter> 1.638 + </property> 1.639 + 1.640 + <property name="showImageColumn" 1.641 + onget="return this.mShowImageColumn;"> 1.642 + <setter> 1.643 + <![CDATA[ 1.644 + if (!val && this.mShowImageColumn) { 1.645 + // remove the image column 1.646 + this.removeColumn("treecolAutoCompleteImage"); 1.647 + } else if (val && !this.mShowImageColumn) { 1.648 + // add the image column 1.649 + this.addColumn({id: "treecolAutoCompleteImage", flex: 1}); 1.650 + } 1.651 + this.mShowImageColumn = val; 1.652 + return val; 1.653 + ]]> 1.654 + </setter> 1.655 + </property> 1.656 + 1.657 + 1.658 + <method name="addColumn"> 1.659 + <parameter name="aAttrs"/> 1.660 + <body> 1.661 + <![CDATA[ 1.662 + var col = document.createElement("treecol"); 1.663 + col.setAttribute("class", "autocomplete-treecol"); 1.664 + for (var name in aAttrs) 1.665 + col.setAttribute(name, aAttrs[name]); 1.666 + this.treecols.appendChild(col); 1.667 + return col; 1.668 + ]]> 1.669 + </body> 1.670 + </method> 1.671 + 1.672 + <method name="removeColumn"> 1.673 + <parameter name="aColId"/> 1.674 + <body> 1.675 + <![CDATA[ 1.676 + return this.treecols.removeChild(document.getElementById(aColId)); 1.677 + ]]> 1.678 + </body> 1.679 + </method> 1.680 + 1.681 + <property name="selectedIndex" 1.682 + onget="return this.tree.currentIndex;"> 1.683 + <setter> 1.684 + <![CDATA[ 1.685 + this.tree.view.selection.select(val); 1.686 + if (this.tree.treeBoxObject.height > 0) 1.687 + this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val); 1.688 + // Fire select event on xul:tree so that accessibility API 1.689 + // support layer can fire appropriate accessibility events. 1.690 + var event = document.createEvent('Events'); 1.691 + event.initEvent("select", true, true); 1.692 + this.tree.dispatchEvent(event); 1.693 + return val; 1.694 + ]]></setter> 1.695 + </property> 1.696 + 1.697 + <method name="adjustHeight"> 1.698 + <body> 1.699 + <![CDATA[ 1.700 + // detect the desired height of the tree 1.701 + var bx = this.tree.treeBoxObject; 1.702 + var view = this.tree.view; 1.703 + if (!view) 1.704 + return; 1.705 + var rows = this.maxRows; 1.706 + if (!view.rowCount || (rows && view.rowCount < rows)) 1.707 + rows = view.rowCount; 1.708 + 1.709 + var height = rows * bx.rowHeight; 1.710 + 1.711 + if (height == 0) 1.712 + this.tree.setAttribute("collapsed", "true"); 1.713 + else { 1.714 + if (this.tree.hasAttribute("collapsed")) 1.715 + this.tree.removeAttribute("collapsed"); 1.716 + 1.717 + this.tree.setAttribute("height", height); 1.718 + } 1.719 + this.tree.setAttribute("hidescrollbar", view.rowCount <= rows); 1.720 + ]]> 1.721 + </body> 1.722 + </method> 1.723 + 1.724 + <method name="openAutocompletePopup"> 1.725 + <parameter name="aInput"/> 1.726 + <parameter name="aElement"/> 1.727 + <body><![CDATA[ 1.728 + // until we have "baseBinding", (see bug #373652) this allows 1.729 + // us to override openAutocompletePopup(), but still call 1.730 + // the method on the base class 1.731 + this._openAutocompletePopup(aInput, aElement); 1.732 + ]]></body> 1.733 + </method> 1.734 + 1.735 + <method name="_openAutocompletePopup"> 1.736 + <parameter name="aInput"/> 1.737 + <parameter name="aElement"/> 1.738 + <body><![CDATA[ 1.739 + if (!this.mPopupOpen) { 1.740 + this.mInput = aInput; 1.741 + this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView); 1.742 + this.invalidate(); 1.743 + 1.744 + this.showCommentColumn = this.mInput.showCommentColumn; 1.745 + this.showImageColumn = this.mInput.showImageColumn; 1.746 + 1.747 + var rect = aElement.getBoundingClientRect(); 1.748 + var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.749 + .getInterface(Components.interfaces.nsIWebNavigation); 1.750 + var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell); 1.751 + var docViewer = docShell.contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer); 1.752 + var width = (rect.right - rect.left) * docViewer.fullZoom; 1.753 + this.setAttribute("width", width > 100 ? width : 100); 1.754 + 1.755 + // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840 1.756 + var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction; 1.757 + this.style.direction = popupDirection; 1.758 + 1.759 + this.openPopup(aElement, "after_start", 0, 0, false, false); 1.760 + } 1.761 + ]]></body> 1.762 + </method> 1.763 + 1.764 + <method name="invalidate"> 1.765 + <body><![CDATA[ 1.766 + this.adjustHeight(); 1.767 + this.tree.treeBoxObject.invalidate(); 1.768 + ]]></body> 1.769 + </method> 1.770 + 1.771 + <method name="selectBy"> 1.772 + <parameter name="aReverse"/> 1.773 + <parameter name="aPage"/> 1.774 + <body><![CDATA[ 1.775 + try { 1.776 + var amount = aPage ? 5 : 1; 1.777 + this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1); 1.778 + if (this.selectedIndex == -1) { 1.779 + this.input._focus(); 1.780 + } 1.781 + } catch (ex) { 1.782 + // do nothing - occasionally timer-related js errors happen here 1.783 + // e.g. "this.selectedIndex has no properties", when you type fast and hit a 1.784 + // navigation key before this popup has opened 1.785 + } 1.786 + ]]></body> 1.787 + </method> 1.788 + 1.789 + <!-- =================== PUBLIC MEMBERS =================== --> 1.790 + 1.791 + <field name="tree"> 1.792 + document.getAnonymousElementByAttribute(this, "anonid", "tree"); 1.793 + </field> 1.794 + 1.795 + <field name="treecols"> 1.796 + document.getAnonymousElementByAttribute(this, "anonid", "treecols"); 1.797 + </field> 1.798 + 1.799 + <property name="view" 1.800 + onget="return this.mView;"> 1.801 + <setter><![CDATA[ 1.802 + // We must do this by hand because the tree binding may not be ready yet 1.803 + this.mView = val; 1.804 + var bx = this.tree.boxObject; 1.805 + bx = bx.QueryInterface(Components.interfaces.nsITreeBoxObject); 1.806 + bx.view = val; 1.807 + ]]></setter> 1.808 + </property> 1.809 + 1.810 + </implementation> 1.811 + </binding> 1.812 + 1.813 + <binding id="autocomplete-base-popup" role="none" 1.814 +extends="chrome://global/content/bindings/popup.xml#popup"> 1.815 + <implementation implements="nsIAutoCompletePopup"> 1.816 + <field name="mInput">null</field> 1.817 + <field name="mPopupOpen">false</field> 1.818 + 1.819 + <!-- =================== nsIAutoCompletePopup =================== --> 1.820 + 1.821 + <property name="input" readonly="true" 1.822 + onget="return this.mInput"/> 1.823 + 1.824 + <property name="overrideValue" readonly="true" 1.825 + onget="return null;"/> 1.826 + 1.827 + <property name="popupOpen" readonly="true" 1.828 + onget="return this.mPopupOpen;"/> 1.829 + 1.830 + <method name="closePopup"> 1.831 + <body> 1.832 + <![CDATA[ 1.833 + if (this.mPopupOpen) { 1.834 + this.hidePopup(); 1.835 + this.removeAttribute("width"); 1.836 + } 1.837 + ]]> 1.838 + </body> 1.839 + </method> 1.840 + 1.841 + <!-- This is the default number of rows that we give the autocomplete 1.842 + popup when the textbox doesn't have a "maxrows" attribute 1.843 + for us to use. --> 1.844 + <field name="defaultMaxRows" readonly="true">6</field> 1.845 + 1.846 + <!-- In some cases (e.g. when the input's dropmarker button is clicked), 1.847 + the input wants to display a popup with more rows. In that case, it 1.848 + should increase its maxRows property and store the "normal" maxRows 1.849 + in this field. When the popup is hidden, we restore the input's 1.850 + maxRows to the value stored in this field. 1.851 + 1.852 + This field is set to -1 between uses so that we can tell when it's 1.853 + been set by the input and when we need to set it in the popupshowing 1.854 + handler. --> 1.855 + <field name="_normalMaxRows">-1</field> 1.856 + 1.857 + <property name="maxRows" readonly="true"> 1.858 + <getter> 1.859 + <![CDATA[ 1.860 + return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows; 1.861 + ]]> 1.862 + </getter> 1.863 + </property> 1.864 + 1.865 + <method name="getNextIndex"> 1.866 + <parameter name="aReverse"/> 1.867 + <parameter name="aAmount"/> 1.868 + <parameter name="aIndex"/> 1.869 + <parameter name="aMaxRow"/> 1.870 + <body><![CDATA[ 1.871 + if (aMaxRow < 0) 1.872 + return -1; 1.873 + 1.874 + var newIdx = aIndex + (aReverse?-1:1)*aAmount; 1.875 + if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow) 1.876 + newIdx = aMaxRow; 1.877 + else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0) 1.878 + newIdx = 0; 1.879 + 1.880 + if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow) 1.881 + aIndex = -1; 1.882 + else 1.883 + aIndex = newIdx; 1.884 + 1.885 + return aIndex; 1.886 + ]]></body> 1.887 + </method> 1.888 + 1.889 + <method name="onPopupClick"> 1.890 + <parameter name="aEvent"/> 1.891 + <body><![CDATA[ 1.892 + var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController); 1.893 + controller.handleEnter(true); 1.894 + ]]></body> 1.895 + </method> 1.896 + </implementation> 1.897 + 1.898 + <handlers> 1.899 + <handler event="popupshowing"><![CDATA[ 1.900 + // If normalMaxRows wasn't already set by the input, then set it here 1.901 + // so that we restore the correct number when the popup is hidden. 1.902 + if (this._normalMaxRows < 0) 1.903 + this._normalMaxRows = this.mInput.maxRows; 1.904 + 1.905 + this.mPopupOpen = true; 1.906 + ]]></handler> 1.907 + 1.908 + <handler event="popuphiding"><![CDATA[ 1.909 + var isListActive = true; 1.910 + if (this.selectedIndex == -1) 1.911 + isListActive = false; 1.912 + var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController); 1.913 + controller.stopSearch(); 1.914 + 1.915 + this.mPopupOpen = false; 1.916 + 1.917 + // Reset the maxRows property to the cached "normal" value, and reset 1.918 + // _normalMaxRows so that we can detect whether it was set by the input 1.919 + // when the popupshowing handler runs. 1.920 + this.mInput.maxRows = this._normalMaxRows; 1.921 + this._normalMaxRows = -1; 1.922 + // If the list was being navigated and then closed, make sure 1.923 + // we fire accessible focus event back to textbox 1.924 + if (isListActive) { 1.925 + this.mInput.mIgnoreFocus = true; 1.926 + this.mInput._focus(); 1.927 + this.mInput.mIgnoreFocus = false; 1.928 + } 1.929 + ]]></handler> 1.930 + </handlers> 1.931 + </binding> 1.932 + 1.933 + <binding id="autocomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup"> 1.934 + <resources> 1.935 + <stylesheet src="chrome://global/skin/autocomplete.css"/> 1.936 + </resources> 1.937 + 1.938 + <content ignorekeys="true" level="top" consumeoutsideclicks="false"> 1.939 + <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/> 1.940 + <xul:hbox> 1.941 + <children/> 1.942 + </xul:hbox> 1.943 + </content> 1.944 + 1.945 + <implementation implements="nsIAutoCompletePopup"> 1.946 + <field name="_currentIndex">0</field> 1.947 + <field name="_rowHeight">0</field> 1.948 + 1.949 + <!-- =================== nsIAutoCompletePopup =================== --> 1.950 + 1.951 + <property name="selectedIndex" 1.952 + onget="return this.richlistbox.selectedIndex;"> 1.953 + <setter> 1.954 + <![CDATA[ 1.955 + this.richlistbox.selectedIndex = val; 1.956 + 1.957 + // when clearing the selection (val == -1, so selectedItem will be 1.958 + // null), we want to scroll back to the top. see bug #406194 1.959 + this.richlistbox.ensureElementIsVisible( 1.960 + this.richlistbox.selectedItem || this.richlistbox.firstChild); 1.961 + 1.962 + return val; 1.963 + ]]> 1.964 + </setter> 1.965 + </property> 1.966 + 1.967 + <method name="openAutocompletePopup"> 1.968 + <parameter name="aInput"/> 1.969 + <parameter name="aElement"/> 1.970 + <body> 1.971 + <![CDATA[ 1.972 + // until we have "baseBinding", (see bug #373652) this allows 1.973 + // us to override openAutocompletePopup(), but still call 1.974 + // the method on the base class 1.975 + this._openAutocompletePopup(aInput, aElement); 1.976 + ]]> 1.977 + </body> 1.978 + </method> 1.979 + 1.980 + <method name="_openAutocompletePopup"> 1.981 + <parameter name="aInput"/> 1.982 + <parameter name="aElement"/> 1.983 + <body> 1.984 + <![CDATA[ 1.985 + if (!this.mPopupOpen) { 1.986 + this.mInput = aInput; 1.987 + // clear any previous selection, see bugs 400671 and 488357 1.988 + this.selectedIndex = -1; 1.989 + 1.990 + var width = aElement.getBoundingClientRect().width; 1.991 + this.setAttribute("width", width > 100 ? width : 100); 1.992 + // invalidate() depends on the width attribute 1.993 + this._invalidate(); 1.994 + 1.995 + this.openPopup(aElement, "after_start", 0, 0, false, false); 1.996 + } 1.997 + ]]> 1.998 + </body> 1.999 + </method> 1.1000 + 1.1001 + <method name="invalidate"> 1.1002 + <body> 1.1003 + <![CDATA[ 1.1004 + // Don't bother doing work if we're not even showing 1.1005 + if (!this.mPopupOpen) 1.1006 + return; 1.1007 + 1.1008 + this._invalidate(); 1.1009 + ]]> 1.1010 + </body> 1.1011 + </method> 1.1012 + 1.1013 + <method name="_invalidate"> 1.1014 + <body> 1.1015 + <![CDATA[ 1.1016 + if (!this.hasAttribute("height")) { 1.1017 + // collapsed if no matches 1.1018 + this.richlistbox.collapsed = (this._matchCount == 0); 1.1019 + 1.1020 + // Dynamically update height until richlistbox.rows works (bug 401939) 1.1021 + // Adjust the height immediately and after the row contents update 1.1022 + this.adjustHeight(); 1.1023 + setTimeout(function(self) self.adjustHeight(), 0, this); 1.1024 + } 1.1025 + 1.1026 + // make sure to collapse any existing richlistitems 1.1027 + // that aren't going to be used 1.1028 + var existingItemsCount = this.richlistbox.childNodes.length; 1.1029 + for (var i = this._matchCount; i < existingItemsCount; i++) 1.1030 + this.richlistbox.childNodes[i].collapsed = true; 1.1031 + 1.1032 + this._currentIndex = 0; 1.1033 + this._appendCurrentResult(); 1.1034 + ]]> 1.1035 + </body> 1.1036 + </method> 1.1037 + 1.1038 + <property name="maxResults" readonly="true"> 1.1039 + <getter> 1.1040 + <![CDATA[ 1.1041 + // this is how many richlistitems will be kept around 1.1042 + // (note, this getter may be overridden) 1.1043 + return 20; 1.1044 + ]]> 1.1045 + </getter> 1.1046 + </property> 1.1047 + 1.1048 + <property name="_matchCount" readonly="true"> 1.1049 + <getter> 1.1050 + <![CDATA[ 1.1051 + return Math.min(this.mInput.controller.matchCount, this.maxResults); 1.1052 + ]]> 1.1053 + </getter> 1.1054 + </property> 1.1055 + 1.1056 + <method name="adjustHeight"> 1.1057 + <body> 1.1058 + <![CDATA[ 1.1059 + // Figure out how many rows to show 1.1060 + let rows = this.richlistbox.childNodes; 1.1061 + let numRows = Math.min(this._matchCount, this.maxRows, rows.length); 1.1062 + 1.1063 + // Default the height to 0 if we have no rows to show 1.1064 + let height = 0; 1.1065 + if (numRows) { 1.1066 + if (!this._rowHeight) { 1.1067 + let firstRowRect = rows[0].getBoundingClientRect(); 1.1068 + this._rowHeight = firstRowRect.height; 1.1069 + } 1.1070 + 1.1071 + // Calculate the height to have the first row to last row shown 1.1072 + height = this._rowHeight * numRows; 1.1073 + } 1.1074 + 1.1075 + // Only update the height if we have a non-zero height and if it 1.1076 + // changed (the richlistbox is collapsed if there are no results) 1.1077 + if (height && height != this.richlistbox.height) 1.1078 + this.richlistbox.height = height; 1.1079 + ]]> 1.1080 + </body> 1.1081 + </method> 1.1082 + 1.1083 + <method name="_appendCurrentResult"> 1.1084 + <body> 1.1085 + <![CDATA[ 1.1086 + var controller = this.mInput.controller; 1.1087 + var matchCount = this._matchCount; 1.1088 + var existingItemsCount = this.richlistbox.childNodes.length; 1.1089 + 1.1090 + // Process maxRows per chunk to improve performance and user experience 1.1091 + for (let i = 0; i < this.maxRows; i++) { 1.1092 + if (this._currentIndex >= matchCount) 1.1093 + return; 1.1094 + 1.1095 + var item; 1.1096 + 1.1097 + // trim the leading/trailing whitespace 1.1098 + var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, ""); 1.1099 + 1.1100 + // Unescape the URI spec for showing as an entry in the popup 1.1101 + let url = Components.classes["@mozilla.org/intl/texttosuburi;1"]. 1.1102 + getService(Components.interfaces.nsITextToSubURI). 1.1103 + unEscapeURIForUI("UTF-8", controller.getValueAt(this._currentIndex)); 1.1104 + 1.1105 + if (typeof this.input.trimValue == "function") 1.1106 + url = this.input.trimValue(url); 1.1107 + 1.1108 + if (this._currentIndex < existingItemsCount) { 1.1109 + // re-use the existing item 1.1110 + item = this.richlistbox.childNodes[this._currentIndex]; 1.1111 + 1.1112 + // Completely re-use the existing richlistitem if it's the same 1.1113 + if (item.getAttribute("text") == trimmedSearchString && 1.1114 + item.getAttribute("url") == url) { 1.1115 + item.collapsed = false; 1.1116 + this._currentIndex++; 1.1117 + continue; 1.1118 + } 1.1119 + } 1.1120 + else { 1.1121 + // need to create a new item 1.1122 + item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem"); 1.1123 + } 1.1124 + 1.1125 + // set these attributes before we set the class 1.1126 + // so that we can use them from the constructor 1.1127 + item.setAttribute("image", controller.getImageAt(this._currentIndex)); 1.1128 + item.setAttribute("url", url); 1.1129 + item.setAttribute("title", controller.getCommentAt(this._currentIndex)); 1.1130 + item.setAttribute("type", controller.getStyleAt(this._currentIndex)); 1.1131 + item.setAttribute("text", trimmedSearchString); 1.1132 + 1.1133 + if (this._currentIndex < existingItemsCount) { 1.1134 + // re-use the existing item 1.1135 + item._adjustAcItem(); 1.1136 + item.collapsed = false; 1.1137 + } 1.1138 + else { 1.1139 + // set the class at the end so we can use the attributes 1.1140 + // in the xbl constructor 1.1141 + item.className = "autocomplete-richlistitem"; 1.1142 + this.richlistbox.appendChild(item); 1.1143 + } 1.1144 + 1.1145 + this._currentIndex++; 1.1146 + } 1.1147 + 1.1148 + // yield after each batch of items so that typing the url bar is responsive 1.1149 + setTimeout(function (self) { self._appendCurrentResult(); }, 0, this); 1.1150 + ]]> 1.1151 + </body> 1.1152 + </method> 1.1153 + 1.1154 + <method name="selectBy"> 1.1155 + <parameter name="aReverse"/> 1.1156 + <parameter name="aPage"/> 1.1157 + <body> 1.1158 + <![CDATA[ 1.1159 + try { 1.1160 + var amount = aPage ? 5 : 1; 1.1161 + 1.1162 + // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount 1.1163 + this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1); 1.1164 + if (this.selectedIndex == -1) { 1.1165 + this.input._focus(); 1.1166 + } 1.1167 + } catch (ex) { 1.1168 + // do nothing - occasionally timer-related js errors happen here 1.1169 + // e.g. "this.selectedIndex has no properties", when you type fast and hit a 1.1170 + // navigation key before this popup has opened 1.1171 + } 1.1172 + ]]> 1.1173 + </body> 1.1174 + </method> 1.1175 + 1.1176 + <field name="richlistbox"> 1.1177 + document.getAnonymousElementByAttribute(this, "anonid", "richlistbox"); 1.1178 + </field> 1.1179 + 1.1180 + <property name="view" 1.1181 + onget="return this.mInput.controller;" 1.1182 + onset="return val;"/> 1.1183 + 1.1184 + </implementation> 1.1185 + </binding> 1.1186 + 1.1187 + <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem"> 1.1188 + <content> 1.1189 + <xul:hbox align="center" class="ac-title-box"> 1.1190 + <xul:image xbl:inherits="src=image" class="ac-site-icon"/> 1.1191 + <xul:hbox anonid="title-box" class="ac-title" flex="1" 1.1192 + onunderflow="_doUnderflow('_title');" 1.1193 + onoverflow="_doOverflow('_title');"> 1.1194 + <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/> 1.1195 + </xul:hbox> 1.1196 + <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected" 1.1197 + class="ac-ellipsis-after ac-comment"/> 1.1198 + <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true"> 1.1199 + <xul:image class="ac-result-type-tag"/> 1.1200 + <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/> 1.1201 + <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/> 1.1202 + </xul:hbox> 1.1203 + <xul:image anonid="type-image" class="ac-type-icon"/> 1.1204 + </xul:hbox> 1.1205 + <xul:hbox align="center" class="ac-url-box"> 1.1206 + <xul:spacer class="ac-site-icon"/> 1.1207 + <xul:image class="ac-action-icon"/> 1.1208 + <xul:hbox anonid="url-box" class="ac-url" flex="1" 1.1209 + onunderflow="_doUnderflow('_url');" 1.1210 + onoverflow="_doOverflow('_url');"> 1.1211 + <xul:description anonid="url" class="ac-normal-text ac-url-text" 1.1212 + xbl:inherits="selected type"/> 1.1213 + <xul:description anonid="action" class="ac-normal-text ac-action-text" 1.1214 + xbl:inherits="selected type"/> 1.1215 + </xul:hbox> 1.1216 + <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected" 1.1217 + class="ac-ellipsis-after ac-url-text"/> 1.1218 + <xul:spacer class="ac-type-icon"/> 1.1219 + </xul:hbox> 1.1220 + </content> 1.1221 + <implementation implements="nsIDOMXULSelectControlItemElement"> 1.1222 + <constructor> 1.1223 + <![CDATA[ 1.1224 + let ellipsis = "\u2026"; 1.1225 + try { 1.1226 + ellipsis = Components.classes["@mozilla.org/preferences-service;1"]. 1.1227 + getService(Components.interfaces.nsIPrefBranch). 1.1228 + getComplexValue("intl.ellipsis", 1.1229 + Components.interfaces.nsIPrefLocalizedString).data; 1.1230 + } catch (ex) { 1.1231 + // Do nothing.. we already have a default 1.1232 + } 1.1233 + 1.1234 + this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis"); 1.1235 + this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis"); 1.1236 + 1.1237 + this._urlOverflowEllipsis.value = ellipsis; 1.1238 + this._titleOverflowEllipsis.value = ellipsis; 1.1239 + 1.1240 + this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image"); 1.1241 + 1.1242 + this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box"); 1.1243 + this._url = document.getAnonymousElementByAttribute(this, "anonid", "url"); 1.1244 + this._action = document.getAnonymousElementByAttribute(this, "anonid", "action"); 1.1245 + 1.1246 + this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box"); 1.1247 + this._title = document.getAnonymousElementByAttribute(this, "anonid", "title"); 1.1248 + 1.1249 + this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box"); 1.1250 + this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra"); 1.1251 + 1.1252 + this._adjustAcItem(); 1.1253 + ]]> 1.1254 + </constructor> 1.1255 + 1.1256 + <property name="label" readonly="true"> 1.1257 + <getter> 1.1258 + <![CDATA[ 1.1259 + var title = this.getAttribute("title"); 1.1260 + var url = this.getAttribute("url"); 1.1261 + var panel = this.parentNode.parentNode; 1.1262 + 1.1263 + // allow consumers that have extended popups to override 1.1264 + // the label values for the richlistitems 1.1265 + if (panel.createResultLabel) 1.1266 + return panel.createResultLabel(title, url, this.getAttribute("type")); 1.1267 + 1.1268 + // aType (ex: "ac-result-type-<aType>") is related to the class of the image, 1.1269 + // and is not "visible" text so don't use it for the label (for accessibility). 1.1270 + return title + " " + url; 1.1271 + ]]> 1.1272 + </getter> 1.1273 + </property> 1.1274 + 1.1275 + <field name="_boundaryCutoff">null</field> 1.1276 + 1.1277 + <property name="boundaryCutoff" readonly="true"> 1.1278 + <getter> 1.1279 + <![CDATA[ 1.1280 + if (!this._boundaryCutoff) { 1.1281 + this._boundaryCutoff = 1.1282 + Components.classes["@mozilla.org/preferences-service;1"]. 1.1283 + getService(Components.interfaces.nsIPrefBranch). 1.1284 + getIntPref("toolkit.autocomplete.richBoundaryCutoff"); 1.1285 + } 1.1286 + return this._boundaryCutoff; 1.1287 + ]]> 1.1288 + </getter> 1.1289 + </property> 1.1290 + 1.1291 + <method name="_getBoundaryIndices"> 1.1292 + <parameter name="aText"/> 1.1293 + <parameter name="aSearchTokens"/> 1.1294 + <body> 1.1295 + <![CDATA[ 1.1296 + // Short circuit for empty search ([""] == "") 1.1297 + if (aSearchTokens == "") 1.1298 + return [0, aText.length]; 1.1299 + 1.1300 + // Find which regions of text match the search terms 1.1301 + let regions = []; 1.1302 + for each (let search in aSearchTokens) { 1.1303 + let matchIndex; 1.1304 + let startIndex = 0; 1.1305 + let searchLen = search.length; 1.1306 + 1.1307 + // Find all matches of the search terms, but stop early for perf 1.1308 + let lowerText = aText.toLowerCase().substr(0, this.boundaryCutoff); 1.1309 + while ((matchIndex = lowerText.indexOf(search, startIndex)) >= 0) { 1.1310 + // Start the next search from where this one finished 1.1311 + startIndex = matchIndex + searchLen; 1.1312 + regions.push([matchIndex, startIndex]); 1.1313 + } 1.1314 + } 1.1315 + 1.1316 + // Sort the regions by start position then end position 1.1317 + regions = regions.sort(function(a, b) let (start = a[0] - b[0]) 1.1318 + start == 0 ? a[1] - b[1] : start); 1.1319 + 1.1320 + // Generate the boundary indices from each region 1.1321 + let start = 0; 1.1322 + let end = 0; 1.1323 + let boundaries = []; 1.1324 + let len = regions.length; 1.1325 + for (let i = 0; i < len; i++) { 1.1326 + // We have a new boundary if the start of the next is past the end 1.1327 + let region = regions[i]; 1.1328 + if (region[0] > end) { 1.1329 + // First index is the beginning of match 1.1330 + boundaries.push(start); 1.1331 + // Second index is the beginning of non-match 1.1332 + boundaries.push(end); 1.1333 + 1.1334 + // Track the new region now that we've stored the previous one 1.1335 + start = region[0]; 1.1336 + } 1.1337 + 1.1338 + // Push back the end index for the current or new region 1.1339 + end = Math.max(end, region[1]); 1.1340 + } 1.1341 + 1.1342 + // Add the last region 1.1343 + boundaries.push(start); 1.1344 + boundaries.push(end); 1.1345 + 1.1346 + // Put on the end boundary if necessary 1.1347 + if (end < aText.length) 1.1348 + boundaries.push(aText.length); 1.1349 + 1.1350 + // Skip the first item because it's always 0 1.1351 + return boundaries.slice(1); 1.1352 + ]]> 1.1353 + </body> 1.1354 + </method> 1.1355 + 1.1356 + <method name="_getSearchTokens"> 1.1357 + <parameter name="aSearch"/> 1.1358 + <body> 1.1359 + <![CDATA[ 1.1360 + let search = aSearch.toLowerCase(); 1.1361 + return search.split(/\s+/); 1.1362 + ]]> 1.1363 + </body> 1.1364 + </method> 1.1365 + 1.1366 + <method name="_setUpDescription"> 1.1367 + <parameter name="aDescriptionElement"/> 1.1368 + <parameter name="aText"/> 1.1369 + <parameter name="aNoEmphasis"/> 1.1370 + <body> 1.1371 + <![CDATA[ 1.1372 + // Get rid of all previous text 1.1373 + while (aDescriptionElement.hasChildNodes()) 1.1374 + aDescriptionElement.removeChild(aDescriptionElement.firstChild); 1.1375 + 1.1376 + // If aNoEmphasis is specified, don't add any emphasis 1.1377 + if (aNoEmphasis) { 1.1378 + aDescriptionElement.appendChild(document.createTextNode(aText)); 1.1379 + return; 1.1380 + } 1.1381 + 1.1382 + // Get the indices that separate match and non-match text 1.1383 + let search = this.getAttribute("text"); 1.1384 + let tokens = this._getSearchTokens(search); 1.1385 + let indices = this._getBoundaryIndices(aText, tokens); 1.1386 + 1.1387 + let next; 1.1388 + let start = 0; 1.1389 + let len = indices.length; 1.1390 + // Even indexed boundaries are matches, so skip the 0th if it's empty 1.1391 + for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) { 1.1392 + next = indices[i]; 1.1393 + let text = aText.substr(start, next - start); 1.1394 + start = next; 1.1395 + 1.1396 + if (i % 2 == 0) { 1.1397 + // Emphasize the text for even indices 1.1398 + let span = aDescriptionElement.appendChild( 1.1399 + document.createElementNS("http://www.w3.org/1999/xhtml", "span")); 1.1400 + span.className = "ac-emphasize-text"; 1.1401 + span.textContent = text; 1.1402 + } else { 1.1403 + // Otherwise, it's plain text 1.1404 + aDescriptionElement.appendChild(document.createTextNode(text)); 1.1405 + } 1.1406 + } 1.1407 + ]]> 1.1408 + </body> 1.1409 + </method> 1.1410 + 1.1411 + <method name="_adjustAcItem"> 1.1412 + <body> 1.1413 + <![CDATA[ 1.1414 + var url = this.getAttribute("url"); 1.1415 + var title = this.getAttribute("title"); 1.1416 + var type = this.getAttribute("type"); 1.1417 + 1.1418 + this.removeAttribute("actiontype"); 1.1419 + 1.1420 + // The ellipses are hidden via their visibility so that they always 1.1421 + // take up space and don't pop in on top of text when shown. For 1.1422 + // keyword searches, however, the title ellipsis should not take up 1.1423 + // space when hidden. Setting the hidden property accomplishes that. 1.1424 + this._titleOverflowEllipsis.hidden = false; 1.1425 + 1.1426 + // If the type includes an action, set up the item appropriately. 1.1427 + var types = type.split(/\s+/); 1.1428 + var actionIndex = types.indexOf("action"); 1.1429 + if (actionIndex >= 0) { 1.1430 + let [,action, param] = url.match(/^moz-action:([^,]+),(.*)$/); 1.1431 + this.setAttribute("actiontype", action); 1.1432 + url = param; 1.1433 + let desc = "]]>&action.switchToTab.label;<![CDATA["; 1.1434 + this._setUpDescription(this._action, desc, true); 1.1435 + 1.1436 + // Remove the "action" substring so that the correct style, if any, 1.1437 + // is applied below. 1.1438 + types.splice(actionIndex, 1); 1.1439 + type = types.join(" "); 1.1440 + } 1.1441 + 1.1442 + // If we have a tag match, show the tags and icon 1.1443 + if (type == "tag") { 1.1444 + // Configure the extra box for tags display 1.1445 + this._extraBox.hidden = false; 1.1446 + this._extraBox.childNodes[0].hidden = false; 1.1447 + this._extraBox.childNodes[1].hidden = true; 1.1448 + this._extraBox.pack = "end"; 1.1449 + this._titleBox.flex = 1; 1.1450 + 1.1451 + // The title is separated from the tags by an endash 1.1452 + let tags; 1.1453 + [, title, tags] = title.match(/^(.+) \u2013 (.+)$/); 1.1454 + 1.1455 + // Each tag is split by a comma in an undefined order, so sort it 1.1456 + let sortedTags = tags.split(",").sort().join(", "); 1.1457 + 1.1458 + // Emphasize the matching text in the tags 1.1459 + this._setUpDescription(this._extra, sortedTags); 1.1460 + 1.1461 + // Treat tagged matches as bookmarks for the star 1.1462 + type = "bookmark"; 1.1463 + } else if (type == "keyword") { 1.1464 + // Configure the extra box for keyword display 1.1465 + this._extraBox.hidden = false; 1.1466 + this._extraBox.childNodes[0].hidden = true; 1.1467 + this._extraBox.childNodes[1].hidden = false; 1.1468 + this._extraBox.pack = "start"; 1.1469 + this._titleBox.flex = 0; 1.1470 + 1.1471 + // Hide the ellipsis so it doesn't take up space. 1.1472 + this._titleOverflowEllipsis.hidden = true; 1.1473 + 1.1474 + // Put the parameters next to the title if we have any 1.1475 + let search = this.getAttribute("text"); 1.1476 + let params = ""; 1.1477 + let paramsIndex = search.indexOf(' '); 1.1478 + if (paramsIndex != -1) 1.1479 + params = search.substr(paramsIndex + 1); 1.1480 + 1.1481 + // Emphasize the keyword parameters 1.1482 + this._setUpDescription(this._extra, params); 1.1483 + 1.1484 + // Don't emphasize keyword searches in the title or url 1.1485 + this.setAttribute("text", ""); 1.1486 + } else { 1.1487 + // Hide the title's extra box if we don't need extra stuff 1.1488 + this._extraBox.hidden = true; 1.1489 + this._titleBox.flex = 1; 1.1490 + } 1.1491 + 1.1492 + // Give the image the icon style and a special one for the type 1.1493 + this._typeImage.className = "ac-type-icon" + 1.1494 + (type ? " ac-result-type-" + type : ""); 1.1495 + 1.1496 + // Show the url as the title if we don't have a title 1.1497 + if (title == "") 1.1498 + title = url; 1.1499 + 1.1500 + // Emphasize the matching search terms for the description 1.1501 + this._setUpDescription(this._title, title); 1.1502 + this._setUpDescription(this._url, url); 1.1503 + 1.1504 + // Set up overflow on a timeout because the contents of the box 1.1505 + // might not have a width yet even though we just changed them 1.1506 + setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis); 1.1507 + setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis); 1.1508 + ]]> 1.1509 + </body> 1.1510 + </method> 1.1511 + 1.1512 + <method name="_setUpOverflow"> 1.1513 + <parameter name="aParentBox"/> 1.1514 + <parameter name="aEllipsis"/> 1.1515 + <body> 1.1516 + <![CDATA[ 1.1517 + // Hide the ellipsis incase there's just enough to not underflow 1.1518 + aEllipsis.style.visibility = "hidden"; 1.1519 + 1.1520 + // Start with the parent's width and subtract off its children 1.1521 + let tooltip = []; 1.1522 + let children = aParentBox.childNodes; 1.1523 + let widthDiff = aParentBox.boxObject.width; 1.1524 + 1.1525 + for (let i = 0; i < children.length; i++) { 1.1526 + // Only consider a child if it actually takes up space 1.1527 + let childWidth = children[i].boxObject.width; 1.1528 + if (childWidth > 0) { 1.1529 + // Subtract a little less to account for subpixel rounding 1.1530 + widthDiff -= childWidth - .5; 1.1531 + 1.1532 + // Add to the tooltip if it's not hidden and has text 1.1533 + let childText = children[i].textContent; 1.1534 + if (childText) 1.1535 + tooltip.push(childText); 1.1536 + } 1.1537 + } 1.1538 + 1.1539 + // If the children take up more space than the parent.. overflow! 1.1540 + if (widthDiff < 0) { 1.1541 + // Re-show the ellipsis now that we know it's needed 1.1542 + aEllipsis.style.visibility = "visible"; 1.1543 + 1.1544 + // Separate text components with a ndash -- 1.1545 + aParentBox.tooltipText = tooltip.join(" \u2013 "); 1.1546 + } 1.1547 + ]]> 1.1548 + </body> 1.1549 + </method> 1.1550 + 1.1551 + <method name="_doUnderflow"> 1.1552 + <parameter name="aName"/> 1.1553 + <body> 1.1554 + <![CDATA[ 1.1555 + // Hide the ellipsis right when we know we're underflowing instead of 1.1556 + // waiting for the timeout to trigger the _setUpOverflow calculations 1.1557 + this[aName + "Box"].tooltipText = ""; 1.1558 + this[aName + "OverflowEllipsis"].style.visibility = "hidden"; 1.1559 + ]]> 1.1560 + </body> 1.1561 + </method> 1.1562 + 1.1563 + <method name="_doOverflow"> 1.1564 + <parameter name="aName"/> 1.1565 + <body> 1.1566 + <![CDATA[ 1.1567 + this._setUpOverflow(this[aName + "Box"], 1.1568 + this[aName + "OverflowEllipsis"]); 1.1569 + ]]> 1.1570 + </body> 1.1571 + </method> 1.1572 + 1.1573 + </implementation> 1.1574 + </binding> 1.1575 + 1.1576 + <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree"> 1.1577 + <content> 1.1578 + <children includes="treecols"/> 1.1579 + <xul:treerows class="autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1"> 1.1580 + <children/> 1.1581 + </xul:treerows> 1.1582 + </content> 1.1583 + </binding> 1.1584 + 1.1585 + <binding id="autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox"> 1.1586 + <implementation> 1.1587 + <field name="mLastMoveTime">Date.now()</field> 1.1588 + </implementation> 1.1589 + <handlers> 1.1590 + <handler event="mouseup"> 1.1591 + <![CDATA[ 1.1592 + // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc. 1.1593 + var item = event.originalTarget; 1.1594 + 1.1595 + while (item && item.localName != "richlistitem") 1.1596 + item = item.parentNode; 1.1597 + 1.1598 + if (!item) 1.1599 + return; 1.1600 + 1.1601 + this.parentNode.onPopupClick(event); 1.1602 + ]]> 1.1603 + </handler> 1.1604 + 1.1605 + <handler event="mousemove"> 1.1606 + <![CDATA[ 1.1607 + if (Date.now() - this.mLastMoveTime > 30) { 1.1608 + var item = event.target; 1.1609 + 1.1610 + while (item && item.localName != "richlistitem") 1.1611 + item = item.parentNode; 1.1612 + 1.1613 + if (!item) 1.1614 + return; 1.1615 + 1.1616 + var rc = this.getIndexOfItem(item); 1.1617 + if (rc != this.selectedIndex) 1.1618 + this.selectedIndex = rc; 1.1619 + 1.1620 + this.mLastMoveTime = Date.now(); 1.1621 + } 1.1622 + ]]> 1.1623 + </handler> 1.1624 + </handlers> 1.1625 + </binding> 1.1626 + 1.1627 + <binding id="autocomplete-treebody"> 1.1628 + <implementation> 1.1629 + <field name="mLastMoveTime">Date.now()</field> 1.1630 + </implementation> 1.1631 + 1.1632 + <handlers> 1.1633 + <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/> 1.1634 + 1.1635 + <handler event="mousedown"><![CDATA[ 1.1636 + var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); 1.1637 + if (rc != this.parentNode.currentIndex) 1.1638 + this.parentNode.view.selection.select(rc); 1.1639 + ]]></handler> 1.1640 + 1.1641 + <handler event="mousemove"><![CDATA[ 1.1642 + if (Date.now() - this.mLastMoveTime > 30) { 1.1643 + var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); 1.1644 + if (rc != this.parentNode.currentIndex) 1.1645 + this.parentNode.view.selection.select(rc); 1.1646 + this.mLastMoveTime = Date.now(); 1.1647 + } 1.1648 + ]]></handler> 1.1649 + </handlers> 1.1650 + </binding> 1.1651 + 1.1652 + <binding id="autocomplete-treerows"> 1.1653 + <content> 1.1654 + <xul:hbox flex="1" class="tree-bodybox"> 1.1655 + <children/> 1.1656 + </xul:hbox> 1.1657 + <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/> 1.1658 + </content> 1.1659 + </binding> 1.1660 + 1.1661 + <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker"> 1.1662 + <implementation> 1.1663 + <method name="showPopup"> 1.1664 + <body><![CDATA[ 1.1665 + var textbox = document.getBindingParent(this); 1.1666 + textbox.showHistoryPopup(); 1.1667 + ]]></body> 1.1668 + </method> 1.1669 + </implementation> 1.1670 + 1.1671 + <handlers> 1.1672 + <handler event="mousedown" button="0"><![CDATA[ 1.1673 + this.showPopup(); 1.1674 + ]]></handler> 1.1675 + </handlers> 1.1676 + </binding> 1.1677 + 1.1678 +</bindings>