Bug 896213 Remove XPFE autocomplete interface usage from the XBL binding r=IanN
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Mon, 12 Aug 2013 23:49:35 +0100
changeset 142288 73ec9b19228ac76a06599bc393783f4298478d9e
parent 142287 716e54fb5d09e7f598aa64920c27fee2c4f449d0
child 142289 cf1c3a76352d731ab402cf0d622a374e2c716a23
push id32364
push userneil@parkwaycc.co.uk
push dateMon, 12 Aug 2013 22:58:55 +0000
treeherdermozilla-inbound@f3e583c68088 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersIanN
bugs896213
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 896213 Remove XPFE autocomplete interface usage from the XBL binding r=IanN
xpfe/components/autocomplete/resources/content/autocomplete.xml
--- a/xpfe/components/autocomplete/resources/content/autocomplete.xml
+++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml
@@ -63,18 +63,16 @@
         this.ifSetAttribute("pastetimeout", 1000);
         this.ifSetAttribute("maxrows", 5);
         this.ifSetAttribute("showpopup", true);
         this.ifSetAttribute("disableKeyNavigation", true);
         
         // initialize the search sessions
         if (this.hasAttribute("autocompletesearch"))
           this.initAutoCompleteSearch();
-        if (this.hasAttribute("searchSessions"))
-          this.initSearchSessions();
         
         // hack to work around lack of bottom-up constructor calling
         if ("initialize" in this.popup)
           this.popup.initialize();
       ]]></constructor>
 
       <destructor><![CDATA[
         this.clearResults(false);
@@ -234,52 +232,29 @@
           this.mInputElt.dispatchEvent(event);
           return val;
         ]]></setter>
       </property>
 
       <property name="focused"
                 onget="return this.getAttribute('focused') == 'true';"/>
 
-      <!-- space-delimited string of search session types to use -->
-      <property name="searchSessions" readonly="true" onget="return this.getAttribute('searchSessions') || ''"/>
-
-      <method name="initSearchSessions">
-        <body><![CDATA[
-          var list = this.searchSessions.split(" ");
-          for (var i = 0; i < list.length; i++) {
-            var name = list[i];
-            var contractid = "@mozilla.org/autocompleteSession;1?type=" + name;
-            if (contractid in Components.classes) {
-              try {
-                var session =
-                  Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSession);
-                this.addSession(session, name);
-              } catch (e) {
-                dump("### ERROR - unable to create search session \"" + name + "\".\n");
-              }
-            } else {
-              dump("search session \"" + name + "\" not found - skipping.\n");
-            }
-          }
-        ]]></body>
-      </method>
-
       <method name="initAutoCompleteSearch">
         <body><![CDATA[
           var list = this.getAttribute("autocompletesearch").split(" ");
           for (var i = 0; i < list.length; i++) {
             var name = list[i];
             var contractid = "@mozilla.org/autocomplete/search;1?name=" + name;
             if (contractid in Components.classes) {
               try {
-                var search =
+                this.mSessions[name] =
                   Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch);
-                var session = new (this.mAutoCompleteSession)(search);
-                this.addSession(session, name);
+                this.mLastResults[name] = null;
+                this.mLastRows[name] = 0;
+                ++this.sessionCount;
               } catch (e) {
                 dump("### ERROR - unable to create search \"" + name + "\".\n");
               }
             } else {
               dump("search \"" + name + "\" not found - skipping.\n");
             }
           }
         ]]></body>
@@ -340,19 +315,18 @@
 
       <!-- state which indicates a search timeout is current waiting -->
       <property name="isWaiting" 
                 onget="return this.mAutoCompleteTimer != 0;"/>
 
       <!-- =================== PRIVATE PROPERTIES =================== -->
 
       <field name="mSessions">({})</field>
-      <field name="mListeners">({})</field>
       <field name="mLastResults">({})</field>
