toolkit/content/widgets/autocomplete.xml

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 <?xml version="1.0"?>
michael@0 2
michael@0 3 # -*- Mode: HTML -*-
michael@0 4 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 5 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 7
michael@0 8 <!DOCTYPE bindings [
michael@0 9 <!ENTITY % actionsDTD SYSTEM "chrome://global/locale/actions.dtd">
michael@0 10 %actionsDTD;
michael@0 11 ]>
michael@0 12
michael@0 13 <bindings id="autocompleteBindings"
michael@0 14 xmlns="http://www.mozilla.org/xbl"
michael@0 15 xmlns:html="http://www.w3.org/1999/xhtml"
michael@0 16 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
michael@0 17 xmlns:xbl="http://www.mozilla.org/xbl">
michael@0 18
michael@0 19 <binding id="autocomplete" role="xul:combobox"
michael@0 20 extends="chrome://global/content/bindings/textbox.xml#textbox">
michael@0 21 <resources>
michael@0 22 <stylesheet src="chrome://global/skin/autocomplete.css"/>
michael@0 23 </resources>
michael@0 24
michael@0 25 <content sizetopopup="pref">
michael@0 26 <xul:hbox class="autocomplete-textbox-container" flex="1" xbl:inherits="focused">
michael@0 27 <children includes="image|deck|stack|box">
michael@0 28 <xul:image class="autocomplete-icon" allowevents="true"/>
michael@0 29 </children>
michael@0 30
michael@0 31 <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
michael@0 32 <children/>
michael@0 33 <html:input anonid="input" class="autocomplete-textbox textbox-input"
michael@0 34 allowevents="true"
michael@0 35 xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
michael@0 36 </xul:hbox>
michael@0 37 <children includes="hbox"/>
michael@0 38 </xul:hbox>
michael@0 39
michael@0 40 <xul:dropmarker anonid="historydropmarker" class="autocomplete-history-dropmarker"
michael@0 41 allowevents="true"
michael@0 42 xbl:inherits="open,enablehistory,parentfocused=focused"/>
michael@0 43
michael@0 44 <xul:popupset anonid="popupset" class="autocomplete-result-popupset"/>
michael@0 45
michael@0 46 <children includes="toolbarbutton"/>
michael@0 47 </content>
michael@0 48
michael@0 49 <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
michael@0 50 <field name="mController">null</field>
michael@0 51 <field name="mSearchNames">null</field>
michael@0 52 <field name="mIgnoreInput">false</field>
michael@0 53 <field name="mEnterEvent">null</field>
michael@0 54
michael@0 55 <field name="_searchBeginHandler">null</field>
michael@0 56 <field name="_searchCompleteHandler">null</field>
michael@0 57 <field name="_textEnteredHandler">null</field>
michael@0 58 <field name="_textRevertedHandler">null</field>
michael@0 59
michael@0 60 <constructor><![CDATA[
michael@0 61 this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
michael@0 62 getService(Components.interfaces.nsIAutoCompleteController);
michael@0 63
michael@0 64 this._searchBeginHandler = this.initEventHandler("searchbegin");
michael@0 65 this._searchCompleteHandler = this.initEventHandler("searchcomplete");
michael@0 66 this._textEnteredHandler = this.initEventHandler("textentered");
michael@0 67 this._textRevertedHandler = this.initEventHandler("textreverted");
michael@0 68
michael@0 69 // For security reasons delay searches on pasted values.
michael@0 70 this.inputField.controllers.insertControllerAt(0, this._pasteController);
michael@0 71 ]]></constructor>
michael@0 72
michael@0 73 <destructor><![CDATA[
michael@0 74 this.inputField.controllers.removeController(this._pasteController);
michael@0 75 ]]></destructor>
michael@0 76
michael@0 77 <!-- =================== nsIAutoCompleteInput =================== -->
michael@0 78
michael@0 79 <field name="popup"><![CDATA[
michael@0 80 // Wrap in a block so that the let statements don't
michael@0 81 // create properties on 'this' (bug 635252).
michael@0 82 {
michael@0 83 let popup = null;
michael@0 84 let popupId = this.getAttribute("autocompletepopup");
michael@0 85 if (popupId)
michael@0 86 popup = document.getElementById(popupId);
michael@0 87 if (!popup) {
michael@0 88 popup = document.createElement("panel");
michael@0 89 popup.setAttribute("type", "autocomplete");
michael@0 90 popup.setAttribute("noautofocus", "true");
michael@0 91
michael@0 92 let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
michael@0 93 popupset.appendChild(popup);
michael@0 94 }
michael@0 95 popup.mInput = this;
michael@0 96 popup;
michael@0 97 }
michael@0 98 ]]></field>
michael@0 99
michael@0 100 <property name="controller" onget="return this.mController;" readonly="true"/>
michael@0 101
michael@0 102 <property name="popupOpen"
michael@0 103 onget="return this.popup.popupOpen;"
michael@0 104 onset="if (val) this.openPopup(); else this.closePopup();"/>
michael@0 105
michael@0 106 <property name="disableAutoComplete"
michael@0 107 onset="this.setAttribute('disableautocomplete', val); return val;"
michael@0 108 onget="return this.getAttribute('disableautocomplete') == 'true';"/>
michael@0 109
michael@0 110 <property name="completeDefaultIndex"
michael@0 111 onset="this.setAttribute('completedefaultindex', val); return val;"
michael@0 112 onget="return this.getAttribute('completedefaultindex') == 'true';"/>
michael@0 113
michael@0 114 <property name="completeSelectedIndex"
michael@0 115 onset="this.setAttribute('completeselectedindex', val); return val;"
michael@0 116 onget="return this.getAttribute('completeselectedindex') == 'true';"/>
michael@0 117
michael@0 118 <property name="forceComplete"
michael@0 119 onset="this.setAttribute('forcecomplete', val); return val;"
michael@0 120 onget="return this.getAttribute('forcecomplete') == 'true';"/>
michael@0 121
michael@0 122 <property name="minResultsForPopup"
michael@0 123 onset="this.setAttribute('minresultsforpopup', val); return val;"
michael@0 124 onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
michael@0 125
michael@0 126 <property name="showCommentColumn"
michael@0 127 onset="this.setAttribute('showcommentcolumn', val); return val;"
michael@0 128 onget="return this.getAttribute('showcommentcolumn') == 'true';"/>
michael@0 129
michael@0 130 <property name="showImageColumn"
michael@0 131 onset="this.setAttribute('showimagecolumn', val); return val;"
michael@0 132 onget="return this.getAttribute('showimagecolumn') == 'true';"/>
michael@0 133
michael@0 134 <property name="timeout"
michael@0 135 onset="this.setAttribute('timeout', val); return val;">
michael@0 136 <getter><![CDATA[
michael@0 137 // For security reasons delay searches on pasted values.
michael@0 138 if (this._valueIsPasted) {
michael@0 139 let t = parseInt(this.getAttribute('pastetimeout'));
michael@0 140 return isNaN(t) ? 1000 : t;
michael@0 141 }
michael@0 142
michael@0 143 let t = parseInt(this.getAttribute('timeout'));
michael@0 144 return isNaN(t) ? 50 : t;
michael@0 145 ]]></getter>
michael@0 146 </property>
michael@0 147
michael@0 148 <property name="searchParam"
michael@0 149 onget="return this.getAttribute('autocompletesearchparam') || '';"
michael@0 150 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
michael@0 151
michael@0 152 <property name="searchCount" readonly="true"
michael@0 153 onget="this.initSearchNames(); return this.mSearchNames.length;"/>
michael@0 154
michael@0 155 <field name="PrivateBrowsingUtils" readonly="true">
michael@0 156 let utils = {};
michael@0 157 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils);
michael@0 158 utils.PrivateBrowsingUtils
michael@0 159 </field>
michael@0 160
michael@0 161 <property name="inPrivateContext" readonly="true"
michael@0 162 onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
michael@0 163
michael@0 164 <!-- This is the maximum number of drop-down rows we get when we
michael@0 165 hit the drop marker beside fields that have it (like the URLbar).-->
michael@0 166 <field name="maxDropMarkerRows" readonly="true">14</field>
michael@0 167
michael@0 168 <method name="getSearchAt">
michael@0 169 <parameter name="aIndex"/>
michael@0 170 <body><![CDATA[
michael@0 171 this.initSearchNames();
michael@0 172 return this.mSearchNames[aIndex];
michael@0 173 ]]></body>
michael@0 174 </method>
michael@0 175
michael@0 176 <property name="textValue"
michael@0 177 onget="return this.value;">
michael@0 178 <setter><![CDATA[
michael@0 179 // Completing a result should simulate the user typing the result,
michael@0 180 // so fire an input event.
michael@0 181 // Trim popup selected values, but never trim results coming from
michael@0 182 // autofill.
michael@0 183 if (this.popup.selectedIndex == -1)
michael@0 184 this._disableTrim = true;
michael@0 185 this.value = val;
michael@0 186 this._disableTrim = false;
michael@0 187
michael@0 188 var evt = document.createEvent("UIEvents");
michael@0 189 evt.initUIEvent("input", true, false, window, 0);
michael@0 190 this.mIgnoreInput = true;
michael@0 191 this.dispatchEvent(evt);
michael@0 192 this.mIgnoreInput = false;
michael@0 193 return this.value;
michael@0 194 ]]></setter>
michael@0 195 </property>
michael@0 196
michael@0 197 <method name="selectTextRange">
michael@0 198 <parameter name="aStartIndex"/>
michael@0 199 <parameter name="aEndIndex"/>
michael@0 200 <body><![CDATA[
michael@0 201 this.inputField.setSelectionRange(aStartIndex, aEndIndex);
michael@0 202 ]]></body>
michael@0 203 </method>
michael@0 204
michael@0 205 <method name="onSearchBegin">
michael@0 206 <body><![CDATA[
michael@0 207 if (this._searchBeginHandler)
michael@0 208 this._searchBeginHandler();
michael@0 209 ]]></body>
michael@0 210 </method>
michael@0 211
michael@0 212 <method name="onSearchComplete">
michael@0 213 <body><![CDATA[
michael@0 214 if (this.mController.matchCount == 0)
michael@0 215 this.setAttribute("nomatch", "true");
michael@0 216 else
michael@0 217 this.removeAttribute("nomatch");
michael@0 218
michael@0 219 if (this._searchCompleteHandler)
michael@0 220 this._searchCompleteHandler();
michael@0 221 ]]></body>
michael@0 222 </method>
michael@0 223
michael@0 224 <method name="onTextEntered">
michael@0 225 <body><![CDATA[
michael@0 226 let rv = false;
michael@0 227 if (this._textEnteredHandler)
michael@0 228 rv = this._textEnteredHandler(this.mEnterEvent);
michael@0 229 this.mEnterEvent = null;
michael@0 230 return rv;
michael@0 231 ]]></body>
michael@0 232 </method>
michael@0 233
michael@0 234 <method name="onTextReverted">
michael@0 235 <body><![CDATA[
michael@0 236 if (this._textRevertedHandler)
michael@0 237 return this._textRevertedHandler();
michael@0 238 return false;
michael@0 239 ]]></body>
michael@0 240 </method>
michael@0 241
michael@0 242 <!-- =================== nsIDOMXULMenuListElement =================== -->
michael@0 243
michael@0 244 <property name="editable" readonly="true"
michael@0 245 onget="return true;" />
michael@0 246
michael@0 247 <property name="crop"
michael@0 248 onset="this.setAttribute('crop',val); return val;"
michael@0 249 onget="return this.getAttribute('crop');"/>
michael@0 250
michael@0 251 <property name="open"
michael@0 252 onget="return this.getAttribute('open') == 'true';">
michael@0 253 <setter><![CDATA[
michael@0 254 if (val)
michael@0 255 this.showHistoryPopup();
michael@0 256 else
michael@0 257 this.closePopup();
michael@0 258 ]]></setter>
michael@0 259 </property>
michael@0 260
michael@0 261 <!-- =================== PUBLIC MEMBERS =================== -->
michael@0 262
michael@0 263 <field name="valueIsTyped">false</field>
michael@0 264 <field name="_disableTrim">false</field>
michael@0 265 <property name="value">
michael@0 266 <getter><![CDATA[
michael@0 267 if (typeof this.onBeforeValueGet == "function") {
michael@0 268 var result = this.onBeforeValueGet();
michael@0 269 if (result)
michael@0 270 return result.value;
michael@0 271 }
michael@0 272 return this.inputField.value;
michael@0 273 ]]></getter>
michael@0 274 <setter><![CDATA[
michael@0 275 this.mIgnoreInput = true;
michael@0 276
michael@0 277 if (typeof this.onBeforeValueSet == "function")
michael@0 278 val = this.onBeforeValueSet(val);
michael@0 279
michael@0 280 if (typeof this.trimValue == "function" && !this._disableTrim)
michael@0 281 val = this.trimValue(val);
michael@0 282
michael@0 283 this.valueIsTyped = false;
michael@0 284 this.inputField.value = val;
michael@0 285
michael@0 286 if (typeof this.formatValue == "function")
michael@0 287 this.formatValue();
michael@0 288
michael@0 289 this.mIgnoreInput = false;
michael@0 290 var event = document.createEvent('Events');
michael@0 291 event.initEvent('ValueChange', true, true);
michael@0 292 this.inputField.dispatchEvent(event);
michael@0 293 return val;
michael@0 294 ]]></setter>
michael@0 295 </property>
michael@0 296
michael@0 297 <property name="focused" readonly="true"
michael@0 298 onget="return this.getAttribute('focused') == 'true';"/>
michael@0 299
michael@0 300 <!-- maximum number of rows to display at a time -->
michael@0 301 <property name="maxRows"
michael@0 302 onset="this.setAttribute('maxrows', val); return val;"
michael@0 303 onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
michael@0 304
michael@0 305 <!-- option to allow scrolling through the list via the tab key, rather than
michael@0 306 tab moving focus out of the textbox -->
michael@0 307 <property name="tabScrolling"
michael@0 308 onset="return this.setAttribute('tabscrolling', val); return val;"
michael@0 309 onget="return this.getAttribute('tabscrolling') == 'true';"/>
michael@0 310
michael@0 311 <!-- disable key navigation handling in the popup results -->
michael@0 312 <property name="disableKeyNavigation"
michael@0 313 onset="this.setAttribute('disablekeynavigation', val); return val;"
michael@0 314 onget="return this.getAttribute('disablekeynavigation') == 'true';"/>
michael@0 315
michael@0 316 <!-- option to completely ignore any blur events while
michael@0 317 searches are still going on. This is useful so that nothing
michael@0 318 gets autopicked if the window is required to lose focus for
michael@0 319 some reason (eg in LDAP autocomplete, another window may be
michael@0 320 brought up so that the user can enter a password to authenticate
michael@0 321 to an LDAP server). -->
michael@0 322 <property name="ignoreBlurWhileSearching"
michael@0 323 onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
michael@0 324 onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
michael@0 325
michael@0 326 <!-- option to highlight entries that don't have any matches -->
michael@0 327 <property name="highlightNonMatches"
michael@0 328 onset="this.setAttribute('highlightnonmatches', val); return val;"
michael@0 329 onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
michael@0 330
michael@0 331 <!-- =================== PRIVATE MEMBERS =================== -->
michael@0 332
michael@0 333 <!-- ::::::::::::: autocomplete controller ::::::::::::: -->
michael@0 334
michael@0 335 <method name="attachController">
michael@0 336 <body><![CDATA[
michael@0 337 this.mController.input = this;
michael@0 338 ]]></body>
michael@0 339 </method>
michael@0 340
michael@0 341 <method name="detachController">
michael@0 342 <body><![CDATA[
michael@0 343 try {
michael@0 344 if (this.mController.input == this)
michael@0 345 this.mController.input = null;
michael@0 346 } catch (ex) {
michael@0 347 // nothing really to do.
michael@0 348 }
michael@0 349 ]]></body>
michael@0 350 </method>
michael@0 351
michael@0 352 <!-- ::::::::::::: popup opening ::::::::::::: -->
michael@0 353
michael@0 354 <method name="openPopup">
michael@0 355 <body><![CDATA[
michael@0 356 this.popup.openAutocompletePopup(this, this);
michael@0 357 ]]></body>
michael@0 358 </method>
michael@0 359
michael@0 360 <method name="closePopup">
michael@0 361 <body><![CDATA[
michael@0 362 this.popup.setAttribute("consumeoutsideclicks", "false");
michael@0 363 this.popup.closePopup();
michael@0 364 ]]></body>
michael@0 365 </method>
michael@0 366
michael@0 367 <method name="showHistoryPopup">
michael@0 368 <body><![CDATA[
michael@0 369 // history dropmarker pushed state
michael@0 370 function cleanup(popup) {
michael@0 371 popup.removeEventListener("popupshowing", onShow, false);
michael@0 372 }
michael@0 373 function onShow(event) {
michael@0 374 var popup = event.target, input = popup.input;
michael@0 375 cleanup(popup);
michael@0 376 input.setAttribute("open", "true");
michael@0 377 function onHide() {
michael@0 378 input.removeAttribute("open");
michael@0 379 popup.setAttribute("consumeoutsideclicks", "false");
michael@0 380 popup.removeEventListener("popuphiding", onHide, false);
michael@0 381 }
michael@0 382 popup.addEventListener("popuphiding", onHide, false);
michael@0 383 }
michael@0 384 this.popup.addEventListener("popupshowing", onShow, false);
michael@0 385 setTimeout(cleanup, 1000, this.popup);
michael@0 386
michael@0 387 // Store our "normal" maxRows on the popup, so that it can reset the
michael@0 388 // value when the popup is hidden.
michael@0 389 this.popup._normalMaxRows = this.maxRows;
michael@0 390
michael@0 391 // Increase our maxRows temporarily, since we want the dropdown to
michael@0 392 // be bigger in this case. The popup's popupshowing/popuphiding
michael@0 393 // handlers will take care of resetting this.
michael@0 394 this.maxRows = this.maxDropMarkerRows;
michael@0 395
michael@0 396 // Ensure that we have focus.
michael@0 397 if (!this.focused)
michael@0 398 this.focus();
michael@0 399 this.popup.setAttribute("consumeoutsideclicks", "true");
michael@0 400 this.attachController();
michael@0 401 this.mController.startSearch("");
michael@0 402 ]]></body>
michael@0 403 </method>
michael@0 404
michael@0 405 <method name="toggleHistoryPopup">
michael@0 406 <body><![CDATA[
michael@0 407 if (!this.popup.popupOpen)
michael@0 408 this.showHistoryPopup();
michael@0 409 else
michael@0 410 this.closePopup();
michael@0 411 ]]></body>
michael@0 412 </method>
michael@0 413
michael@0 414 <!-- ::::::::::::: event dispatching ::::::::::::: -->
michael@0 415
michael@0 416 <method name="initEventHandler">
michael@0 417 <parameter name="aEventType"/>
michael@0 418 <body><![CDATA[
michael@0 419 let handlerString = this.getAttribute("on" + aEventType);
michael@0 420 if (handlerString) {
michael@0 421 return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
michael@0 422 }
michael@0 423 return null;
michael@0 424 ]]></body>
michael@0 425 </method>
michael@0 426
michael@0 427 <!-- ::::::::::::: key handling ::::::::::::: -->
michael@0 428
michael@0 429 <method name="onKeyPress">
michael@0 430 <parameter name="aEvent"/>
michael@0 431 <body><![CDATA[
michael@0 432 if (aEvent.target.localName != "textbox")
michael@0 433 return true; // Let child buttons of autocomplete take input
michael@0 434
michael@0 435 //XXXpch this is so bogus...
michael@0 436 if (aEvent.defaultPrevented)
michael@0 437 return false;
michael@0 438
michael@0 439 var cancel = false;
michael@0 440
michael@0 441 // Catch any keys that could potentially move the caret. Ctrl can be
michael@0 442 // used in combination with these keys on Windows and Linux; and Alt
michael@0 443 // can be used on OS X, so make sure the unused one isn't used.
michael@0 444 if (!this.disableKeyNavigation &&
michael@0 445 #ifdef XP_MACOSX
michael@0 446 !aEvent.ctrlKey) {
michael@0 447 #else
michael@0 448 !aEvent.altKey) {
michael@0 449 #endif
michael@0 450 switch (aEvent.keyCode) {
michael@0 451 case KeyEvent.DOM_VK_LEFT:
michael@0 452 case KeyEvent.DOM_VK_RIGHT:
michael@0 453 case KeyEvent.DOM_VK_HOME:
michael@0 454 cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
michael@0 455 break;
michael@0 456 }
michael@0 457 }
michael@0 458
michael@0 459 // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
michael@0 460 if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
michael@0 461 switch (aEvent.keyCode) {
michael@0 462 case KeyEvent.DOM_VK_TAB:
michael@0 463 if (this.tabScrolling && this.popup.popupOpen)
michael@0 464 cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
michael@0 465 KeyEvent.DOM_VK_UP :
michael@0 466 KeyEvent.DOM_VK_DOWN);
michael@0 467 else if (this.forceComplete && this.mController.matchCount >= 1)
michael@0 468 this.mController.handleTab();
michael@0 469 break;
michael@0 470 case KeyEvent.DOM_VK_UP:
michael@0 471 case KeyEvent.DOM_VK_DOWN:
michael@0 472 case KeyEvent.DOM_VK_PAGE_UP:
michael@0 473 case KeyEvent.DOM_VK_PAGE_DOWN:
michael@0 474 cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
michael@0 475 break;
michael@0 476 }
michael@0 477 }
michael@0 478
michael@0 479 // Handle keys we know aren't part of a shortcut, even with Alt or
michael@0 480 // Ctrl.
michael@0 481 switch (aEvent.keyCode) {
michael@0 482 case KeyEvent.DOM_VK_ESCAPE:
michael@0 483 cancel = this.mController.handleEscape();
michael@0 484 break;
michael@0 485 case KeyEvent.DOM_VK_RETURN:
michael@0 486 #ifdef XP_MACOSX
michael@0 487 // Prevent the default action, since it will beep on Mac
michael@0 488 if (aEvent.metaKey)
michael@0 489 aEvent.preventDefault();
michael@0 490 #endif
michael@0 491 this.mEnterEvent = aEvent;
michael@0 492 cancel = this.mController.handleEnter(false);
michael@0 493 break;
michael@0 494 case KeyEvent.DOM_VK_DELETE:
michael@0 495 #ifdef XP_MACOSX
michael@0 496 case KeyEvent.DOM_VK_BACK_SPACE:
michael@0 497 if (aEvent.shiftKey)
michael@0 498 #endif
michael@0 499 cancel = this.mController.handleDelete();
michael@0 500 break;
michael@0 501 case KeyEvent.DOM_VK_DOWN:
michael@0 502 case KeyEvent.DOM_VK_UP:
michael@0 503 if (aEvent.altKey)
michael@0 504 this.toggleHistoryPopup();
michael@0 505 break;
michael@0 506 #ifndef XP_MACOSX
michael@0 507 case KeyEvent.DOM_VK_F4:
michael@0 508 this.toggleHistoryPopup();
michael@0 509 break;
michael@0 510 #endif
michael@0 511 }
michael@0 512
michael@0 513 if (cancel) {
michael@0 514 aEvent.stopPropagation();
michael@0 515 aEvent.preventDefault();
michael@0 516 }
michael@0 517
michael@0 518 return true;
michael@0 519 ]]></body>
michael@0 520 </method>
michael@0 521
michael@0 522 <!-- ::::::::::::: miscellaneous ::::::::::::: -->
michael@0 523
michael@0 524 <method name="initSearchNames">
michael@0 525 <body><![CDATA[
michael@0 526 if (!this.mSearchNames) {
michael@0 527 var names = this.getAttribute("autocompletesearch");
michael@0 528 if (!names)
michael@0 529 this.mSearchNames = [];
michael@0 530 else
michael@0 531 this.mSearchNames = names.split(" ");
michael@0 532 }
michael@0 533 ]]></body>
michael@0 534 </method>
michael@0 535
michael@0 536 <method name="_focus">
michael@0 537 <!-- doesn't reset this.mController -->
michael@0 538 <body><![CDATA[
michael@0 539 this._dontBlur = true;
michael@0 540 this.focus();
michael@0 541 this._dontBlur = false;
michael@0 542 ]]></body>
michael@0 543 </method>
michael@0 544
michael@0 545 <method name="resetActionType">
michael@0 546 <body><![CDATA[
michael@0 547 if (this.mIgnoreInput)
michael@0 548 return;
michael@0 549 this.removeAttribute("actiontype");
michael@0 550 ]]></body>
michael@0 551 </method>
michael@0 552
michael@0 553 <field name="_valueIsPasted">false</field>
michael@0 554 <field name="_pasteController"><![CDATA[
michael@0 555 ({
michael@0 556 _autocomplete: this,
michael@0 557 _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
michael@0 558 supportsCommand: function(aCommand) aCommand == "cmd_paste",
michael@0 559 doCommand: function(aCommand) {
michael@0 560 this._autocomplete._valueIsPasted = true;
michael@0 561 this._autocomplete.editor.paste(this._kGlobalClipboard);
michael@0 562 this._autocomplete._valueIsPasted = false;
michael@0 563 },
michael@0 564 isCommandEnabled: function(aCommand) {
michael@0 565 return this._autocomplete.editor.isSelectionEditable &&
michael@0 566 this._autocomplete.editor.canPaste(this._kGlobalClipboard);
michael@0 567 },
michael@0 568 onEvent: function() {}
michael@0 569 })
michael@0 570 ]]></field>
michael@0 571 </implementation>
michael@0 572
michael@0 573 <handlers>
michael@0 574 <handler event="input"><![CDATA[
michael@0 575 if (!this.mIgnoreInput && this.mController.input == this) {
michael@0 576 this.valueIsTyped = true;
michael@0 577 this.mController.handleText();
michael@0 578 }
michael@0 579 this.resetActionType();
michael@0 580 ]]></handler>
michael@0 581
michael@0 582 <handler event="keypress" phase="capturing"
michael@0 583 action="return this.onKeyPress(event);"/>
michael@0 584
michael@0 585 <handler event="compositionstart" phase="capturing"
michael@0 586 action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
michael@0 587
michael@0 588 <handler event="compositionend" phase="capturing"
michael@0 589 action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
michael@0 590
michael@0 591 <handler event="focus" phase="capturing"
michael@0 592 action="this.attachController();"/>
michael@0 593
michael@0 594 <handler event="blur" phase="capturing"
michael@0 595 action="if (!this._dontBlur) this.detachController();"/>
michael@0 596 </handlers>
michael@0 597 </binding>
michael@0 598
michael@0 599 <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
michael@0 600 <resources>
michael@0 601 <stylesheet src="chrome://global/skin/tree.css"/>
michael@0 602 <stylesheet src="chrome://global/skin/autocomplete.css"/>
michael@0 603 </resources>
michael@0 604
michael@0 605 <content ignorekeys="true" level="top" consumeoutsideclicks="false">
michael@0 606 <xul:tree anonid="tree" class="autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
michael@0 607 <xul:treecols anonid="treecols">
michael@0 608 <xul:treecol id="treecolAutoCompleteValue" class="autocomplete-treecol" flex="1" overflow="true"/>
michael@0 609 </xul:treecols>
michael@0 610 <xul:treechildren class="autocomplete-treebody"/>
michael@0 611 </xul:tree>
michael@0 612 </content>
michael@0 613
michael@0 614 <implementation>
michael@0 615 <field name="mShowCommentColumn">false</field>
michael@0 616 <field name="mShowImageColumn">false</field>
michael@0 617
michael@0 618 <property name="showCommentColumn"
michael@0 619 onget="return this.mShowCommentColumn;">
michael@0 620 <setter>
michael@0 621 <![CDATA[
michael@0 622 if (!val && this.mShowCommentColumn) {
michael@0 623 // reset the flex on the value column and remove the comment column
michael@0 624 document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1);
michael@0 625 this.removeColumn("treecolAutoCompleteComment");
michael@0 626 } else if (val && !this.mShowCommentColumn) {
michael@0 627 // reset the flex on the value column and add the comment column
michael@0 628 document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2);
michael@0 629 this.addColumn({id: "treecolAutoCompleteComment", flex: 1});
michael@0 630 }
michael@0 631 this.mShowCommentColumn = val;
michael@0 632 return val;
michael@0 633 ]]>
michael@0 634 </setter>
michael@0 635 </property>
michael@0 636
michael@0 637 <property name="showImageColumn"
michael@0 638 onget="return this.mShowImageColumn;">
michael@0 639 <setter>
michael@0 640 <![CDATA[
michael@0 641 if (!val && this.mShowImageColumn) {
michael@0 642 // remove the image column
michael@0 643 this.removeColumn("treecolAutoCompleteImage");
michael@0 644 } else if (val && !this.mShowImageColumn) {
michael@0 645 // add the image column
michael@0 646 this.addColumn({id: "treecolAutoCompleteImage", flex: 1});
michael@0 647 }
michael@0 648 this.mShowImageColumn = val;
michael@0 649 return val;
michael@0 650 ]]>
michael@0 651 </setter>
michael@0 652 </property>
michael@0 653
michael@0 654
michael@0 655 <method name="addColumn">
michael@0 656 <parameter name="aAttrs"/>
michael@0 657 <body>
michael@0 658 <![CDATA[
michael@0 659 var col = document.createElement("treecol");
michael@0 660 col.setAttribute("class", "autocomplete-treecol");
michael@0 661 for (var name in aAttrs)
michael@0 662 col.setAttribute(name, aAttrs[name]);
michael@0 663 this.treecols.appendChild(col);
michael@0 664 return col;
michael@0 665 ]]>
michael@0 666 </body>
michael@0 667 </method>
michael@0 668
michael@0 669 <method name="removeColumn">
michael@0 670 <parameter name="aColId"/>
michael@0 671 <body>
michael@0 672 <![CDATA[
michael@0 673 return this.treecols.removeChild(document.getElementById(aColId));
michael@0 674 ]]>
michael@0 675 </body>
michael@0 676 </method>
michael@0 677
michael@0 678 <property name="selectedIndex"
michael@0 679 onget="return this.tree.currentIndex;">
michael@0 680 <setter>
michael@0 681 <![CDATA[
michael@0 682 this.tree.view.selection.select(val);
michael@0 683 if (this.tree.treeBoxObject.height > 0)
michael@0 684 this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val);
michael@0 685 // Fire select event on xul:tree so that accessibility API
michael@0 686 // support layer can fire appropriate accessibility events.
michael@0 687 var event = document.createEvent('Events');
michael@0 688 event.initEvent("select", true, true);
michael@0 689 this.tree.dispatchEvent(event);
michael@0 690 return val;
michael@0 691 ]]></setter>
michael@0 692 </property>
michael@0 693
michael@0 694 <method name="adjustHeight">
michael@0 695 <body>
michael@0 696 <![CDATA[
michael@0 697 // detect the desired height of the tree
michael@0 698 var bx = this.tree.treeBoxObject;
michael@0 699 var view = this.tree.view;
michael@0 700 if (!view)
michael@0 701 return;
michael@0 702 var rows = this.maxRows;
michael@0 703 if (!view.rowCount || (rows && view.rowCount < rows))
michael@0 704 rows = view.rowCount;
michael@0 705
michael@0 706 var height = rows * bx.rowHeight;
michael@0 707
michael@0 708 if (height == 0)
michael@0 709 this.tree.setAttribute("collapsed", "true");
michael@0 710 else {
michael@0 711 if (this.tree.hasAttribute("collapsed"))
michael@0 712 this.tree.removeAttribute("collapsed");
michael@0 713
michael@0 714 this.tree.setAttribute("height", height);
michael@0 715 }
michael@0 716 this.tree.setAttribute("hidescrollbar", view.rowCount <= rows);
michael@0 717 ]]>
michael@0 718 </body>
michael@0 719 </method>
michael@0 720
michael@0 721 <method name="openAutocompletePopup">
michael@0 722 <parameter name="aInput"/>
michael@0 723 <parameter name="aElement"/>
michael@0 724 <body><![CDATA[
michael@0 725 // until we have "baseBinding", (see bug #373652) this allows
michael@0 726 // us to override openAutocompletePopup(), but still call
michael@0 727 // the method on the base class
michael@0 728 this._openAutocompletePopup(aInput, aElement);
michael@0 729 ]]></body>
michael@0 730 </method>
michael@0 731
michael@0 732 <method name="_openAutocompletePopup">
michael@0 733 <parameter name="aInput"/>
michael@0 734 <parameter name="aElement"/>
michael@0 735 <body><![CDATA[
michael@0 736 if (!this.mPopupOpen) {
michael@0 737 this.mInput = aInput;
michael@0 738 this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
michael@0 739 this.invalidate();
michael@0 740
michael@0 741 this.showCommentColumn = this.mInput.showCommentColumn;
michael@0 742 this.showImageColumn = this.mInput.showImageColumn;
michael@0 743
michael@0 744 var rect = aElement.getBoundingClientRect();
michael@0 745 var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
michael@0 746 .getInterface(Components.interfaces.nsIWebNavigation);
michael@0 747 var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell);
michael@0 748 var docViewer = docShell.contentViewer.QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
michael@0 749 var width = (rect.right - rect.left) * docViewer.fullZoom;
michael@0 750 this.setAttribute("width", width > 100 ? width : 100);
michael@0 751
michael@0 752 // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840
michael@0 753 var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction;
michael@0 754 this.style.direction = popupDirection;
michael@0 755
michael@0 756 this.openPopup(aElement, "after_start", 0, 0, false, false);
michael@0 757 }
michael@0 758 ]]></body>
michael@0 759 </method>
michael@0 760
michael@0 761 <method name="invalidate">
michael@0 762 <body><![CDATA[
michael@0 763 this.adjustHeight();
michael@0 764 this.tree.treeBoxObject.invalidate();
michael@0 765 ]]></body>
michael@0 766 </method>
michael@0 767
michael@0 768 <method name="selectBy">
michael@0 769 <parameter name="aReverse"/>
michael@0 770 <parameter name="aPage"/>
michael@0 771 <body><![CDATA[
michael@0 772 try {
michael@0 773 var amount = aPage ? 5 : 1;
michael@0 774 this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1);
michael@0 775 if (this.selectedIndex == -1) {
michael@0 776 this.input._focus();
michael@0 777 }
michael@0 778 } catch (ex) {
michael@0 779 // do nothing - occasionally timer-related js errors happen here
michael@0 780 // e.g. "this.selectedIndex has no properties", when you type fast and hit a
michael@0 781 // navigation key before this popup has opened
michael@0 782 }
michael@0 783 ]]></body>
michael@0 784 </method>
michael@0 785
michael@0 786 <!-- =================== PUBLIC MEMBERS =================== -->
michael@0 787
michael@0 788 <field name="tree">
michael@0 789 document.getAnonymousElementByAttribute(this, "anonid", "tree");
michael@0 790 </field>
michael@0 791
michael@0 792 <field name="treecols">
michael@0 793 document.getAnonymousElementByAttribute(this, "anonid", "treecols");
michael@0 794 </field>
michael@0 795
michael@0 796 <property name="view"
michael@0 797 onget="return this.mView;">
michael@0 798 <setter><![CDATA[
michael@0 799 // We must do this by hand because the tree binding may not be ready yet
michael@0 800 this.mView = val;
michael@0 801 var bx = this.tree.boxObject;
michael@0 802 bx = bx.QueryInterface(Components.interfaces.nsITreeBoxObject);
michael@0 803 bx.view = val;
michael@0 804 ]]></setter>
michael@0 805 </property>
michael@0 806
michael@0 807 </implementation>
michael@0 808 </binding>
michael@0 809
michael@0 810 <binding id="autocomplete-base-popup" role="none"
michael@0 811 extends="chrome://global/content/bindings/popup.xml#popup">
michael@0 812 <implementation implements="nsIAutoCompletePopup">
michael@0 813 <field name="mInput">null</field>
michael@0 814 <field name="mPopupOpen">false</field>
michael@0 815
michael@0 816 <!-- =================== nsIAutoCompletePopup =================== -->
michael@0 817
michael@0 818 <property name="input" readonly="true"
michael@0 819 onget="return this.mInput"/>
michael@0 820
michael@0 821 <property name="overrideValue" readonly="true"
michael@0 822 onget="return null;"/>
michael@0 823
michael@0 824 <property name="popupOpen" readonly="true"
michael@0 825 onget="return this.mPopupOpen;"/>
michael@0 826
michael@0 827 <method name="closePopup">
michael@0 828 <body>
michael@0 829 <![CDATA[
michael@0 830 if (this.mPopupOpen) {
michael@0 831 this.hidePopup();
michael@0 832 this.removeAttribute("width");
michael@0 833 }
michael@0 834 ]]>
michael@0 835 </body>
michael@0 836 </method>
michael@0 837
michael@0 838 <!-- This is the default number of rows that we give the autocomplete
michael@0 839 popup when the textbox doesn't have a "maxrows" attribute
michael@0 840 for us to use. -->
michael@0 841 <field name="defaultMaxRows" readonly="true">6</field>
michael@0 842
michael@0 843 <!-- In some cases (e.g. when the input's dropmarker button is clicked),
michael@0 844 the input wants to display a popup with more rows. In that case, it
michael@0 845 should increase its maxRows property and store the "normal" maxRows
michael@0 846 in this field. When the popup is hidden, we restore the input's
michael@0 847 maxRows to the value stored in this field.
michael@0 848
michael@0 849 This field is set to -1 between uses so that we can tell when it's
michael@0 850 been set by the input and when we need to set it in the popupshowing
michael@0 851 handler. -->
michael@0 852 <field name="_normalMaxRows">-1</field>
michael@0 853
michael@0 854 <property name="maxRows" readonly="true">
michael@0 855 <getter>
michael@0 856 <![CDATA[
michael@0 857 return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
michael@0 858 ]]>
michael@0 859 </getter>
michael@0 860 </property>
michael@0 861
michael@0 862 <method name="getNextIndex">
michael@0 863 <parameter name="aReverse"/>
michael@0 864 <parameter name="aAmount"/>
michael@0 865 <parameter name="aIndex"/>
michael@0 866 <parameter name="aMaxRow"/>
michael@0 867 <body><![CDATA[
michael@0 868 if (aMaxRow < 0)
michael@0 869 return -1;
michael@0 870
michael@0 871 var newIdx = aIndex + (aReverse?-1:1)*aAmount;
michael@0 872 if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow)
michael@0 873 newIdx = aMaxRow;
michael@0 874 else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0)
michael@0 875 newIdx = 0;
michael@0 876
michael@0 877 if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
michael@0 878 aIndex = -1;
michael@0 879 else
michael@0 880 aIndex = newIdx;
michael@0 881
michael@0 882 return aIndex;
michael@0 883 ]]></body>
michael@0 884 </method>
michael@0 885
michael@0 886 <method name="onPopupClick">
michael@0 887 <parameter name="aEvent"/>
michael@0 888 <body><![CDATA[
michael@0 889 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
michael@0 890 controller.handleEnter(true);
michael@0 891 ]]></body>
michael@0 892 </method>
michael@0 893 </implementation>
michael@0 894
michael@0 895 <handlers>
michael@0 896 <handler event="popupshowing"><![CDATA[
michael@0 897 // If normalMaxRows wasn't already set by the input, then set it here
michael@0 898 // so that we restore the correct number when the popup is hidden.
michael@0 899 if (this._normalMaxRows < 0)
michael@0 900 this._normalMaxRows = this.mInput.maxRows;
michael@0 901
michael@0 902 this.mPopupOpen = true;
michael@0 903 ]]></handler>
michael@0 904
michael@0 905 <handler event="popuphiding"><![CDATA[
michael@0 906 var isListActive = true;
michael@0 907 if (this.selectedIndex == -1)
michael@0 908 isListActive = false;
michael@0 909 var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
michael@0 910 controller.stopSearch();
michael@0 911
michael@0 912 this.mPopupOpen = false;
michael@0 913
michael@0 914 // Reset the maxRows property to the cached "normal" value, and reset
michael@0 915 // _normalMaxRows so that we can detect whether it was set by the input
michael@0 916 // when the popupshowing handler runs.
michael@0 917 this.mInput.maxRows = this._normalMaxRows;
michael@0 918 this._normalMaxRows = -1;
michael@0 919 // If the list was being navigated and then closed, make sure
michael@0 920 // we fire accessible focus event back to textbox
michael@0 921 if (isListActive) {
michael@0 922 this.mInput.mIgnoreFocus = true;
michael@0 923 this.mInput._focus();
michael@0 924 this.mInput.mIgnoreFocus = false;
michael@0 925 }
michael@0 926 ]]></handler>
michael@0 927 </handlers>
michael@0 928 </binding>
michael@0 929
michael@0 930 <binding id="autocomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-base-popup">
michael@0 931 <resources>
michael@0 932 <stylesheet src="chrome://global/skin/autocomplete.css"/>
michael@0 933 </resources>
michael@0 934
michael@0 935 <content ignorekeys="true" level="top" consumeoutsideclicks="false">
michael@0 936 <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox" flex="1"/>
michael@0 937 <xul:hbox>
michael@0 938 <children/>
michael@0 939 </xul:hbox>
michael@0 940 </content>
michael@0 941
michael@0 942 <implementation implements="nsIAutoCompletePopup">
michael@0 943 <field name="_currentIndex">0</field>
michael@0 944 <field name="_rowHeight">0</field>
michael@0 945
michael@0 946 <!-- =================== nsIAutoCompletePopup =================== -->
michael@0 947
michael@0 948 <property name="selectedIndex"
michael@0 949 onget="return this.richlistbox.selectedIndex;">
michael@0 950 <setter>
michael@0 951 <![CDATA[
michael@0 952 this.richlistbox.selectedIndex = val;
michael@0 953
michael@0 954 // when clearing the selection (val == -1, so selectedItem will be
michael@0 955 // null), we want to scroll back to the top. see bug #406194
michael@0 956 this.richlistbox.ensureElementIsVisible(
michael@0 957 this.richlistbox.selectedItem || this.richlistbox.firstChild);
michael@0 958
michael@0 959 return val;
michael@0 960 ]]>
michael@0 961 </setter>
michael@0 962 </property>
michael@0 963
michael@0 964 <method name="openAutocompletePopup">
michael@0 965 <parameter name="aInput"/>
michael@0 966 <parameter name="aElement"/>
michael@0 967 <body>
michael@0 968 <![CDATA[
michael@0 969 // until we have "baseBinding", (see bug #373652) this allows
michael@0 970 // us to override openAutocompletePopup(), but still call
michael@0 971 // the method on the base class
michael@0 972 this._openAutocompletePopup(aInput, aElement);
michael@0 973 ]]>
michael@0 974 </body>
michael@0 975 </method>
michael@0 976
michael@0 977 <method name="_openAutocompletePopup">
michael@0 978 <parameter name="aInput"/>
michael@0 979 <parameter name="aElement"/>
michael@0 980 <body>
michael@0 981 <![CDATA[
michael@0 982 if (!this.mPopupOpen) {
michael@0 983 this.mInput = aInput;
michael@0 984 // clear any previous selection, see bugs 400671 and 488357
michael@0 985 this.selectedIndex = -1;
michael@0 986
michael@0 987 var width = aElement.getBoundingClientRect().width;
michael@0 988 this.setAttribute("width", width > 100 ? width : 100);
michael@0 989 // invalidate() depends on the width attribute
michael@0 990 this._invalidate();
michael@0 991
michael@0 992 this.openPopup(aElement, "after_start", 0, 0, false, false);
michael@0 993 }
michael@0 994 ]]>
michael@0 995 </body>
michael@0 996 </method>
michael@0 997
michael@0 998 <method name="invalidate">
michael@0 999 <body>
michael@0 1000 <![CDATA[
michael@0 1001 // Don't bother doing work if we're not even showing
michael@0 1002 if (!this.mPopupOpen)
michael@0 1003 return;
michael@0 1004
michael@0 1005 this._invalidate();
michael@0 1006 ]]>
michael@0 1007 </body>
michael@0 1008 </method>
michael@0 1009
michael@0 1010 <method name="_invalidate">
michael@0 1011 <body>
michael@0 1012 <![CDATA[
michael@0 1013 if (!this.hasAttribute("height")) {
michael@0 1014 // collapsed if no matches
michael@0 1015 this.richlistbox.collapsed = (this._matchCount == 0);
michael@0 1016
michael@0 1017 // Dynamically update height until richlistbox.rows works (bug 401939)
michael@0 1018 // Adjust the height immediately and after the row contents update
michael@0 1019 this.adjustHeight();
michael@0 1020 setTimeout(function(self) self.adjustHeight(), 0, this);
michael@0 1021 }
michael@0 1022
michael@0 1023 // make sure to collapse any existing richlistitems
michael@0 1024 // that aren't going to be used
michael@0 1025 var existingItemsCount = this.richlistbox.childNodes.length;
michael@0 1026 for (var i = this._matchCount; i < existingItemsCount; i++)
michael@0 1027 this.richlistbox.childNodes[i].collapsed = true;
michael@0 1028
michael@0 1029 this._currentIndex = 0;
michael@0 1030 this._appendCurrentResult();
michael@0 1031 ]]>
michael@0 1032 </body>
michael@0 1033 </method>
michael@0 1034
michael@0 1035 <property name="maxResults" readonly="true">
michael@0 1036 <getter>
michael@0 1037 <![CDATA[
michael@0 1038 // this is how many richlistitems will be kept around
michael@0 1039 // (note, this getter may be overridden)
michael@0 1040 return 20;
michael@0 1041 ]]>
michael@0 1042 </getter>
michael@0 1043 </property>
michael@0 1044
michael@0 1045 <property name="_matchCount" readonly="true">
michael@0 1046 <getter>
michael@0 1047 <![CDATA[
michael@0 1048 return Math.min(this.mInput.controller.matchCount, this.maxResults);
michael@0 1049 ]]>
michael@0 1050 </getter>
michael@0 1051 </property>
michael@0 1052
michael@0 1053 <method name="adjustHeight">
michael@0 1054 <body>
michael@0 1055 <![CDATA[
michael@0 1056 // Figure out how many rows to show
michael@0 1057 let rows = this.richlistbox.childNodes;
michael@0 1058 let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
michael@0 1059
michael@0 1060 // Default the height to 0 if we have no rows to show
michael@0 1061 let height = 0;
michael@0 1062 if (numRows) {
michael@0 1063 if (!this._rowHeight) {
michael@0 1064 let firstRowRect = rows[0].getBoundingClientRect();
michael@0 1065 this._rowHeight = firstRowRect.height;
michael@0 1066 }
michael@0 1067
michael@0 1068 // Calculate the height to have the first row to last row shown
michael@0 1069 height = this._rowHeight * numRows;
michael@0 1070 }
michael@0 1071
michael@0 1072 // Only update the height if we have a non-zero height and if it
michael@0 1073 // changed (the richlistbox is collapsed if there are no results)
michael@0 1074 if (height && height != this.richlistbox.height)
michael@0 1075 this.richlistbox.height = height;
michael@0 1076 ]]>
michael@0 1077 </body>
michael@0 1078 </method>
michael@0 1079
michael@0 1080 <method name="_appendCurrentResult">
michael@0 1081 <body>
michael@0 1082 <![CDATA[
michael@0 1083 var controller = this.mInput.controller;
michael@0 1084 var matchCount = this._matchCount;
michael@0 1085 var existingItemsCount = this.richlistbox.childNodes.length;
michael@0 1086
michael@0 1087 // Process maxRows per chunk to improve performance and user experience
michael@0 1088 for (let i = 0; i < this.maxRows; i++) {
michael@0 1089 if (this._currentIndex >= matchCount)
michael@0 1090 return;
michael@0 1091
michael@0 1092 var item;
michael@0 1093
michael@0 1094 // trim the leading/trailing whitespace
michael@0 1095 var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
michael@0 1096
michael@0 1097 // Unescape the URI spec for showing as an entry in the popup
michael@0 1098 let url = Components.classes["@mozilla.org/intl/texttosuburi;1"].
michael@0 1099 getService(Components.interfaces.nsITextToSubURI).
michael@0 1100 unEscapeURIForUI("UTF-8", controller.getValueAt(this._currentIndex));
michael@0 1101
michael@0 1102 if (typeof this.input.trimValue == "function")
michael@0 1103 url = this.input.trimValue(url);
michael@0 1104
michael@0 1105 if (this._currentIndex < existingItemsCount) {
michael@0 1106 // re-use the existing item
michael@0 1107 item = this.richlistbox.childNodes[this._currentIndex];
michael@0 1108
michael@0 1109 // Completely re-use the existing richlistitem if it's the same
michael@0 1110 if (item.getAttribute("text") == trimmedSearchString &&
michael@0 1111 item.getAttribute("url") == url) {
michael@0 1112 item.collapsed = false;
michael@0 1113 this._currentIndex++;
michael@0 1114 continue;
michael@0 1115 }
michael@0 1116 }
michael@0 1117 else {
michael@0 1118 // need to create a new item
michael@0 1119 item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
michael@0 1120 }
michael@0 1121
michael@0 1122 // set these attributes before we set the class
michael@0 1123 // so that we can use them from the constructor
michael@0 1124 item.setAttribute("image", controller.getImageAt(this._currentIndex));
michael@0 1125 item.setAttribute("url", url);
michael@0 1126 item.setAttribute("title", controller.getCommentAt(this._currentIndex));
michael@0 1127 item.setAttribute("type", controller.getStyleAt(this._currentIndex));
michael@0 1128 item.setAttribute("text", trimmedSearchString);
michael@0 1129
michael@0 1130 if (this._currentIndex < existingItemsCount) {
michael@0 1131 // re-use the existing item
michael@0 1132 item._adjustAcItem();
michael@0 1133 item.collapsed = false;
michael@0 1134 }
michael@0 1135 else {
michael@0 1136 // set the class at the end so we can use the attributes
michael@0 1137 // in the xbl constructor
michael@0 1138 item.className = "autocomplete-richlistitem";
michael@0 1139 this.richlistbox.appendChild(item);
michael@0 1140 }
michael@0 1141
michael@0 1142 this._currentIndex++;
michael@0 1143 }
michael@0 1144
michael@0 1145 // yield after each batch of items so that typing the url bar is responsive
michael@0 1146 setTimeout(function (self) { self._appendCurrentResult(); }, 0, this);
michael@0 1147 ]]>
michael@0 1148 </body>
michael@0 1149 </method>
michael@0 1150
michael@0 1151 <method name="selectBy">
michael@0 1152 <parameter name="aReverse"/>
michael@0 1153 <parameter name="aPage"/>
michael@0 1154 <body>
michael@0 1155 <![CDATA[
michael@0 1156 try {
michael@0 1157 var amount = aPage ? 5 : 1;
michael@0 1158
michael@0 1159 // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
michael@0 1160 this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1);
michael@0 1161 if (this.selectedIndex == -1) {
michael@0 1162 this.input._focus();
michael@0 1163 }
michael@0 1164 } catch (ex) {
michael@0 1165 // do nothing - occasionally timer-related js errors happen here
michael@0 1166 // e.g. "this.selectedIndex has no properties", when you type fast and hit a
michael@0 1167 // navigation key before this popup has opened
michael@0 1168 }
michael@0 1169 ]]>
michael@0 1170 </body>
michael@0 1171 </method>
michael@0 1172
michael@0 1173 <field name="richlistbox">
michael@0 1174 document.getAnonymousElementByAttribute(this, "anonid", "richlistbox");
michael@0 1175 </field>
michael@0 1176
michael@0 1177 <property name="view"
michael@0 1178 onget="return this.mInput.controller;"
michael@0 1179 onset="return val;"/>
michael@0 1180
michael@0 1181 </implementation>
michael@0 1182 </binding>
michael@0 1183
michael@0 1184 <binding id="autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
michael@0 1185 <content>
michael@0 1186 <xul:hbox align="center" class="ac-title-box">
michael@0 1187 <xul:image xbl:inherits="src=image" class="ac-site-icon"/>
michael@0 1188 <xul:hbox anonid="title-box" class="ac-title" flex="1"
michael@0 1189 onunderflow="_doUnderflow('_title');"
michael@0 1190 onoverflow="_doOverflow('_title');">
michael@0 1191 <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
michael@0 1192 </xul:hbox>
michael@0 1193 <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected"
michael@0 1194 class="ac-ellipsis-after ac-comment"/>
michael@0 1195 <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true">
michael@0 1196 <xul:image class="ac-result-type-tag"/>
michael@0 1197 <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/>
michael@0 1198 <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
michael@0 1199 </xul:hbox>
michael@0 1200 <xul:image anonid="type-image" class="ac-type-icon"/>
michael@0 1201 </xul:hbox>
michael@0 1202 <xul:hbox align="center" class="ac-url-box">
michael@0 1203 <xul:spacer class="ac-site-icon"/>
michael@0 1204 <xul:image class="ac-action-icon"/>
michael@0 1205 <xul:hbox anonid="url-box" class="ac-url" flex="1"
michael@0 1206 onunderflow="_doUnderflow('_url');"
michael@0 1207 onoverflow="_doOverflow('_url');">
michael@0 1208 <xul:description anonid="url" class="ac-normal-text ac-url-text"
michael@0 1209 xbl:inherits="selected type"/>
michael@0 1210 <xul:description anonid="action" class="ac-normal-text ac-action-text"
michael@0 1211 xbl:inherits="selected type"/>
michael@0 1212 </xul:hbox>
michael@0 1213 <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected"
michael@0 1214 class="ac-ellipsis-after ac-url-text"/>
michael@0 1215 <xul:spacer class="ac-type-icon"/>
michael@0 1216 </xul:hbox>
michael@0 1217 </content>
michael@0 1218 <implementation implements="nsIDOMXULSelectControlItemElement">
michael@0 1219 <constructor>
michael@0 1220 <![CDATA[
michael@0 1221 let ellipsis = "\u2026";
michael@0 1222 try {
michael@0 1223 ellipsis = Components.classes["@mozilla.org/preferences-service;1"].
michael@0 1224 getService(Components.interfaces.nsIPrefBranch).
michael@0 1225 getComplexValue("intl.ellipsis",
michael@0 1226 Components.interfaces.nsIPrefLocalizedString).data;
michael@0 1227 } catch (ex) {
michael@0 1228 // Do nothing.. we already have a default
michael@0 1229 }
michael@0 1230
michael@0 1231 this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis");
michael@0 1232 this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis");
michael@0 1233
michael@0 1234 this._urlOverflowEllipsis.value = ellipsis;
michael@0 1235 this._titleOverflowEllipsis.value = ellipsis;
michael@0 1236
michael@0 1237 this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
michael@0 1238
michael@0 1239 this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box");
michael@0 1240 this._url = document.getAnonymousElementByAttribute(this, "anonid", "url");
michael@0 1241 this._action = document.getAnonymousElementByAttribute(this, "anonid", "action");
michael@0 1242
michael@0 1243 this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box");
michael@0 1244 this._title = document.getAnonymousElementByAttribute(this, "anonid", "title");
michael@0 1245
michael@0 1246 this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box");
michael@0 1247 this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra");
michael@0 1248
michael@0 1249 this._adjustAcItem();
michael@0 1250 ]]>
michael@0 1251 </constructor>
michael@0 1252
michael@0 1253 <property name="label" readonly="true">
michael@0 1254 <getter>
michael@0 1255 <![CDATA[
michael@0 1256 var title = this.getAttribute("title");
michael@0 1257 var url = this.getAttribute("url");
michael@0 1258 var panel = this.parentNode.parentNode;
michael@0 1259
michael@0 1260 // allow consumers that have extended popups to override
michael@0 1261 // the label values for the richlistitems
michael@0 1262 if (panel.createResultLabel)
michael@0 1263 return panel.createResultLabel(title, url, this.getAttribute("type"));
michael@0 1264
michael@0 1265 // aType (ex: "ac-result-type-<aType>") is related to the class of the image,
michael@0 1266 // and is not "visible" text so don't use it for the label (for accessibility).
michael@0 1267 return title + " " + url;
michael@0 1268 ]]>
michael@0 1269 </getter>
michael@0 1270 </property>
michael@0 1271
michael@0 1272 <field name="_boundaryCutoff">null</field>
michael@0 1273
michael@0 1274 <property name="boundaryCutoff" readonly="true">
michael@0 1275 <getter>
michael@0 1276 <![CDATA[
michael@0 1277 if (!this._boundaryCutoff) {
michael@0 1278 this._boundaryCutoff =
michael@0 1279 Components.classes["@mozilla.org/preferences-service;1"].
michael@0 1280 getService(Components.interfaces.nsIPrefBranch).
michael@0 1281 getIntPref("toolkit.autocomplete.richBoundaryCutoff");
michael@0 1282 }
michael@0 1283 return this._boundaryCutoff;
michael@0 1284 ]]>
michael@0 1285 </getter>
michael@0 1286 </property>
michael@0 1287
michael@0 1288 <method name="_getBoundaryIndices">
michael@0 1289 <parameter name="aText"/>
michael@0 1290 <parameter name="aSearchTokens"/>
michael@0 1291 <body>
michael@0 1292 <![CDATA[
michael@0 1293 // Short circuit for empty search ([""] == "")
michael@0 1294 if (aSearchTokens == "")
michael@0 1295 return [0, aText.length];
michael@0 1296
michael@0 1297 // Find which regions of text match the search terms
michael@0 1298 let regions = [];
michael@0 1299 for each (let search in aSearchTokens) {
michael@0 1300 let matchIndex;
michael@0 1301 let startIndex = 0;
michael@0 1302 let searchLen = search.length;
michael@0 1303
michael@0 1304 // Find all matches of the search terms, but stop early for perf
michael@0 1305 let lowerText = aText.toLowerCase().substr(0, this.boundaryCutoff);
michael@0 1306 while ((matchIndex = lowerText.indexOf(search, startIndex)) >= 0) {
michael@0 1307 // Start the next search from where this one finished
michael@0 1308 startIndex = matchIndex + searchLen;
michael@0 1309 regions.push([matchIndex, startIndex]);
michael@0 1310 }
michael@0 1311 }
michael@0 1312
michael@0 1313 // Sort the regions by start position then end position
michael@0 1314 regions = regions.sort(function(a, b) let (start = a[0] - b[0])
michael@0 1315 start == 0 ? a[1] - b[1] : start);
michael@0 1316
michael@0 1317 // Generate the boundary indices from each region
michael@0 1318 let start = 0;
michael@0 1319 let end = 0;
michael@0 1320 let boundaries = [];
michael@0 1321 let len = regions.length;
michael@0 1322 for (let i = 0; i < len; i++) {
michael@0 1323 // We have a new boundary if the start of the next is past the end
michael@0 1324 let region = regions[i];
michael@0 1325 if (region[0] > end) {
michael@0 1326 // First index is the beginning of match
michael@0 1327 boundaries.push(start);
michael@0 1328 // Second index is the beginning of non-match
michael@0 1329 boundaries.push(end);
michael@0 1330
michael@0 1331 // Track the new region now that we've stored the previous one
michael@0 1332 start = region[0];
michael@0 1333 }
michael@0 1334
michael@0 1335 // Push back the end index for the current or new region
michael@0 1336 end = Math.max(end, region[1]);
michael@0 1337 }
michael@0 1338
michael@0 1339 // Add the last region
michael@0 1340 boundaries.push(start);
michael@0 1341 boundaries.push(end);
michael@0 1342
michael@0 1343 // Put on the end boundary if necessary
michael@0 1344 if (end < aText.length)
michael@0 1345 boundaries.push(aText.length);
michael@0 1346
michael@0 1347 // Skip the first item because it's always 0
michael@0 1348 return boundaries.slice(1);
michael@0 1349 ]]>
michael@0 1350 </body>
michael@0 1351 </method>
michael@0 1352
michael@0 1353 <method name="_getSearchTokens">
michael@0 1354 <parameter name="aSearch"/>
michael@0 1355 <body>
michael@0 1356 <![CDATA[
michael@0 1357 let search = aSearch.toLowerCase();
michael@0 1358 return search.split(/\s+/);
michael@0 1359 ]]>
michael@0 1360 </body>
michael@0 1361 </method>
michael@0 1362
michael@0 1363 <method name="_setUpDescription">
michael@0 1364 <parameter name="aDescriptionElement"/>
michael@0 1365 <parameter name="aText"/>
michael@0 1366 <parameter name="aNoEmphasis"/>
michael@0 1367 <body>
michael@0 1368 <![CDATA[
michael@0 1369 // Get rid of all previous text
michael@0 1370 while (aDescriptionElement.hasChildNodes())
michael@0 1371 aDescriptionElement.removeChild(aDescriptionElement.firstChild);
michael@0 1372
michael@0 1373 // If aNoEmphasis is specified, don't add any emphasis
michael@0 1374 if (aNoEmphasis) {
michael@0 1375 aDescriptionElement.appendChild(document.createTextNode(aText));
michael@0 1376 return;
michael@0 1377 }
michael@0 1378
michael@0 1379 // Get the indices that separate match and non-match text
michael@0 1380 let search = this.getAttribute("text");
michael@0 1381 let tokens = this._getSearchTokens(search);
michael@0 1382 let indices = this._getBoundaryIndices(aText, tokens);
michael@0 1383
michael@0 1384 let next;
michael@0 1385 let start = 0;
michael@0 1386 let len = indices.length;
michael@0 1387 // Even indexed boundaries are matches, so skip the 0th if it's empty
michael@0 1388 for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
michael@0 1389 next = indices[i];
michael@0 1390 let text = aText.substr(start, next - start);
michael@0 1391 start = next;
michael@0 1392
michael@0 1393 if (i % 2 == 0) {
michael@0 1394 // Emphasize the text for even indices
michael@0 1395 let span = aDescriptionElement.appendChild(
michael@0 1396 document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
michael@0 1397 span.className = "ac-emphasize-text";
michael@0 1398 span.textContent = text;
michael@0 1399 } else {
michael@0 1400 // Otherwise, it's plain text
michael@0 1401 aDescriptionElement.appendChild(document.createTextNode(text));
michael@0 1402 }
michael@0 1403 }
michael@0 1404 ]]>
michael@0 1405 </body>
michael@0 1406 </method>
michael@0 1407
michael@0 1408 <method name="_adjustAcItem">
michael@0 1409 <body>
michael@0 1410 <![CDATA[
michael@0 1411 var url = this.getAttribute("url");
michael@0 1412 var title = this.getAttribute("title");
michael@0 1413 var type = this.getAttribute("type");
michael@0 1414
michael@0 1415 this.removeAttribute("actiontype");
michael@0 1416
michael@0 1417 // The ellipses are hidden via their visibility so that they always
michael@0 1418 // take up space and don't pop in on top of text when shown. For
michael@0 1419 // keyword searches, however, the title ellipsis should not take up
michael@0 1420 // space when hidden. Setting the hidden property accomplishes that.
michael@0 1421 this._titleOverflowEllipsis.hidden = false;
michael@0 1422
michael@0 1423 // If the type includes an action, set up the item appropriately.
michael@0 1424 var types = type.split(/\s+/);
michael@0 1425 var actionIndex = types.indexOf("action");
michael@0 1426 if (actionIndex >= 0) {
michael@0 1427 let [,action, param] = url.match(/^moz-action:([^,]+),(.*)$/);
michael@0 1428 this.setAttribute("actiontype", action);
michael@0 1429 url = param;
michael@0 1430 let desc = "]]>&action.switchToTab.label;<![CDATA[";
michael@0 1431 this._setUpDescription(this._action, desc, true);
michael@0 1432
michael@0 1433 // Remove the "action" substring so that the correct style, if any,
michael@0 1434 // is applied below.
michael@0 1435 types.splice(actionIndex, 1);
michael@0 1436 type = types.join(" ");
michael@0 1437 }
michael@0 1438
michael@0 1439 // If we have a tag match, show the tags and icon
michael@0 1440 if (type == "tag") {
michael@0 1441 // Configure the extra box for tags display
michael@0 1442 this._extraBox.hidden = false;
michael@0 1443 this._extraBox.childNodes[0].hidden = false;
michael@0 1444 this._extraBox.childNodes[1].hidden = true;
michael@0 1445 this._extraBox.pack = "end";
michael@0 1446 this._titleBox.flex = 1;
michael@0 1447
michael@0 1448 // The title is separated from the tags by an endash
michael@0 1449 let tags;
michael@0 1450 [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);
michael@0 1451
michael@0 1452 // Each tag is split by a comma in an undefined order, so sort it
michael@0 1453 let sortedTags = tags.split(",").sort().join(", ");
michael@0 1454
michael@0 1455 // Emphasize the matching text in the tags
michael@0 1456 this._setUpDescription(this._extra, sortedTags);
michael@0 1457
michael@0 1458 // Treat tagged matches as bookmarks for the star
michael@0 1459 type = "bookmark";
michael@0 1460 } else if (type == "keyword") {
michael@0 1461 // Configure the extra box for keyword display
michael@0 1462 this._extraBox.hidden = false;
michael@0 1463 this._extraBox.childNodes[0].hidden = true;
michael@0 1464 this._extraBox.childNodes[1].hidden = false;
michael@0 1465 this._extraBox.pack = "start";
michael@0 1466 this._titleBox.flex = 0;
michael@0 1467
michael@0 1468 // Hide the ellipsis so it doesn't take up space.
michael@0 1469 this._titleOverflowEllipsis.hidden = true;
michael@0 1470
michael@0 1471 // Put the parameters next to the title if we have any
michael@0 1472 let search = this.getAttribute("text");
michael@0 1473 let params = "";
michael@0 1474 let paramsIndex = search.indexOf(' ');
michael@0 1475 if (paramsIndex != -1)
michael@0 1476 params = search.substr(paramsIndex + 1);
michael@0 1477
michael@0 1478 // Emphasize the keyword parameters
michael@0 1479 this._setUpDescription(this._extra, params);
michael@0 1480
michael@0 1481 // Don't emphasize keyword searches in the title or url
michael@0 1482 this.setAttribute("text", "");
michael@0 1483 } else {
michael@0 1484 // Hide the title's extra box if we don't need extra stuff
michael@0 1485 this._extraBox.hidden = true;
michael@0 1486 this._titleBox.flex = 1;
michael@0 1487 }
michael@0 1488
michael@0 1489 // Give the image the icon style and a special one for the type
michael@0 1490 this._typeImage.className = "ac-type-icon" +
michael@0 1491 (type ? " ac-result-type-" + type : "");
michael@0 1492
michael@0 1493 // Show the url as the title if we don't have a title
michael@0 1494 if (title == "")
michael@0 1495 title = url;
michael@0 1496
michael@0 1497 // Emphasize the matching search terms for the description
michael@0 1498 this._setUpDescription(this._title, title);
michael@0 1499 this._setUpDescription(this._url, url);
michael@0 1500
michael@0 1501 // Set up overflow on a timeout because the contents of the box
michael@0 1502 // might not have a width yet even though we just changed them
michael@0 1503 setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis);
michael@0 1504 setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis);
michael@0 1505 ]]>
michael@0 1506 </body>
michael@0 1507 </method>
michael@0 1508
michael@0 1509 <method name="_setUpOverflow">
michael@0 1510 <parameter name="aParentBox"/>
michael@0 1511 <parameter name="aEllipsis"/>
michael@0 1512 <body>
michael@0 1513 <![CDATA[
michael@0 1514 // Hide the ellipsis incase there's just enough to not underflow
michael@0 1515 aEllipsis.style.visibility = "hidden";
michael@0 1516
michael@0 1517 // Start with the parent's width and subtract off its children
michael@0 1518 let tooltip = [];
michael@0 1519 let children = aParentBox.childNodes;
michael@0 1520 let widthDiff = aParentBox.boxObject.width;
michael@0 1521
michael@0 1522 for (let i = 0; i < children.length; i++) {
michael@0 1523 // Only consider a child if it actually takes up space
michael@0 1524 let childWidth = children[i].boxObject.width;
michael@0 1525 if (childWidth > 0) {
michael@0 1526 // Subtract a little less to account for subpixel rounding
michael@0 1527 widthDiff -= childWidth - .5;
michael@0 1528
michael@0 1529 // Add to the tooltip if it's not hidden and has text
michael@0 1530 let childText = children[i].textContent;
michael@0 1531 if (childText)
michael@0 1532 tooltip.push(childText);
michael@0 1533 }
michael@0 1534 }
michael@0 1535
michael@0 1536 // If the children take up more space than the parent.. overflow!
michael@0 1537 if (widthDiff < 0) {
michael@0 1538 // Re-show the ellipsis now that we know it's needed
michael@0 1539 aEllipsis.style.visibility = "visible";
michael@0 1540
michael@0 1541 // Separate text components with a ndash --
michael@0 1542 aParentBox.tooltipText = tooltip.join(" \u2013 ");
michael@0 1543 }
michael@0 1544 ]]>
michael@0 1545 </body>
michael@0 1546 </method>
michael@0 1547
michael@0 1548 <method name="_doUnderflow">
michael@0 1549 <parameter name="aName"/>
michael@0 1550 <body>
michael@0 1551 <![CDATA[
michael@0 1552 // Hide the ellipsis right when we know we're underflowing instead of
michael@0 1553 // waiting for the timeout to trigger the _setUpOverflow calculations
michael@0 1554 this[aName + "Box"].tooltipText = "";
michael@0 1555 this[aName + "OverflowEllipsis"].style.visibility = "hidden";
michael@0 1556 ]]>
michael@0 1557 </body>
michael@0 1558 </method>
michael@0 1559
michael@0 1560 <method name="_doOverflow">
michael@0 1561 <parameter name="aName"/>
michael@0 1562 <body>
michael@0 1563 <![CDATA[
michael@0 1564 this._setUpOverflow(this[aName + "Box"],
michael@0 1565 this[aName + "OverflowEllipsis"]);
michael@0 1566 ]]>
michael@0 1567 </body>
michael@0 1568 </method>
michael@0 1569
michael@0 1570 </implementation>
michael@0 1571 </binding>
michael@0 1572
michael@0 1573 <binding id="autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
michael@0 1574 <content>
michael@0 1575 <children includes="treecols"/>
michael@0 1576 <xul:treerows class="autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
michael@0 1577 <children/>
michael@0 1578 </xul:treerows>
michael@0 1579 </content>
michael@0 1580 </binding>
michael@0 1581
michael@0 1582 <binding id="autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
michael@0 1583 <implementation>
michael@0 1584 <field name="mLastMoveTime">Date.now()</field>
michael@0 1585 </implementation>
michael@0 1586 <handlers>
michael@0 1587 <handler event="mouseup">
michael@0 1588 <![CDATA[
michael@0 1589 // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
michael@0 1590 var item = event.originalTarget;
michael@0 1591
michael@0 1592 while (item && item.localName != "richlistitem")
michael@0 1593 item = item.parentNode;
michael@0 1594
michael@0 1595 if (!item)
michael@0 1596 return;
michael@0 1597
michael@0 1598 this.parentNode.onPopupClick(event);
michael@0 1599 ]]>
michael@0 1600 </handler>
michael@0 1601
michael@0 1602 <handler event="mousemove">
michael@0 1603 <![CDATA[
michael@0 1604 if (Date.now() - this.mLastMoveTime > 30) {
michael@0 1605 var item = event.target;
michael@0 1606
michael@0 1607 while (item && item.localName != "richlistitem")
michael@0 1608 item = item.parentNode;
michael@0 1609
michael@0 1610 if (!item)
michael@0 1611 return;
michael@0 1612
michael@0 1613 var rc = this.getIndexOfItem(item);
michael@0 1614 if (rc != this.selectedIndex)
michael@0 1615 this.selectedIndex = rc;
michael@0 1616
michael@0 1617 this.mLastMoveTime = Date.now();
michael@0 1618 }
michael@0 1619 ]]>
michael@0 1620 </handler>
michael@0 1621 </handlers>
michael@0 1622 </binding>
michael@0 1623
michael@0 1624 <binding id="autocomplete-treebody">
michael@0 1625 <implementation>
michael@0 1626 <field name="mLastMoveTime">Date.now()</field>
michael@0 1627 </implementation>
michael@0 1628
michael@0 1629 <handlers>
michael@0 1630 <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/>
michael@0 1631
michael@0 1632 <handler event="mousedown"><![CDATA[
michael@0 1633 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
michael@0 1634 if (rc != this.parentNode.currentIndex)
michael@0 1635 this.parentNode.view.selection.select(rc);
michael@0 1636 ]]></handler>
michael@0 1637
michael@0 1638 <handler event="mousemove"><![CDATA[
michael@0 1639 if (Date.now() - this.mLastMoveTime > 30) {
michael@0 1640 var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
michael@0 1641 if (rc != this.parentNode.currentIndex)
michael@0 1642 this.parentNode.view.selection.select(rc);
michael@0 1643 this.mLastMoveTime = Date.now();
michael@0 1644 }
michael@0 1645 ]]></handler>
michael@0 1646 </handlers>
michael@0 1647 </binding>
michael@0 1648
michael@0 1649 <binding id="autocomplete-treerows">
michael@0 1650 <content>
michael@0 1651 <xul:hbox flex="1" class="tree-bodybox">
michael@0 1652 <children/>
michael@0 1653 </xul:hbox>
michael@0 1654 <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/>
michael@0 1655 </content>
michael@0 1656 </binding>
michael@0 1657
michael@0 1658 <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
michael@0 1659 <implementation>
michael@0 1660 <method name="showPopup">
michael@0 1661 <body><![CDATA[
michael@0 1662 var textbox = document.getBindingParent(this);
michael@0 1663 textbox.showHistoryPopup();
michael@0 1664 ]]></body>
michael@0 1665 </method>
michael@0 1666 </implementation>
michael@0 1667
michael@0 1668 <handlers>
michael@0 1669 <handler event="mousedown" button="0"><![CDATA[
michael@0 1670 this.showPopup();
michael@0 1671 ]]></handler>
michael@0 1672 </handlers>
michael@0 1673 </binding>
michael@0 1674
michael@0 1675 </bindings>

mercurial