xpfe/components/autocomplete/resources/content/autocomplete.xml

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 <?xml version="1.0"?>
     2 <!-- This Source Code Form is subject to the terms of the Mozilla Public
     3    - License, v. 2.0. If a copy of the MPL was not distributed with this
     4    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     7 <bindings id="autocompleteBindings"
     8           xmlns="http://www.mozilla.org/xbl"
     9           xmlns:html="http://www.w3.org/1999/xhtml"
    10           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    11           xmlns:xbl="http://www.mozilla.org/xbl">
    13   <binding id="autocomplete" role="xul:combobox"
    14            extends="chrome://global/content/bindings/textbox.xml#textbox">
    15     <resources>
    16       <stylesheet src="chrome://global/content/autocomplete.css"/>
    17       <stylesheet src="chrome://global/skin/autocomplete.css"/>
    18     </resources>
    20     <content>
    21       <children includes="menupopup"/>
    23       <xul:hbox class="autocomplete-textbox-container" flex="1" align="center">
    24         <children includes="image|deck|stack|box">
    25           <xul:image class="autocomplete-icon" allowevents="true"/>
    26         </children>
    28         <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext">
    29           <children/>
    30           <html:input anonid="input" class="autocomplete-textbox textbox-input"
    31                       allowevents="true"
    32                       xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/>
    33         </xul:hbox>
    34         <children includes="hbox"/>
    35       </xul:hbox>
    37       <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true"
    38                       xbl:inherits="open,enablehistory" anonid="historydropmarker"/>
    40       <xul:popupset>
    41         <xul:panel type="autocomplete" anonid="popup"
    42                    ignorekeys="true" noautofocus="true" level="top"
    43                    xbl:inherits="for=id,nomatch"/>
    44       </xul:popupset>
    45     </content>
    47     <implementation implements="nsIDOMXULMenuListElement">
    49       <constructor><![CDATA[
    50         // XXX bug 90337 band-aid until we figure out what's going on here
    51         if (this.value != this.mInputElt.value)
    52           this.mInputElt.value = this.value;
    53         delete this.value;
    55         // listen for pastes
    56         this.mInputElt.controllers.insertControllerAt(0, this.mPasteController);
    58         // listen for menubar activation
    59         window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
    61         // set default property values
    62         this.ifSetAttribute("timeout", 50);
    63         this.ifSetAttribute("pastetimeout", 1000);
    64         this.ifSetAttribute("maxrows", 5);
    65         this.ifSetAttribute("showpopup", true);
    66         this.ifSetAttribute("disableKeyNavigation", true);
    68         // initialize the search sessions
    69         if (this.hasAttribute("autocompletesearch"))
    70           this.initAutoCompleteSearch();
    72         // hack to work around lack of bottom-up constructor calling
    73         if ("initialize" in this.popup)
    74           this.popup.initialize();
    75       ]]></constructor>
    77       <destructor><![CDATA[
    78         this.clearResults(false);
    79         window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true);
    80         this.mInputElt.controllers.removeController(this.mPasteController);
    81       ]]></destructor>
    83       <!-- =================== nsIAutoCompleteInput =================== -->
    84       <!-- XXX: This implementation is currently incomplete. -->
    86       <!-- reference to the results popup element -->
    87       <field name="popup"><![CDATA[
    88         document.getAnonymousElementByAttribute(this, "anonid", "popup");
    89       ]]></field>
    91       <property name="popupOpen"
    92                 onget="return this.mMenuOpen;"
    93                 onset="if (val) this.openPopup(); else this.closePopup(); return val;"/>
    95       <!-- option to turn off autocomplete -->
    96       <property name="disableAutoComplete"
    97                 onset="this.setAttribute('disableautocomplete', val); return val;"
    98                 onget="return this.getAttribute('disableautocomplete') == 'true';"/>
   100       <!-- if the resulting match string is not at the beginning of the typed string,
   101            this will optionally autofill like this "bar |>> foobar|" -->
   102       <property name="completeDefaultIndex"
   103                 onset="this.setAttribute('completedefaultindex', val); return val;"
   104                 onget="return this.getAttribute('completedefaultindex') == 'true';"/>
   106       <!-- option for completing to the default result whenever the user hits
   107            enter or the textbox loses focus -->
   108       <property name="forceComplete"
   109                 onset="this.setAttribute('forcecomplete', val); return val;"
   110                 onget="return this.getAttribute('forcecomplete') == 'true';"/>
   112       <property name="minResultsForPopup"
   113                 onset="this.setAttribute('minresultsforpopup', val); return val;"
   114                 onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/>
   116       <!-- maximum number of rows to display -->
   117       <property name="maxRows"
   118                 onset="this.setAttribute('maxrows', val); return val;"
   119                 onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
   121       <!-- toggles a second column in the results list which contains
   122            the string in the comment field of each autocomplete result -->
   123       <property name="showCommentColumn"
   124                 onget="return this.getAttribute('showcommentcolumn') == 'true';">
   125         <setter><![CDATA[
   126           this.popup.showCommentColumn = val;
   127           this.setAttribute('showcommentcolumn', val);
   128           return val;
   129         ]]></setter>
   130       </property>
   132       <!-- number of milliseconds after a keystroke before a search begins -->
   133       <property name="timeout"
   134                 onset="this.setAttribute('timeout', val); return val;"
   135                 onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
   137       <property name="searchParam"
   138                 onget="return this.getAttribute('autocompletesearchparam') || '';"
   139                 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
   141       <property name="searchCount" readonly="true"
   142                 onget="return this.sessionCount;"/>
   144       <method name="getSearchAt">
   145         <parameter name="aIndex"/>
   146         <body><![CDATA[
   147           var idx = -1;
   148           for (var name in this.mSessions)
   149             if (++idx == aIndex)
   150               return name;
   152           return null;
   153         ]]></body>
   154       </method>
   156       <property name="textValue"
   157                 onget="return this.value;"
   158                 onset="this.setTextValue(val); return val;"/>
   160       <method name="onSearchBegin">
   161         <body><![CDATA[
   162           this._fireEvent("searchbegin");
   163         ]]></body>
   164       </method>
   166       <method name="onSearchComplete">
   167         <body><![CDATA[
   168           if (this.noMatch)
   169             this.setAttribute("nomatch", "true");
   170           else
   171             this.removeAttribute("nomatch");
   173           this._fireEvent("searchcomplete");
   174         ]]></body>
   175       </method>
   177       <method name="onTextReverted">
   178         <body><![CDATA[
   179           return this._fireEvent("textreverted");
   180         ]]></body>
   181       </method>
   183       <!-- =================== nsIDOMXULMenuListElement =================== -->
   185       <property name="editable" readonly="true"
   186                 onget="return true;" />
   188       <property name="crop"
   189                 onset="this.setAttribute('crop', val); return val;"
   190                 onget="return this.getAttribute('crop');"/>
   192       <property name="label" readonly="true"
   193                 onget="return this.mInputElt.value;"/>
   195       <property name="open"
   196                 onget="return this.getAttribute('open') == 'true';">
   197         <setter>
   198           <![CDATA[
   199             var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
   200             if (val) {
   201               this.setAttribute('open', true);
   202               historyPopup.showPopup();
   203             } else {
   204               this.removeAttribute('open');
   205               historyPopup.hidePopup();
   206             }
   207           ]]>
   208         </setter>
   209       </property>
   211       <!-- =================== PUBLIC PROPERTIES =================== -->
   213       <property name="value"
   214                 onget="return this.mInputElt.value;">
   215         <setter><![CDATA[
   216           this.ignoreInputEvent = true;
   217           this.mInputElt.value = val;
   218           this.ignoreInputEvent = false;
   219           var event = document.createEvent('Events');
   220           event.initEvent('ValueChange', true, true);
   221           this.mInputElt.dispatchEvent(event);
   222           return val;
   223         ]]></setter>
   224       </property>
   226       <property name="focused"
   227                 onget="return this.getAttribute('focused') == 'true';"/>
   229       <method name="initAutoCompleteSearch">
   230         <body><![CDATA[
   231           var list = this.getAttribute("autocompletesearch").split(" ");
   232           for (var i = 0; i < list.length; i++) {
   233             var name = list[i];
   234             var contractid = "@mozilla.org/autocomplete/search;1?name=" + name;
   235             if (contractid in Components.classes) {
   236               try {
   237                 this.mSessions[name] =
   238                   Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch);
   239                 this.mLastResults[name] = null;
   240                 this.mLastRows[name] = 0;
   241                 ++this.sessionCount;
   242               } catch (e) {
   243                 dump("### ERROR - unable to create search \"" + name + "\".\n");
   244               }
   245             } else {
   246               dump("search \"" + name + "\" not found - skipping.\n");
   247             }
   248           }
   249         ]]></body>
   250       </method>
   252       <!-- the number of sessions currently in use -->
   253       <field name="sessionCount">0</field>
   255       <!-- number of milliseconds after a paste before a search begins -->
   256       <property name="pasteTimeout"
   257                 onset="this.setAttribute('pastetimeout', val); return val;"
   258                 onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/>
   260       <!-- option for filling the textbox with the best match while typing 
   261            and selecting the difference -->
   262       <property name="autoFill"
   263                 onset="this.setAttribute('autofill', val); return val;"
   264                 onget="return this.getAttribute('autofill') == 'true';"/>
   266       <!--  if this attribute is set, allow different style for 
   267             non auto-completed lines -->
   268       <property name="highlightNonMatches"
   269                 onset="this.setAttribute('highlightnonmatches', val); return val;"
   270                 onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
   272       <!-- option to show the popup containing the results -->
   273       <property name="showPopup"
   274                 onset="this.setAttribute('showpopup', val); return val;"
   275                 onget="return this.getAttribute('showpopup') == 'true';"/>
   277       <!-- option to allow scrolling through the list via the tab key, rather than
   278            tab moving focus out of the textbox -->
   279       <property name="tabScrolling"
   280                 onset="return this.setAttribute('tabscrolling', val); return val;"
   281                 onget="return this.getAttribute('tabscrolling') == 'true';"/>
   283       <!-- option to completely ignore any blur events while  
   284            searches are still going on.  This is useful so that nothing
   285            gets autopicked if the window is required to lose focus for
   286            some reason (eg in LDAP autocomplete, another window may be
   287            brought up so that the user can enter a password to authenticate
   288            to an LDAP server).  -->
   289       <property name="ignoreBlurWhileSearching"
   290                 onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
   291                 onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
   293       <!-- state which indicates the current action being performed by the user.
   294            Possible values are : none, typing, scrolling -->
   295       <property name="userAction"
   296                 onset="this.setAttribute('userAction', val); return val;"
   297                 onget="return this.getAttribute('userAction');"/>
   299       <!-- state which indicates if the last search had no matches -->
   300       <field name="noMatch">true</field>
   302       <!-- state which indicates a search is currently happening -->
   303       <field name="isSearching">false</field>
   305       <!-- state which indicates a search timeout is current waiting -->
   306       <property name="isWaiting" 
   307                 onget="return this.mAutoCompleteTimer != 0;"/>
   309       <!-- =================== PRIVATE PROPERTIES =================== -->
   311       <field name="mSessions">({})</field>
   312       <field name="mLastResults">({})</field>
   313       <field name="mLastRows">({})</field>
   314       <field name="mLastKeyCode">null</field>
   315       <field name="mAutoCompleteTimer">0</field>
   316       <field name="mMenuOpen">false</field>
   317       <field name="mFireAfterSearch">false</field>
   318       <field name="mFinishAfterSearch">false</field>
   319       <field name="mNeedToFinish">false</field>
   320       <field name="mNeedToComplete">false</field>
   321       <field name="mTransientValue">false</field>
   322       <field name="mView">null</field>
   323       <field name="currentSearchString">""</field>
   324       <field name="ignoreInputEvent">false</field>
   325       <field name="oninit">null</field>
   326       <field name="mDefaultMatchFilled">false</field>
   327       <field name="mFirstReturn">true</field>
   328       <field name="mIsPasting">false</field>
   330       <field name="mPasteController"><![CDATA[
   331         ({
   332           self: this,
   333           kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
   334           supportsCommand: function(aCommand) {
   335             return aCommand == "cmd_paste";
   336           },
   337           isCommandEnabled: function(aCommand) {
   338             return aCommand == "cmd_paste" &&
   339                    this.self.editor.isSelectionEditable &&
   340                    this.self.editor.canPaste(this.kGlobalClipboard);
   341           },
   342           doCommand: function(aCommand) {
   343             if (aCommand == "cmd_paste") {
   344               this.self.mIsPasting = true;
   345               this.self.editor.paste(this.kGlobalClipboard);
   346               this.self.mIsPasting = false;
   347             }
   348           },
   349           onEvent: function() {}
   350         })
   351       ]]></field>
   353       <field name="mMenuBarListener"><![CDATA[
   354         ({
   355           self: this,
   356           handleEvent: function(aEvent) {
   357             try {
   358               this.self.finishAutoComplete(false, false, aEvent);
   359               this.self.clearTimer();
   360               this.self.closePopup();
   361             } catch (e) {
   362               window.top.removeEventListener("DOMMenuBarActive", this, true);
   363             }
   364           }
   365         })
   366       ]]></field>
   368       <field name="mAutoCompleteObserver"><![CDATA[
   369         ({
   370           self: this,
   371           onSearchResult: function(aSearch, aResult) {
   372             for (var name in this.self.mSessions)
   373               if (this.self.mSessions[name] == aSearch)
   374                 this.self.processResults(name, aResult);
   375           }
   376         })
   377       ]]></field>
   379       <field name="mInputElt"><![CDATA[
   380         document.getAnonymousElementByAttribute(this, "anonid", "input");
   381       ]]></field>
   383       <field name="mMenuAccessKey"><![CDATA[
   384         Components.classes["@mozilla.org/preferences-service;1"]
   385                  .getService(Components.interfaces.nsIPrefBranch)
   386                  .getIntPref("ui.key.menuAccessKey");
   387       ]]></field>
   389       <!-- =================== PUBLIC METHODS =================== -->
   391       <method name="getErrorAt">
   392         <parameter name="aIndex"/>
   393         <body><![CDATA[
   394           var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex);
   395           return obj && this.mLastResults[obj.session] &&
   396                         this.mLastResults[obj.session].errorDescription;
   397         ]]></body>
   398       </method>
   400       <!-- get a value from the autocomplete results as a string via an absolute index-->
   401       <method name="getResultValueAt">
   402         <parameter name="aIndex"/>
   403         <body><![CDATA[
   404           var obj = this.convertIndexToSession(aIndex);
   405           return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
   406         ]]></body>
   407       </method>
   409       <!-- get a value from the autocomplete results as a string from a specific session -->
   410       <method name="getSessionValueAt">
   411         <parameter name="aSession"/>
   412         <parameter name="aIndex"/>
   413         <body><![CDATA[
   414           var result = this.mLastResults[aSession];
   415           return result.errorDescription || result.getValueAt(aIndex);
   416         ]]></body>
   417       </method>
   419       <!-- get the total number of results overall -->
   420       <method name="getResultCount">
   421         <body><![CDATA[
   422           return this.view.rowCount;
   423         ]]></body>
   424       </method>
   426       <!-- get the first session that has results -->
   427       <method name="getDefaultSession">
   428         <body><![CDATA[
   429           for (var name in this.mLastResults) {
   430             var results = this.mLastResults[name];
   431             if (results && results.matchCount > 0 && !results.errorDescription)
   432               return name;
   433           }
   434           return null;
   435         ]]></body>
   436       </method>
   438       <!-- empty the cached result data and empty the results popup -->
   439       <method name="clearResults">
   440         <parameter name="aInvalidate"/>
   441         <body><![CDATA[
   442           this.clearResultData();
   443           this.clearResultElements(aInvalidate);
   444         ]]></body>
   445       </method>
   447       <!-- =================== PRIVATE METHODS =================== -->
   449       <!-- ::::::::::::: session searching ::::::::::::: -->
   451       <!--  -->
   452       <method name="callListener">
   453         <parameter name="me"/>
   454         <parameter name="aAction"/>
   455         <body><![CDATA[
   456           // bail if the binding was detached or the element removed from
   457           // document during the timeout
   458           if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode)
   459             return;
   461           me.clearTimer();
   463           if (me.disableAutoComplete)
   464             return;
   466           switch (aAction) {
   467             case "startLookup":
   468               me.startLookup();
   469               break;
   471             case "stopLookup":
   472               me.stopLookup();
   473               break;
   474           }
   475         ]]></body>
   476       </method>
   478       <!--  -->
   479       <method name="startLookup">
   480         <body><![CDATA[
   481           var str = this.currentSearchString;
   482           if (!str) {
   483             this.clearResults(false);
   484             this.closePopup();
   485             return;
   486           }
   488           this.isSearching = true;
   489           this.mFirstReturn = true;
   490           this.mSessionReturns = this.sessionCount;
   491           this.mFailureItems = 0;
   492           this.mDefaultMatchFilled = false; // clear out our prefill state.
   494           // Notify the input that the search is beginning.
   495           this.onSearchBegin();
   497           // tell each session to start searching...
   498           for (var name in this.mSessions)
   499             try {
   500               this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver);
   501             } catch (e) {
   502               --this.mSessionReturns;
   503               this.searchFailed();
   504             }
   505         ]]></body>
   506       </method>
   508       <!--  -->
   509       <method name="stopLookup">
   510         <body><![CDATA[
   511           for (var name in this.mSessions)
   512             this.mSessions[name].stopSearch();
   513         ]]></body>
   514       </method>
   516       <!--  -->
   517       <method name="processResults">
   518         <parameter name="aSessionName"/>
   519         <parameter name="aResults"/>
   520         <body><![CDATA[
   521           if (this.disableAutoComplete)
   522             return;
   524           const ACR = Components.interfaces.nsIAutoCompleteResult;
   525           var status = aResults.searchResult;
   526           if (status != ACR.RESULT_NOMATCH_ONGOING &&
   527               status != ACR.RESULT_SUCCESS_ONGOING)
   528             --this.mSessionReturns;
   530           // check the many criteria for failure
   531           if (aResults.errorDescription)
   532             ++this.mFailureItems;
   533           else if (status == ACR.RESULT_IGNORED ||
   534                    status == ACR.RESULT_FAILURE ||
   535                    status == ACR.RESULT_NOMATCH ||
   536                    status == ACR.RESULT_NOMATCH_ONGOING ||
   537                    aResults.matchCount == 0 ||
   538                    aResults.searchString != this.currentSearchString)
   539           {
   540             this.mLastResults[aSessionName] = null;
   541             if (this.mFirstReturn)
   542               this.clearResultElements(false);
   543             this.mFirstReturn = false;
   544             this.searchFailed();
   545             return;
   546           }
   548           if (this.mFirstReturn) {
   549             if (this.view.mTree)
   550               this.view.mTree.beginUpdateBatch();
   551             this.clearResultElements(false); // clear results, but don't repaint yet
   552           }
   554           // always call openPopup...we may not have opened it
   555           // if a previous search session didn't return enough search results.
   556           // it's smart and doesn't try to open itself multiple times...
   557           // be sure to add our result elements before calling openResultPopuup as we need
   558           // to know the total # of results found so far.
   559           this.addResultElements(aSessionName, aResults);
   561           this.autoFillInput(aSessionName, aResults, false);
   562           if (this.mFirstReturn && this.view.mTree)
   563             this.view.mTree.endUpdateBatch();
   564           this.openPopup();
   565           this.mFirstReturn = false;
   567           // if this is the last session to return... 
   568           if (this.mSessionReturns == 0) 
   569             this.postSearchCleanup();
   571           if (this.mFinishAfterSearch)
   572             this.finishAutoComplete(false, this.mFireAfterSearch, null);
   573         ]]></body>
   574       </method>
   576       <!-- called each time a search fails, except when failure items need
   577            to be displayed. If all searches have failed, clear the list
   578            and close the popup -->
   579       <method name="searchFailed">
   580         <body><![CDATA[
   581           // if all searches are done and they all failed...
   582           if (this.mSessionReturns == 0 && this.getResultCount() == 0) {
   583             if (this.minResultsForPopup == 0) {
   584               this.clearResults(true); // clear data and repaint empty
   585               this.openPopup();
   586             } else {
   587               this.closePopup(); 
   588             }
   589           }
   591           // if it's the last session to return, time to clean up...
   592           if (this.mSessionReturns == 0)
   593             this.postSearchCleanup();
   594         ]]></body>
   595       </method>
   597       <!-- does some stuff after a search is done (success or failure) -->
   598       <method name="postSearchCleanup">
   599         <body><![CDATA[
   600           this.isSearching = false;
   602           // figure out if there are no matches in all search sessions
   603           var failed = true;
   604           for (var name in this.mSessions) {
   605             if (this.mLastResults[name])
   606               failed = this.mLastResults[name].errorDescription ||
   607                        this.mLastResults[name].matchCount == 0;
   608             if (!failed)
   609               break;
   610           }
   611           this.noMatch = failed;
   613           // if we have processed all of our searches, and none of them gave us a default index,
   614           // then we should try to auto fill the input field with the first match. 
   615           // note: autoFillInput is smart enough to kick out if we've already prefilled something...
   616           if (!this.noMatch) {
   617              var defaultSession = this.getDefaultSession();
   618              if (defaultSession)
   619                 this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true);         
   620           }
   622           // Notify the input that the search is complete.
   623           this.onSearchComplete();
   624         ]]></body>
   625       </method>
   627       <!-- when the focus exits the widget or user hits return, 
   628            determine what value to leave in the textbox -->
   629       <method name="finishAutoComplete">
   630         <parameter name="aForceComplete"/>
   631         <parameter name="aFireTextCommand"/>
   632         <parameter name="aTriggeringEvent"/>
   633         <body><![CDATA[
   634           this.mFinishAfterSearch = false;
   635           this.mFireAfterSearch = false;
   636           if (this.mNeedToFinish && !this.disableAutoComplete) {
   637             // set textbox value to either override value, or default search result 
   638             var val = this.popup.overrideValue;
   639             if (val) {
   640               this.setTextValue(val);
   641               this.mNeedToFinish = false;
   642             } else if (this.mTransientValue || 
   643                        !(this.forceComplete ||
   644                         (aForceComplete &&
   645                          this.mDefaultMatchFilled &&
   646                          this.mNeedToComplete))) {
   647               this.mNeedToFinish = false;
   648             } else if (this.isWaiting) {
   649               // if the user typed, the search results are out of date, so let
   650               // the search finish, and tell it to come back here when it's done
   651               this.mFinishAfterSearch = true;
   652               this.mFireAfterSearch = aFireTextCommand;
   653               return;
   654             } else {
   655               // we want to use the default item index for the first session which gave us a valid
   656               // default item index...
   657               for (var name in this.mLastResults) {
   658                 var results = this.mLastResults[name];
   659                 if (results && results.matchCount > 0 &&
   660                     !results.errorDescription && results.defaultIndex != -1)
   661                 {
   662                   val = results.getValueAt(results.defaultIndex);
   663                   this.setTextValue(val);
   664                   this.mDefaultMatchFilled = true;
   665                   this.mNeedToFinish = false;
   666                   break;
   667                 }
   668               }
   670               if (this.mNeedToFinish) {
   671                 // if a search is happening at this juncture, bail out of this function
   672                 // and let the search finish, and tell it to come back here when it's done
   673                 if (this.isSearching) {
   674                   this.mFinishAfterSearch = true;
   675                   this.mFireAfterSearch = aFireTextCommand;
   676                   return;
   677                 }
   679                 this.mNeedToFinish = false;
   680                 var defaultSession = this.getDefaultSession();
   681                 if (defaultSession)
   682                 {
   683                     // preselect the first one
   684                     var first = this.getSessionValueAt(defaultSession, 0);
   685                     this.setTextValue(first);
   686                     this.mDefaultMatchFilled = true;
   687                 }
   688               }
   689             }
   691             this.stopLookup();
   693             this.closePopup();
   694           }
   696           this.mNeedToComplete = false;
   697           this.clearTimer();
   699           if (aFireTextCommand)
   700             this._fireEvent("textentered", this.userAction, aTriggeringEvent);
   701         ]]></body>
   702       </method>
   704       <!--  when the user clicks an entry in the autocomplete popup -->
   705       <method name="onResultClick">
   706         <body><![CDATA[
   707          // set textbox value to either override value, or the clicked result
   708           var errItem = this.getErrorAt(this.popup.selectedIndex);
   709           var val = this.popup.overrideValue;
   710             if (val)
   711               this.setTextValue(val);
   712           else if (this.popup.selectedIndex != -1) {
   713             if (errItem) {
   714               this.setTextValue(this.currentSearchString);
   715               this.mTransientValue = true;
   716             } else { 
   717               this.setTextValue(this.getResultValueAt(
   718                                              this.popup.selectedIndex));
   719             }
   720           }
   722           this.mNeedToFinish = false;
   723           this.mNeedToComplete = false;
   725           this.closePopup();
   727           this.currentSearchString = "";
   729           if (errItem)
   730             this._fireEvent("errorcommand", errItem);
   731           this._fireEvent("textentered", "clicking");
   732         ]]></body>
   733       </method>
   735       <!-- when the user hits escape, revert the previously typed value in the textbox -->
   736       <method name="undoAutoComplete">
   737         <body><![CDATA[
   738           var val = this.currentSearchString;
   740           var ok = this.onTextReverted();
   741           if ((ok || ok == undefined) && val)
   742             this.setTextValue(val);
   744           this.userAction = "typing";
   746           this.currentSearchString = this.value;
   747           this.mNeedToComplete = false;
   748         ]]></body>
   749       </method>
   751       <!-- convert an absolute result index into a session name/index pair -->
   752       <method name="convertIndexToSession">
   753         <parameter name="aIndex"/>
   754         <body><![CDATA[
   755           for (var name in this.mLastRows) {
   756             if (aIndex < this.mLastRows[name])
   757               return { session: name, index: aIndex };
   758             aIndex -= this.mLastRows[name];
   759           }
   760           return null;
   761         ]]></body>
   762       </method>
   764       <!-- ::::::::::::: user input handling ::::::::::::: -->
   766       <!--  -->
   767       <method name="processInput">
   768         <body><![CDATA[
   769           // stop current lookup in case it's async.
   770           this.stopLookup();
   771           // stop the queued up lookup on a timer
   772           this.clearTimer();
   774           if (this.disableAutoComplete)
   775             return;
   777           this.userAction = "typing";
   778           this.mFinishAfterSearch = false;
   779           this.mNeedToFinish = true;
   780           this.mTransientValue = false;
   781           this.mNeedToComplete = true;
   782           var str = this.value;
   783           this.currentSearchString = str;
   784           this.popup.clearSelection();
   786           var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout;
   787           this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup");
   788         ]]></body>
   789       </method>
   791       <!--  -->
   792       <method name="processKeyPress">
   793         <parameter name="aEvent"/>
   794         <body><![CDATA[
   795           this.mLastKeyCode = aEvent.keyCode;
   797           var killEvent = false;
   799           switch (aEvent.keyCode) {
   800             case KeyEvent.DOM_VK_TAB:
   801               if (this.tabScrolling) {
   802                 // don't kill this event if alt-tab or ctrl-tab is hit
   803                 if (!aEvent.altKey && !aEvent.ctrlKey) {
   804                   killEvent = this.mMenuOpen;
   805                   if (killEvent)
   806                     this.keyNavigation(aEvent);
   807                 }
   808               } 
   809               break;              
   811             case KeyEvent.DOM_VK_RETURN:
   813               // if this is a failure item, save it for fireErrorCommand
   814               var errItem = this.getErrorAt(this.popup.selectedIndex);
   816               killEvent = this.mMenuOpen;
   817               this.finishAutoComplete(true, true, aEvent);
   818               this.closePopup();
   819               if (errItem) {
   820                   this._fireEvent("errorcommand", errItem);
   821               }
   822               break;
   824             case KeyEvent.DOM_VK_ESCAPE:
   825               this.clearTimer();
   826               killEvent = this.mMenuOpen;
   827               this.undoAutoComplete();
   828               this.closePopup();
   829               break;
   831             case KeyEvent.DOM_VK_LEFT:
   832             case KeyEvent.DOM_VK_RIGHT:
   833             case KeyEvent.DOM_VK_HOME:
   834             case KeyEvent.DOM_VK_END:
   835               this.finishAutoComplete(true, false, aEvent);
   836               this.clearTimer();
   837               this.closePopup();
   838               break;
   840             case KeyEvent.DOM_VK_DOWN:
   841               if (!aEvent.altKey) {
   842                 this.clearTimer();
   843                 killEvent = this.keyNavigation(aEvent);
   844                 break;
   845               }
   846             // Alt+Down falls through to history popup toggling code
   848             case KeyEvent.DOM_VK_F4:
   849               if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") {
   850                 var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker");
   851                 if (historyPopup)
   852                   historyPopup.showPopup();
   853                 else
   854                   historyPopup.hidePopup();
   855               }
   856               break;
   857             case KeyEvent.DOM_VK_PAGE_UP:
   858             case KeyEvent.DOM_VK_PAGE_DOWN:
   859             case KeyEvent.DOM_VK_UP:
   860               if (!aEvent.ctrlKey && !aEvent.metaKey) {
   861                 this.clearTimer();
   862                 killEvent = this.keyNavigation(aEvent);
   863               }
   864               break;
   866             case KeyEvent.DOM_VK_BACK_SPACE:
   867               if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey &&
   868                   this.selectionStart == this.currentSearchString.length &&
   869                   this.selectionEnd == this.value.length &&
   870                   this.mDefaultMatchFilled) {
   871                 this.mDefaultMatchFilled = false;
   872                 this.value = this.currentSearchString;
   873               }
   875               if (!/Mac/.test(navigator.platform))
   876                 break;
   877             case KeyEvent.DOM_VK_DELETE:
   878               if (/Mac/.test(navigator.platform) && !aEvent.shiftKey)
   879                 break;
   881               if (this.mMenuOpen && this.popup.selectedIndex != -1) {
   882                 var obj = this.convertIndexToSession(this.popup.selectedIndex);
   883                 if (obj) {
   884                   var result = this.mLastResults[obj.session];
   885                   if (!result.errorDescription) {
   886                     var count = result.matchCount;
   887                     result.removeValueAt(obj.index, true);
   888                     this.view.updateResults(this.popup.selectedIndex, result.matchCount - count);
   889                     killEvent = true;
   890                   }
   891                 }
   892               }
   893               break;
   894           }
   896           if (killEvent) {
   897             aEvent.preventDefault();
   898             aEvent.stopPropagation();
   899           }
   901           return true;
   902         ]]></body>
   903       </method>
   905       <!--  -->
   906       <method name="processStartComposition">
   907         <body><![CDATA[
   908           this.finishAutoComplete(false, false, null);
   909           this.clearTimer();
   910           this.closePopup();
   911         ]]></body>
   912       </method>
   914       <!--  -->
   915       <method name="keyNavigation">
   916         <parameter name="aEvent"/>
   917         <body><![CDATA[
   918           var k = aEvent.keyCode;
   919           if (k == KeyEvent.DOM_VK_TAB ||
   920               k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN ||
   921               k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN)
   922           {
   923             if (!this.mMenuOpen) {
   924               // Original xpfe style was to allow the up and down keys to have
   925               // their default Mac action if the popup could not be opened.
   926               // For compatibility for toolkit we now have to predict which
   927               // keys have a default action that we can always allow to fire.
   928               if (/Mac/.test(navigator.platform) &&
   929                   ((k == KeyEvent.DOM_VK_UP &&
   930                    (this.selectionStart != 0 ||
   931                     this.selectionEnd != 0)) ||
   932                   (k == KeyEvent.DOM_VK_DOWN &&
   933                    (this.selectionStart != this.value.length ||
   934                     this.selectionEnd != this.value.length))))
   935                 return false;
   936               if (this.currentSearchString != this.value) {
   937                  this.processInput();
   938                  return true;
   939               }
   940               if (this.view.rowCount < this.minResultsForPopup)
   941                 return true; // used to be false, see above
   943               this.mNeedToFinish = true;
   944               this.openPopup();
   945               return true;
   946             }
   948             this.userAction = "scrolling";
   949             this.mNeedToComplete = false;
   951             var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey ||
   952                           k == KeyEvent.DOM_VK_UP ||
   953                           k == KeyEvent.DOM_VK_PAGE_UP;
   954             var page = k == KeyEvent.DOM_VK_PAGE_UP ||
   955                        k == KeyEvent.DOM_VK_PAGE_DOWN;
   956             var selected = this.popup.selectBy(reverse, page);
   958             // determine which value to place in the textbox
   959             this.ignoreInputEvent = true;
   960             if (selected != -1) {
   961               if (this.getErrorAt(selected)) {
   962                 if (this.currentSearchString)
   963                   this.setTextValue(this.currentSearchString);
   964               } else {
   965                 this.setTextValue(this.getResultValueAt(selected));
   966               }
   967               this.mTransientValue = true;
   968             } else {
   969               if (this.currentSearchString)
   970                 this.setTextValue(this.currentSearchString);
   971               this.mTransientValue = false;
   972             }
   974             // move cursor to the end
   975             this.mInputElt.setSelectionRange(this.value.length, this.value.length);
   976             this.ignoreInputEvent = false;
   977           }
   978           return true;
   979         ]]></body>
   980       </method>
   982       <!-- while the user is typing, fill the textbox with the "default" value
   983            if one can be assumed, and select the end of the text -->
   984       <method name="autoFillInput">
   985         <parameter name="aSessionName"/>
   986         <parameter name="aResults"/>
   987         <parameter name="aUseFirstMatchIfNoDefault"/>
   988         <body><![CDATA[
   989           if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
   990               this.mDefaultMatchFilled)
   991             return;
   993           if (!this.mFinishAfterSearch &&
   994               (this.autoFill || this.completeDefaultIndex) && 
   995               this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
   996               this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
   997             var indexToUse = aResults.defaultIndex;
   998             if (aUseFirstMatchIfNoDefault && indexToUse == -1)
   999               indexToUse = 0; 
  1001             if (indexToUse != -1) {
  1002               var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
  1003               var match = resultValue.toLowerCase();
  1004               var entry = this.currentSearchString.toLowerCase();
  1005               this.ignoreInputEvent = true;
  1006               if (match.indexOf(entry) == 0) {
  1007                 var endPoint = this.value.length;
  1008                 this.setTextValue(this.value + resultValue.substr(endPoint));
  1009                 this.mInputElt.setSelectionRange(endPoint, this.value.length);
  1010               } else {
  1011                 if (this.completeDefaultIndex) {
  1012                   this.setTextValue(this.value + " >> " + resultValue);
  1013                   this.mInputElt.setSelectionRange(entry.length, this.value.length);
  1014                 } else {
  1015                   var postIndex = resultValue.indexOf(this.value);
  1016                   if (postIndex >= 0) {
  1017                     var startPt = this.value.length;
  1018                     this.setTextValue(this.value + 
  1019                                       resultValue.substr(startPt+postIndex));
  1020                     this.mInputElt.setSelectionRange(startPt, this.value.length);
  1024               this.mNeedToComplete = true;
  1025               this.ignoreInputEvent = false;
  1026               this.mDefaultMatchFilled = true;
  1029         ]]></body>
  1030       </method>
  1032       <!-- ::::::::::::: popup and tree ::::::::::::: -->
  1034       <!--  -->
  1035       <method name="openPopup">
  1036         <body><![CDATA[
  1037           if (!this.mMenuOpen && this.focused &&
  1038               (this.getResultCount() >= this.minResultsForPopup ||
  1039                this.mFailureItems)) {
  1040             var w = this.boxObject.width;
  1041             if (w != this.popup.boxObject.width)
  1042               this.popup.setAttribute("width", w);
  1043             this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft");
  1044             this.mMenuOpen = true;
  1046         ]]></body>
  1047       </method>
  1049       <!--  -->
  1050       <method name="closePopup">
  1051         <body><![CDATA[
  1052           if (this.popup && this.mMenuOpen) {
  1053             this.popup.hidePopup();
  1054             this.mMenuOpen = false;
  1056         ]]></body>
  1057       </method>
  1059       <!--  -->
  1060       <method name="addResultElements">
  1061         <parameter name="aSession"/>
  1062         <parameter name="aResults"/>
  1063         <body><![CDATA[
  1064           var count = aResults.errorDescription ? 1 : aResults.matchCount;
  1065           if (this.focused && this.showPopup) {
  1066             var row = 0;
  1067             for (var name in this.mSessions) {
  1068               row += this.mLastRows[name];
  1069               if (name == aSession)
  1070                 break;
  1072             this.view.updateResults(row, count - this.mLastRows[name]);
  1073             this.popup.adjustHeight();
  1075           this.mLastResults[aSession] = aResults;
  1076           this.mLastRows[aSession] = count;
  1077         ]]></body>
  1078       </method>
  1080       <!--  -->
  1081       <method name="clearResultElements">
  1082         <parameter name="aInvalidate"/>
  1083         <body><![CDATA[
  1084           for (var name in this.mSessions)
  1085             this.mLastRows[name] = 0;
  1086           this.view.clearResults();
  1087           if (aInvalidate)
  1088             this.popup.adjustHeight();
  1090           this.noMatch = true;
  1091         ]]></body>
  1092       </method>
  1094       <!--  -->
  1095       <method name="setTextValue">
  1096         <parameter name="aValue"/>
  1097         <body><![CDATA[
  1098           this.value = aValue;
  1100           // Completing a result should simulate the user typing the result,
  1101           // so fire an input event.
  1102           var evt = document.createEvent("UIEvents");
  1103           evt.initUIEvent("input", true, false, window, 0);
  1104           var oldIgnoreInput = this.ignoreInputEvent;
  1105           this.ignoreInputEvent = true;
  1106           this.dispatchEvent(evt);
  1107           this.ignoreInputEvent = oldIgnoreInput;
  1108         ]]></body>
  1109       </method>
  1111       <!--  -->
  1112       <method name="clearResultData">
  1113         <body><![CDATA[
  1114           for (var name in this.mSessions)
  1115             this.mLastResults[name] = null;
  1116         ]]></body>
  1117       </method>
  1119       <!-- ::::::::::::: miscellaneous ::::::::::::: -->
  1121       <!--  -->
  1122       <method name="ifSetAttribute">
  1123         <parameter name="aAttr"/>
  1124         <parameter name="aVal"/>
  1125         <body><![CDATA[
  1126           if (!this.hasAttribute(aAttr))
  1127             this.setAttribute(aAttr, aVal);
  1128         ]]></body>
  1129       </method>
  1131       <!--  -->
  1132       <method name="clearTimer">
  1133         <body><![CDATA[
  1134           if (this.mAutoCompleteTimer) {
  1135             clearTimeout(this.mAutoCompleteTimer);
  1136             this.mAutoCompleteTimer = 0;
  1138         ]]></body>
  1139       </method>
  1141       <!-- ::::::::::::: event dispatching ::::::::::::: -->
  1143       <method name="_fireEvent">
  1144         <parameter name="aEventType"/>
  1145         <parameter name="aEventParam"/>
  1146         <parameter name="aTriggeringEvent"/>
  1147         <body>
  1148         <![CDATA[
  1149           var noCancel = true;
  1150           // handle any xml attribute event handlers
  1151           var handler = this.getAttribute("on"+aEventType);
  1152           if (handler) {
  1153             var fn = new Function("eventParam", "domEvent", handler);
  1154             var returned = fn.apply(this, [aEventParam, aTriggeringEvent]);
  1155             if (returned == false)
  1156               noCancel = false;
  1159           return noCancel;
  1160         ]]>
  1161         </body>
  1162       </method>
  1164       <!-- =================== TREE VIEW =================== -->
  1166       <field name="view"><![CDATA[
  1167         ({
  1168           mTextbox: this,
  1169           mTree: null,
  1170           mSelection: null,
  1171           mRowCount: 0,
  1173           clearResults: function()
  1175             var oldCount = this.mRowCount;
  1176             this.mRowCount = 0;  
  1178             if (this.mTree) {
  1179               this.mTree.rowCountChanged(0, -oldCount);
  1180               this.mTree.scrollToRow(0);
  1182           },
  1184           updateResults: function(aRow, aCount)
  1186             this.mRowCount += aCount;
  1188             if (this.mTree)
  1189               this.mTree.rowCountChanged(aRow, aCount);
  1190           },
  1192           //////////////////////////////////////////////////////////
  1193           // nsIAutoCompleteController interface
  1195           // this is the only method required by the treebody mouseup handler
  1196           handleEnter: function(aIsPopupSelection) {
  1197             this.mTextbox.onResultClick();
  1198           },
  1200           //////////////////////////////////////////////////////////
  1201           // nsITreeView interface
  1203           get rowCount() {
  1204             return this.mRowCount;
  1205           },
  1207           get selection() {
  1208             return this.mSelection;
  1209           },
  1211           set selection(aVal) {
  1212             return this.mSelection = aVal;
  1213           },
  1215           setTree: function(aTree)
  1217             this.mTree = aTree;
  1218           },
  1220           getCellText: function(aRow, aCol)
  1222             for (var name in this.mTextbox.mSessions) {
  1223               if (aRow < this.mTextbox.mLastRows[name]) {
  1224                 var result = this.mTextbox.mLastResults[name];
  1225                 switch (aCol.id) {
  1226                   case "treecolAutoCompleteValue":
  1227                     return result.errorDescription || result.getLabelAt(aRow);
  1228                   case "treecolAutoCompleteComment":
  1229                     if (!result.errorDescription)
  1230                       return result.getCommentAt(aRow);
  1231                   default:
  1232                     return "";
  1235               aRow -= this.mTextbox.mLastRows[name];
  1237             return "";
  1238           },
  1240           getRowProperties: function(aIndex)
  1242             return "";
  1243           },
  1245           getCellProperties: function(aIndex, aCol)
  1247             // for the value column, append nsIAutoCompleteItem::className
  1248             // to the property list so that we can style this column
  1249             // using that property
  1250             if (aCol.id == "treecolAutoCompleteValue") {
  1251               for (var name in this.mTextbox.mSessions) {
  1252                 if (aIndex < this.mTextbox.mLastRows[name]) {
  1253                   var result = this.mTextbox.mLastResults[name];
  1254                   if (result.errorDescription)
  1255                     return "";
  1256                   return result.getStyleAt(aIndex);
  1258                 aIndex -= this.mTextbox.mLastRows[name];
  1261             return "";
  1262           },
  1264           getColumnProperties: function(aCol)
  1266             return "";
  1267           },
  1269           getImageSrc: function(aRow, aCol)
  1271             if (aCol.id == "treecolAutoCompleteValue") {
  1272               for (var name in this.mTextbox.mSessions) {
  1273                 if (aRow < this.mTextbox.mLastRows[name]) {
  1274                   var result = this.mTextbox.mLastResults[name];
  1275                   if (result.errorDescription)
  1276                     return "";
  1277                   return result.getImageAt(aRow);
  1279                 aRow -= this.mTextbox.mLastRows[name];
  1282             return "";
  1283           },
  1285           getParentIndex: function(aRowIndex) { },
  1286           hasNextSibling: function(aRowIndex, aAfterIndex) { },
  1287           getLevel: function(aIndex) {},
  1288           getProgressMode: function(aRow, aCol) {},
  1289           getCellValue: function(aRow, aCol) {},
  1290           isContainer: function(aIndex) {},
  1291           isContainerOpen: function(aIndex) {},
  1292           isContainerEmpty: function(aIndex) {},
  1293           isSeparator: function(aIndex) {},
  1294           isSorted: function() {},
  1295           toggleOpenState: function(aIndex) {},
  1296           selectionChanged: function() {},
  1297           cycleHeader: function(aCol) {},
  1298           cycleCell: function(aRow, aCol) {},
  1299           isEditable: function(aRow, aCol) {},
  1300           isSelectable: function(aRow, aCol) {},
  1301           setCellValue: function(aRow, aCol, aValue) {},
  1302           setCellText: function(aRow, aCol, aValue) {},
  1303           performAction: function(aAction) {},
  1304           performActionOnRow: function(aAction, aRow) {},
  1305           performActionOnCell: function(aAction, aRow, aCol) {}
  1306         });
  1307       ]]></field>
  1309     </implementation>
  1311     <handlers>
  1312       <handler event="input"
  1313                action="if (!this.ignoreInputEvent) this.processInput();"/>
  1315       <handler event="keypress" phase="capturing"
  1316                 action="return this.processKeyPress(event);"/>
  1318       <handler event="compositionstart" phase="capturing"
  1319                 action="this.processStartComposition();"/>
  1321       <handler event="focus" phase="capturing"
  1322                action="this.userAction = 'typing';"/>
  1324       <handler event="blur" phase="capturing"
  1325                action="if ( !(this.ignoreBlurWhileSearching &amp;&amp; this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/>
  1327       <handler event="mousedown" phase="capturing"
  1328                action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/>
  1329     </handlers>
  1330   </binding>
  1332   <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup">
  1333     <resources>
  1334       <stylesheet src="chrome://global/content/autocomplete.css"/>
  1335       <stylesheet src="chrome://global/skin/autocomplete.css"/>
  1336     </resources>
  1338     <content ignorekeys="true" level="top">
  1339       <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1">
  1340         <xul:treecols anonid="treecols">
  1341           <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/>
  1342           <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/>
  1343         </xul:treecols>
  1344         <xul:treechildren anonid="treebody" class="autocomplete-treebody"/>
  1345       </xul:tree>
  1346     </content>
  1348     <implementation implements="nsIAutoCompletePopup">
  1349       <constructor><![CDATA[
  1350         if (this.textbox && this.textbox.view)
  1351           this.initialize();
  1352       ]]></constructor>
  1354       <destructor><![CDATA[
  1355         if (this.view)
  1356           this.tree.view = null;
  1357       ]]></destructor>
  1359       <field name="textbox">
  1360         document.getBindingParent(this);
  1361       </field>
  1363       <field name="tree">
  1364         document.getAnonymousElementByAttribute(this, "anonid", "tree");
  1365       </field>
  1367       <field name="treecols">
  1368         document.getAnonymousElementByAttribute(this, "anonid", "treecols");
  1369       </field>
  1371       <field name="treebody">
  1372         document.getAnonymousElementByAttribute(this, "anonid", "treebody");
  1373       </field>
  1375       <field name="view">
  1376         null
  1377       </field>
  1379       <!-- Setting tree.view doesn't always immediately create a selection,
  1380            so we ensure the selection by asking the tree for the view. Note:
  1381            this.view.selection is quicker if we know the selection exists. -->
  1382       <property name="selection" onget="return this.tree.view.selection;"/>
  1384      <property name="pageCount"
  1385                onget="return this.tree.treeBoxObject.getPageLength();"/>
  1387      <field name="maxRows">0</field>
  1388      <field name="mLastRows">0</field>
  1390       <method name="initialize">
  1391         <body><![CDATA[
  1392         this.showCommentColumn = this.textbox.showCommentColumn;
  1393         this.tree.view = this.textbox.view;
  1394         this.view = this.textbox.view;
  1395         this.maxRows = this.textbox.maxRows;
  1396         ]]></body>
  1397       </method>
  1399       <property name="showCommentColumn"
  1400                 onget="return !this.treecols.lastChild.hidden;"
  1401                 onset="this.treecols.lastChild.hidden = !val; return val;"/>
  1403       <method name="adjustHeight">
  1404         <body><![CDATA[
  1405           // detect the desired height of the tree
  1406           var bx = this.tree.treeBoxObject;
  1407           var view = this.view;
  1408           var rows = this.maxRows || 6;
  1409           if (!view.rowCount || (rows && view.rowCount < rows))
  1410             rows = view.rowCount;
  1412           var height = rows * bx.rowHeight;
  1414           if (height == 0)
  1415             this.tree.setAttribute("collapsed", "true");
  1416           else {
  1417             if (this.tree.hasAttribute("collapsed"))
  1418               this.tree.removeAttribute("collapsed");
  1419             this.tree.setAttribute("height", height);
  1421         ]]></body>
  1422       </method>
  1424       <method name="clearSelection">
  1425         <body>
  1426           this.selection.clearSelection();
  1427         </body>
  1428       </method>
  1430       <method name="getNextIndex">
  1431         <parameter name="aReverse"/>
  1432         <parameter name="aPage"/>
  1433         <parameter name="aIndex"/>
  1434         <parameter name="aMaxRow"/>
  1435         <body><![CDATA[
  1436           if (aMaxRow < 0)
  1437             return -1;
  1439           if (aIndex == -1)
  1440             return aReverse ? aMaxRow : 0;
  1441           if (aIndex == (aReverse ? 0 : aMaxRow))
  1442             return -1;
  1444           var amount = aPage ? this.pageCount - 1 : 1;
  1445           aIndex = aReverse ? aIndex - amount : aIndex + amount;
  1446           if (aIndex > aMaxRow)
  1447             return aMaxRow;
  1448           if (aIndex < 0)
  1449             return 0;
  1450           return aIndex;
  1451         ]]></body>
  1452       </method>
  1454       <!-- =================== nsIAutoCompletePopup =================== -->
  1456       <field name="input">
  1457         null
  1458       </field>
  1460       <!-- This property is meant to be overriden by bindings extending
  1461            this one.  When the user selects an item from the list by
  1462            hitting enter or clicking, this method can set the value
  1463            of the textbox to a different value if it wants to. -->
  1464       <property name="overrideValue" readonly="true" onget="return null;"/>
  1466       <property name="selectedIndex">
  1467         <getter>
  1468           if (!this.view || !this.selection.count)
  1469             return -1;
  1470           var start = {}, end = {};
  1471           this.view.selection.getRangeAt(0, start, end);
  1472           return start.value;
  1473         </getter>
  1474         <setter>
  1475           if (this.view) {
  1476             this.selection.select(val);
  1477             if (val >= 0) {
  1478               this.view.selection.currentIndex = -1;
  1479               this.tree.treeBoxObject.ensureRowIsVisible(val);
  1482           return val;
  1483         </setter>
  1484       </property>
  1486       <property name="popupOpen" onget="return !!this.input;" readonly="true"/>
  1488       <method name="openAutocompletePopup">
  1489         <parameter name="aInput"/>
  1490         <parameter name="aElement"/>
  1491         <body><![CDATA[
  1492           if (!this.input) {
  1493             this.tree.view = aInput.controller;
  1494             this.view = this.tree.view;
  1495             this.showCommentColumn = aInput.showCommentColumn;
  1496             this.maxRows = aInput.maxRows;
  1497             this.invalidate();
  1499             var viewer = aElement
  1500                 .ownerDocument
  1501                 .defaultView
  1502                 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  1503                 .getInterface(Components.interfaces.nsIWebNavigation)
  1504                 .QueryInterface(Components.interfaces.nsIDocShell)
  1505                 .contentViewer
  1506                 .QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
  1507             var rect = aElement.getBoundingClientRect();
  1508             var width = Math.round((rect.right - rect.left) * viewer.fullZoom);
  1509             this.setAttribute("width", width > 100 ? width : 100);
  1510             // Adjust the direction (which is not inherited) of the autocomplete
  1511             // popup list, based on the textbox direction. (Bug 707039)
  1512             this.style.direction = aElement.ownerDocument.defaultView
  1513                                            .getComputedStyle(aElement)
  1514                                            .direction;
  1515             const nsIPopupBoxObject = Components.interfaces.nsIPopupBoxObject;
  1516             this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent
  1517               ? nsIPopupBoxObject.ROLLUP_CONSUME
  1518               : nsIPopupBoxObject.ROLLUP_NO_CONSUME);
  1519             this.openPopup(aElement, "after_start", 0, 0, false, false);
  1520             if (this.state != "closed")
  1521               this.input = aInput;
  1523         ]]></body>
  1524       </method>
  1526       <method name="closePopup">
  1527         <body>
  1528           this.hidePopup();
  1529         </body>
  1530       </method>
  1532       <method name="invalidate">
  1533         <body>
  1534           if (this.view)
  1535             this.adjustHeight();
  1536           this.tree.treeBoxObject.invalidate();
  1537         </body>
  1538       </method>
  1540       <method name="selectBy">
  1541         <parameter name="aReverse"/>
  1542         <parameter name="aPage"/>
  1543         <body><![CDATA[
  1544           try {
  1545             return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1);
  1546           } catch (ex) {
  1547             // do nothing - occasionally timer-related js errors happen here
  1548             // e.g. "this.selectedIndex has no properties", when you type fast and hit a
  1549             // navigation key before this popup has opened
  1550             return -1;
  1552         ]]></body>
  1553       </method>
  1554     </implementation>
  1556     <handlers>
  1557       <handler event="popupshowing">
  1558         if (this.textbox)
  1559           this.textbox.mMenuOpen = true;
  1560       </handler>
  1562       <handler event="popuphiding">
  1563         if (this.textbox)
  1564           this.textbox.mMenuOpen = false;
  1565         this.clearSelection();
  1566         this.input = null;
  1567       </handler>
  1568     </handlers>
  1569   </binding>
  1571   <binding id="autocomplete-treebody">
  1572     <implementation>
  1573       <field name="popup">document.getBindingParent(this);</field>
  1575       <field name="mLastMoveTime">Date.now()</field>
  1576     </implementation>
  1578     <handlers>
  1579       <handler event="mouseout" action="this.popup.selectedIndex = -1;"/>
  1581       <handler event="mouseup"><![CDATA[
  1582         var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
  1583         if (rc != -1) {
  1584           this.popup.selectedIndex = rc;
  1585           this.popup.view.handleEnter(true);
  1587       ]]></handler>
  1589       <handler event="mousemove"><![CDATA[
  1590         if (Date.now() - this.mLastMoveTime > 30) {
  1591           var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
  1592           if (rc != -1 && rc != this.popup.selectedIndex) 
  1593             this.popup.selectedIndex = rc;
  1594           this.mLastMoveTime = Date.now();
  1596       ]]></handler>
  1597     </handlers>
  1598   </binding>
  1600   <binding id="autocomplete-history-popup"
  1601            extends="chrome://global/content/bindings/popup.xml#popup-scrollbars">
  1602     <resources>
  1603       <stylesheet src="chrome://global/content/autocomplete.css"/>
  1604       <stylesheet src="chrome://global/skin/autocomplete.css"/>
  1605     </resources>
  1607     <implementation>
  1608       <method name="removeOpenAttribute">
  1609         <parameter name="parentNode"/>
  1610         <body><![CDATA[
  1611           parentNode.removeAttribute("open");
  1612         ]]></body>
  1613       </method>
  1614     </implementation>
  1616     <handlers>
  1617       <handler event="popuphiding"><![CDATA[
  1618         setTimeout(this.removeOpenAttribute, 0, this.parentNode);
  1619       ]]></handler>
  1620     </handlers>
  1621   </binding>
  1623   <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
  1625     <implementation>
  1626       <method name="showPopup">
  1627         <body><![CDATA[
  1628           var textbox = document.getBindingParent(this);
  1629           var kids = textbox.getElementsByClassName("autocomplete-history-popup");
  1630           if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup
  1631             var w = textbox.boxObject.width;
  1632             if (w != kids[0].boxObject.width)
  1633               kids[0].width = w;
  1634             kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft");
  1635             textbox.setAttribute("open", "true");
  1637         ]]></body>
  1638       </method>
  1639     </implementation>
  1641     <handlers>
  1642       <handler event="mousedown"><![CDATA[
  1643         this.showPopup();
  1644       ]]></handler>
  1645     </handlers>
  1646   </binding>
  1648 </bindings>

mercurial