-      <field name="mLastStatus">({})</field>
+      <field name="mLastRows">({})</field>
       <field name="mLastKeyCode">null</field>
       <field name="mAutoCompleteTimer">0</field>
       <field name="mMenuOpen">false</field>
       <field name="mFireAfterSearch">false</field>
       <field name="mFinishAfterSearch">false</field>
       <field name="mNeedToFinish">false</field>
       <field name="mNeedToComplete">false</field>
       <field name="mTransientValue">false</field>
@@ -397,261 +371,80 @@
               this.self.closePopup();
             } catch (e) {
               window.top.removeEventListener("DOMMenuBarActive", this, true);
             }
           }
         })
       ]]></field>
 
-      <field name="mAutoCompleteSession"><![CDATA[
-        var session = function(aSession) { this.session = aSession };
-        session.prototype = {
-          session: null,
-          param: this,
-          onStartLookup: function(aSearchString, aPreviousSearchResult, aListener) {
-            this.session.startSearch(aSearchString,
-                         this.param.searchParam,
-                         aPreviousSearchResult && aPreviousSearchResult.lastResult,
-                         aListener);
-          },
-          onStopLookup: function() {
-            this.session.stopSearch();
-          },
-          onAutoComplete: function() {
+      <field name="mAutoCompleteObserver"><![CDATA[
+        ({
+          self: this,
+          onSearchResult: function(aSearch, aResult) {
+            for (var name in this.self.mSessions)
+              if (this.self.mSessions[name] == aSearch)
+                this.self.processResults(name, aResult);
           }
-        };
-        session;
-      ]]></field>
-
-      <field name="mAutoCompleteListener"><![CDATA[
-        var listener = function(aName) { this.sessionName = aName };
-        listener.prototype = {
-          param: this,
-          sessionName: null,
-          lastResult: null,
-          lastItems: [],
-          onAutoComplete: function(aResults, aStatus) 
-          {
-            this.param.processResults(this.sessionName, aResults, aStatus);
-          },
-          onSearchResult: function(aSearch, aResult)
-          {
-            this.lastResult = aResult;
-            this.lastItems = new Array(aResult.matchCount);
-            const nsIAutoCompleteStatus = Components.interfaces.nsIAutoCompleteStatus;
-            var status = nsIAutoCompleteStatus.failed;
-            if (aResult.errorDescription) {
-              status = nsIAutoCompleteStatus.failureItems;
-              this.lastItems = [{
-                value: aResult.errorDescription,
-                comment: null,
-                className: null,
-                param: null
-              }];
-            }
-            else if (aResult.searchResult == aResult.RESULT_IGNORED)
-              status = nsIAutoCompleteStatus.ignored;
-            else if (aResult.searchResult == aResult.RESULT_NOMATCH)
-              status = nsIAutoCompleteStatus.noMatch;
-            else if (aResult.searchResult == aResult.RESULT_SUCCESS)
-              status = nsIAutoCompleteStatus.matchFound;
-            else if (aResult.searchResult == aResult.RESULT_NOMATCH_ONGOING)
-              status = nsIAutoCompleteStatus.noMatchYet;
-            else if (aResult.searchResult == aResult.RESULT_SUCCESS_ONGOING)
-              status = nsIAutoCompleteStatus.matchSoFar;
-            this.param.processResults(this.sessionName, {
-              searchString: aResult.searchString,
-              items: this,
-              defaultItemIndex: aResult.defaultIndex,
-              param: null,
-              lastResult: aResult
-            }, status);
-          },
-          Count: function() {
-            return this.lastItems.length;
-          },
-          QueryElementAt: function(aIndex, aIID) {
-            if (aIndex < 0 || aIndex >= this.lastItems.length)
-              return null;
-            if (!this.lastItems[aIndex]) {
-              this.lastItems[aIndex] = {
-                value: this.lastResult.getValueAt(aIndex),
-                comment: this.lastResult.getCommentAt(aIndex),
-                className: this.lastResult.getStyleAt(aIndex),
-                param: null
-              }
-            }
-            return this.lastItems[aIndex];
-          }
-        };
-        listener;
+        })
       ]]></field>
 
       <field name="mInputElt"><![CDATA[
         document.getAnonymousElementByAttribute(this, "anonid", "input");
       ]]></field>
 
       <field name="mMenuAccessKey"><![CDATA[
         Components.classes["@mozilla.org/preferences-service;1"]
                  .getService(Components.interfaces.nsIPrefBranch)
                  .getIntPref("ui.key.menuAccessKey");
       ]]></field>
 
       <!-- =================== PUBLIC METHODS =================== -->
 
-      <!-- get the result object from the autocomplete results from a specific session -->
-      <method name="getResultAt">
+      <method name="getErrorAt">
         <parameter name="aIndex"/>
         <body><![CDATA[
-          var obj = this.convertIndexToSession(aIndex);
-          if (obj && this.mLastResults[obj.session]) {
-            const nsIAutoCompleteItem = Components.interfaces.nsIAutoCompleteItem;
-            if (obj.index >= 0) {
-              var item = this.mLastResults[obj.session].items.QueryElementAt(obj.index, nsIAutoCompleteItem);
-              return item;
-            }
-          }
-          return null;
+          var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex);
+          return obj && this.mLastResults[obj.session] &&
+                        this.mLastResults[obj.session].errorDescription;
         ]]></body>
       </method>
 
-      <!-- get the autocomplete session status returned by the session
-           that a given item came from -->
-      <method name="getSessionStatusAt">
-        <parameter name="aIndex"/>
-        <body><![CDATA[
-          var obj = this.convertIndexToSession(aIndex);
-          return obj ? this.mLastStatus[obj.session] : null;
-        ]]></body>
-      </method>
-
-
       <!-- get a value from the autocomplete results as a string via an absolute index-->
       <method name="getResultValueAt">
         <parameter name="aIndex"/>
         <body><![CDATA[
           var obj = this.convertIndexToSession(aIndex);
           return obj ? this.getSessionValueAt(obj.session, obj.index) : null;
         ]]></body>
       </method>
 
-      <!-- get the result object from the autocomplete results from a specific session -->
-      <method name="getSessionResultAt">
-        <parameter name="aSession"/>
-        <parameter name="aIndex"/>
-        <body><![CDATA[
-          var session = this.mLastResults[aSession];
-          if (session) {
-            var item = session.items.QueryElementAt(aIndex, Components.interfaces.nsIAutoCompleteItem);
-            return item;
-          }
-          return null;
-        ]]></body>
-      </method>
-
       <!-- get a value from the autocomplete results as a string from a specific session -->
       <method name="getSessionValueAt">
         <parameter name="aSession"/>
         <parameter name="aIndex"/>
         <body><![CDATA[
-          var result = this.getSessionResultAt(aSession, aIndex);
-          if (result)
-            return result.value;
-          return null;
+          var result = this.mLastResults[aSession];
+          return result.errorDescription || result.getValueAt(aIndex);
         ]]></body>
       </method>
 
       <!-- get the total number of results overall -->
       <method name="getResultCount">
         <body><![CDATA[
           return this.view.rowCount;
         ]]></body>
       </method>
 
-      <!-- get a session object by index -->
-      <method name="getSession">
-        <parameter name="aIndex"/>
-        <body><![CDATA[
-          var idx = -1;
-          for (var name in this.mSessions)
-            if (++idx == aIndex)
-              return this.mSessions[name];
-
-          return null;
-        ]]></body>
-      </method>
-
-      <!-- get a session object by name -->
-      <method name="getSessionByName">
-        <parameter name="aSessionName"/>
-        <body><![CDATA[
-          return this.mSessions[aSessionName];
-        ]]></body>
-      </method>
-
-      <!-- add a session by reference -->
-      <method name="addSession">
-        <parameter name="aSession"/>
-        <parameter name="aName"/>
-        <body><![CDATA[
-          ++this.sessionCount;
-          var name = aName || ("anon_" + this.sessionCount);
-          this.mSessions[name] = aSession;
-          this.mListeners[name] = new (this.mAutoCompleteListener)(name);
-          this.mLastResults[name] = null;
-          this.mLastStatus[name] = null;
-          return this.mSessions[name];
-        ]]></body>
-      </method>
-
-      <!-- remove a session by reference -->
-      <method name="removeSession">
-        <parameter name="aSession"/>
-        <body><![CDATA[
-          for (var name in this.mSessions) {
-            if (this.mSessions[name] == aSession) {
-              delete this.mSessions[name];
-              delete this.mListeners[name];
-              delete this.mLastResults[name];
-              delete this.mLastStatus[name];
-              --this.sessionCount;
-              break;
-            }
-          }
-        ]]></body>
-      </method>
-
-      <!-- make this widget listen to all of the same autocomplete sessions 
-           from another autocomplete widget -->
-      <method name="syncSessions">
-        <parameter name="aCopyFrom"/>
-        <body><![CDATA[
-          this.sessionCount = aCopyFrom.sessionCount;
-          this.mSessions = {};
-          this.mListeners = {};
-          this.mLastResults = {};
-          this.mLastStatus = {};
-          for (var name in aCopyFrom.mSessions) {
-            this.mSessions[name] = aCopyFrom.mSessions[name];
-            this.mListeners[name] = new (this.mAutoCompleteListener)(name);
-            this.mLastResults[name] = null;
-            this.mLastStatus[name] = null;
-          }
-        ]]></body>
-      </method>
-
       <!-- get the first session that has results -->
       <method name="getDefaultSession">
         <body><![CDATA[
           for (var name in this.mLastResults) {
             var results = this.mLastResults[name];
-            var status = this.mLastStatus[name];
-            if (results && results.items.Count() > 0 && status !=
-                Components.interfaces.nsIAutoCompleteStatus.failureItems)
+            if (results && results.matchCount > 0 && !results.errorDescription)
               return name;
           }
           return null;
         ]]></body>
       </method>
 
       <!-- empty the cached result data and empty the results popup -->
       <method name="clearResults">
@@ -684,20 +477,16 @@
           switch (aAction) {
             case "startLookup":
               me.startLookup();
               break;
 
             case "stopLookup":
               me.stopLookup();
               break;
-
-            case "autoComplete":
-              me.autoComplete();
-              break;
           }
         ]]></body>
       </method>
 
       <!--  -->
       <method name="startLookup">
         <body><![CDATA[
           var str = this.currentSearchString;
@@ -714,89 +503,76 @@
           this.mDefaultMatchFilled = false; // clear out our prefill state.
 
           // Notify the input that the search is beginning.
           this.onSearchBegin();
 
           // tell each session to start searching...
           for (var name in this.mSessions)
             try {
-              this.mSessions[name].onStartLookup(str, this.mLastResults[name], this.mListeners[name]);
+              this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver);
             } catch (e) {
               --this.mSessionReturns;
               this.searchFailed();
             }
         ]]></body>
       </method>
 
       <!--  -->
       <method name="stopLookup">
         <body><![CDATA[
           for (var name in this.mSessions)
-            this.mSessions[name].onStopLookup();
-        ]]></body>
-      </method>
-
-      <!--  -->
-      <method name="autoComplete">
-        <body><![CDATA[
-          for (var name in this.mSessions)
-            this.mSessions[name].onAutoComplete(this.value, 
-                                                this.mLastResults[name],
-                                                this.mListeners[name]);
+            this.mSessions[name].stopSearch();
         ]]></body>
       </method>
 
       <!--  -->
       <method name="processResults">
         <parameter name="aSessionName"/>
         <parameter name="aResults"/>
