toolkit/content/widgets/autocomplete.xml

changeset 0
6474c204b198
     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>

mercurial