-        <parameter name="aStatus"/>
         <body><![CDATA[
           if (this.disableAutoComplete)
             return;
 
-          if (aStatus != Components.interfaces.nsIAutoCompleteStatus.noMatchYet &&
-              aStatus != Components.interfaces.nsIAutoCompleteStatus.matchSoFar)
+          const ACR = Components.interfaces.nsIAutoCompleteResult;
+          var status = aResults.searchResult;
+          if (status != ACR.RESULT_NOMATCH_ONGOING &&
+              status != ACR.RESULT_SUCCESS_ONGOING)
             --this.mSessionReturns;
 
-          this.mLastStatus[aSessionName] = aStatus;
-
           // check the many criteria for failure
-          if (aStatus == Components.interfaces.nsIAutoCompleteStatus.failed ||
-              aStatus == Components.interfaces.nsIAutoCompleteStatus.ignored || 
-              aStatus == Components.interfaces.nsIAutoCompleteStatus.noMatch ||
-              aStatus == Components.interfaces.nsIAutoCompleteStatus.noMatchYet ||
-              aResults == null ||
-              aResults.items.Count() == 0 ||
-              aResults.searchString != this.currentSearchString)
+          if (aResults.errorDescription)
+            ++this.mFailureItems;
+          else if (status == ACR.RESULT_IGNORED ||
+                   status == ACR.RESULT_FAILURE ||
+                   status == ACR.RESULT_NOMATCH ||
+                   status == ACR.RESULT_NOMATCH_ONGOING ||
+                   aResults.matchCount == 0 ||
+                   aResults.searchString != this.currentSearchString)
           {
             this.mLastResults[aSessionName] = null;
             if (this.mFirstReturn)
               this.clearResultElements(false);
             this.mFirstReturn = false;
             this.searchFailed();
             return;
           }
-          if (aStatus == Components.interfaces.nsIAutoCompleteStatus.failureItems)
-            ++this.mFailureItems;
           
           if (this.mFirstReturn) {
             if (this.view.mTree)
               this.view.mTree.beginUpdateBatch();
             this.clearResultElements(false); // clear results, but don't repaint yet
           }
 
           // always call openPopup...we may not have opened it
           // if a previous search session didn't return enough search results.
           // it's smart and doesn't try to open itself multiple times...
           // be sure to add our result elements before calling openResultPopuup as we need
           // to know the total # of results found so far.
-          this.addResultElements(aResults, this.mLastResults[aSessionName]);
-          this.mLastResults[aSessionName] = aResults;
+          this.addResultElements(aSessionName, aResults);
 
           this.autoFillInput(aSessionName, aResults, false);
           if (this.mFirstReturn && this.view.mTree)
             this.view.mTree.endUpdateBatch();
           this.openPopup();
           this.mFirstReturn = false;
          
           // if this is the last session to return... 
@@ -833,17 +609,18 @@
       <method name="postSearchCleanup">
         <body><![CDATA[
           this.isSearching = false;
 
           // figure out if there are no matches in all search sessions
           var failed = true;
           for (var name in this.mSessions) {
             if (this.mLastResults[name])
-              failed = this.mLastResults[name].items.Count() < 1;
+              failed = this.mLastResults[name].errorDescription ||
+                       this.mLastResults[name].matchCount == 0;
             if (!failed)
               break;
           }
           this.noMatch = failed;
           
           // if we have processed all of our searches, and none of them gave us a default index,
           // then we should try to auto fill the input field with the first match. 
           // note: autoFillInput is smart enough to kick out if we've already prefilled something...
@@ -885,19 +662,20 @@
               this.mFinishAfterSearch = true;
               this.mFireAfterSearch = aFireTextCommand;
               return;
             } else {
               // we want to use the default item index for the first session which gave us a valid
               // default item index...
               for (var name in this.mLastResults) {
                 var results = this.mLastResults[name];
-                if (results && results.items.Count() > 0 && results.defaultItemIndex != -1)
+                if (results && results.matchCount > 0 &&
+                    !results.errorDescription && results.defaultIndex != -1)
                 {
-                  val = this.getSessionValueAt(name, results.defaultItemIndex);
+                  val = this.getSessionValueAt(name, results.defaultIndex);
                   this.setTextValue(val);
                   this.mDefaultMatchFilled = true;
                   this.mNeedToFinish = false;
                   break;
                 }
               }
 
               if (this.mNeedToFinish) {
@@ -918,53 +696,45 @@
                     this.setTextValue(first);
                     this.mDefaultMatchFilled = true;
                 }
               }
             }
 
             this.stopLookup();
 
-            if (!this.noMatch)
-              this.autoComplete();
-
             this.closePopup();
           }
           
           this.mNeedToComplete = false;
           this.clearTimer();
 
           if (aFireTextCommand)
             this._fireEvent("textentered", this.userAction, aTriggeringEvent);
         ]]></body>
       </method>
 
       <!--  when the user clicks an entry in the autocomplete popup -->
       <method name="onResultClick">
         <body><![CDATA[
          // set textbox value to either override value, or the clicked result
-          var errItem=null;
+          var errItem = this.getErrorAt(this.popup.selectedIndex);
           var val = this.popup.overrideValue;
             if (val)
               this.setTextValue(val);
           else if (this.popup.selectedIndex != -1) {
-            if (this.getSessionStatusAt(this.popup.selectedIndex) ==
-                Components.interfaces.nsIAutoCompleteStatus.failureItems) {
+            if (errItem) {
               this.setTextValue(this.currentSearchString);
               this.mTransientValue = true;
-              errItem = this.getResultAt(this.popup.selectedIndex);
             } else { 
               this.setTextValue(this.getResultValueAt(
                                              this.popup.selectedIndex));
             }
           }
 
-          if (!this.noMatch)
-            this.autoComplete();
-
           this.mNeedToFinish = false;
           this.mNeedToComplete = false;
           
           this.closePopup();
 
           this.currentSearchString = "";
 
           if (errItem)
@@ -988,24 +758,20 @@
           this.mNeedToComplete = false;
         ]]></body>
       </method>
 
       <!-- convert an absolute result index into a session name/index pair -->
       <method name="convertIndexToSession">
         <parameter name="aIndex"/>
         <body><![CDATA[
-          var idx = 0;
-          for (var name in this.mLastResults) {
-            if (this.mLastResults[name]) {
-              if ((idx+this.mLastResults[name].items.Count())-1 >= aIndex) {
-                return {session: name, index: aIndex-idx};
-              }
-              idx += this.mLastResults[name].items.Count();
-            }
+          for (var name in this.mLastRows) {
+            if (aIndex < this.mLastRows[name])
+              return { session: name, index: aIndex };
+            aIndex -= this.mLastRows[name];
           }
           return null;
         ]]></body>
       </method>
 
       <!-- ::::::::::::: user input handling ::::::::::::: -->
 
       <!--  -->
@@ -1051,22 +817,17 @@
                     this.keyNavigation(aEvent);
                 }
               } 
               break;              
               
             case KeyEvent.DOM_VK_RETURN:
 
               // if this is a failure item, save it for fireErrorCommand
-              var errItem = null;
-              if (this.popup.selectedIndex != -1 && 
-                  this.getSessionStatusAt(this.popup.selectedIndex) ==
-                  Components.interfaces.nsIAutoCompleteStatus.failureItems) {
-                errItem = this.getResultAt(this.popup.selectedIndex);
-              }
+              var errItem = this.getErrorAt(this.popup.selectedIndex);
 
               killEvent = this.mMenuOpen;
               this.finishAutoComplete(true, true, aEvent);
               this.closePopup();
               if (errItem) {
                   this._fireEvent("errorcommand", errItem);
               }
               break;
@@ -1174,18 +935,17 @@
                           k == KeyEvent.DOM_VK_PAGE_UP;
             var page = k == KeyEvent.DOM_VK_PAGE_UP ||
                        k == KeyEvent.DOM_VK_PAGE_DOWN;
             var selected = this.popup.selectBy(reverse, page);
           
             // determine which value to place in the textbox
             this.ignoreInputEvent = true;
             if (selected != -1) {
-              if (this.getSessionStatusAt(selected) == 
-                  Components.interfaces.nsIAutoCompleteStatus.failureItems) { 
+              if (this.getErrorAt(selected)) {
                 if (this.currentSearchString)
                   this.setTextValue(this.currentSearchString);
               } else {
                 this.setTextValue(this.getResultValueAt(selected));
               }
               this.mTransientValue = true;
             } else {
               if (this.currentSearchString)
@@ -1211,17 +971,17 @@
           if (this.mInputElt.selectionEnd < this.currentSearchString.length ||
               this.mDefaultMatchFilled)
             return;
 
           if (!this.mFinishAfterSearch &&
               (this.autoFill || this.completeDefaultIndex) && 
               this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE &&
               this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) {
-            var indexToUse = aResults.defaultItemIndex;
+            var indexToUse = aResults.defaultIndex;
             if (aUseFirstMatchIfNoDefault && indexToUse == -1)
               indexToUse = 0; 
 
             if (indexToUse != -1) {
               var resultValue = this.getSessionValueAt(aSessionName, indexToUse);
               var match = resultValue.toLowerCase();
               var entry = this.currentSearchString.toLowerCase();
               this.ignoreInputEvent = true;
@@ -1275,30 +1035,41 @@
             this.popup.hidePopup();
             this.mMenuOpen = false;
           }
         ]]></body>
       </method>
 
       <!--  -->
       <method name="addResultElements">
+        <parameter name="aSession"/>
         <parameter name="aResults"/>
-        <parameter name="aOldResults"/>
         <body><![CDATA[
+          var count = aResults.errorDescription ? 1 : aResults.matchCount;
           if (this.focused && this.showPopup) {
-            this.view.replaceResults(aResults, aOldResults);
+            var row = 0;
+            for (var name in this.mSessions) {
+              row += this.mLastRows[name];
+              if (name == aSession)
+                break;
+            }
+            this.view.updateResults(row, count - this.mLastRows[name]);
             this.popup.adjustHeight();
           }
+          this.mLastResults[aSession] = aResults;
+          this.mLastRows[aSession] = count;
         ]]></body>
       </method>
 
       <!--  -->
       <method name="clearResultElements">
         <parameter name="aInvalidate"/>
         <body><![CDATA[
+          for (var name in this.mSessions)
+            this.mLastRows[name] = 0;
           this.view.clearResults();
           if (aInvalidate)
             this.popup.adjustHeight();
 
           this.noMatch = true;
         ]]></body>
       </method>
 
@@ -1317,31 +1088,18 @@
           this.dispatchEvent(evt);
           this.ignoreInputEvent = oldIgnoreInput;
         ]]></body>
       </method>
 
       <!--  -->
       <method name="clearResultData">
         <body><![CDATA[
-          const nsIAutoCompleteItem =
-            Components.interfaces.nsIAutoCompleteItem;
-          for (var name in this.mSessions) {
-            // clearing out mLastResults[name] does not guarantee that
-            // each result will go away right now (it might be gc'ed later)
-            // so we have to clear the 'param' element manually
-            var session = this.mLastResults[name];
-            if (session) {
-              const resultCount = session.items.Count();
-              for (var i=0; i<resultCount; i++)
-                  session.items.QueryElementAt(i, nsIAutoCompleteItem).param = null;
-              this.mLastResults[name] = null;
-            }
-            this.mLastStatus[name] = null;
-          }
+          for (var name in this.mSessions)
+            this.mLastResults[name] = null;
         ]]></body>
       </method>
 
       <!-- ::::::::::::: miscellaneous ::::::::::::: -->
 
       <!--  -->
       <method name="ifSetAttribute">
         <parameter name="aAttr"/>
@@ -1387,52 +1145,35 @@
 
       <!-- =================== TREE VIEW =================== -->
 
       <field name="view"><![CDATA[
         ({
           mTextbox: this,
           mTree: null,
           mSelection: null,
-          mResults: [],
-          mCounts: [],
           mRowCount: 0,
           
           clearResults: function()
           {
             var oldCount = this.mRowCount;
             this.mRowCount = 0;  
-            this.mResults = [];
-            this.mCounts = [];
             
             if (this.mTree) {
               this.mTree.rowCountChanged(0, -oldCount);
               this.mTree.scrollToRow(0);
             }
           },
           
-          replaceResults: function(aResults, aOldResults)
+          updateResults: function(aRow, aCount)
           {
-            var count = aResults.items.Count();
-            var row = this.mRowCount;
-            var oldIndex = this.mResults.indexOf(aOldResults);
-            if (oldIndex == -1) {
-              this.mResults.push(aResults);
-              this.mCounts.push(count);
-            } else {
-              this.mResults[oldIndex] = aResults;
-              count -= this.mCounts[oldIndex];
-              this.mCounts[oldIndex] += count;
-              while (++oldIndex < this.mCounts.length)
-                row -= this.mCounts[oldIndex];
-            }
-            this.mRowCount += count;
+            this.mRowCount += aCount;
             
             if (this.mTree)
-              this.mTree.rowCountChanged(row, count);
+              this.mTree.rowCountChanged(aRow, aCount);
           },
           
           //////////////////////////////////////////////////////////
           // nsIAutoCompleteController interface
 
           // this is the only method required by the treebody mouseup handler
           handleEnter: function(aIsPopupSelection) {
             this.mTextbox.onResultClick();
@@ -1455,40 +1196,55 @@
           
           setTree: function(aTree)
           {
             this.mTree = aTree;
           },
           
           getCellText: function(aRow, aCol)
           {
-            var result = this.mTextbox.getResultAt(aRow);
-            if (!result) return "";
-            return aCol.id == "treecolAutoCompleteValue" ? result.value : (aCol.id == "treecolAutoCompleteComment" ? result.comment : "");
+            for (var name in this.mTextbox.mSessions) {
+              if (aRow < this.mTextbox.mLastRows[name]) {
+                var result = this.mTextbox.mLastResults[name];
+                switch (aCol.id) {
+                  case "treecolAutoCompleteValue":
+                    return result.errorDescription || result.getValueAt(aRow);
+                  case "treecolAutoCompleteComment":
+                    if (!result.errorDescription)
+                      return result.getCommentAt(aRow);
+                  default:
+                    return "";
+                }
+              }
+              aRow -= this.mTextbox.mLastRows[name];
+            }
+            return "";
           },
 
-          getRowProperties: function(aIndex) 
+          getRowProperties: function(aIndex)
           {
             return "";
           },
 
           getCellProperties: function(aIndex, aCol)
           {
             // for the value column, append nsIAutoCompleteItem::className
             // to the property list so that we can style this column
             // using that property
-            try { 
-              if (aCol.id == "treecolAutoCompleteValue") {
-                return this.mTextbox.getResultAt(aIndex).className;
+            if (aCol.id == "treecolAutoCompleteValue") {
+              for (var name in this.mTextbox.mSessions) {
+                if (aIndex < this.mTextbox.mLastRows[name]) {
+                  var result = this.mTextbox.mLastResults[name];
+                  if (result.errorDescription)
+                    return "";
+                  return result.getStyleAt(aIndex);
+                }
+                aIndex -= this.mTextbox.mLastRows[name];
               }
-            } catch (ex) {
-              // the ability to style here is a frill, so don't abort
-              // if there's a problem
-            } 
-
+            }
             return "";
           },
 
           getColumnProperties: function(aCol)
           {
             return "";
           },