Bug 452232 Add support for an incomplete toolkit LDAP autocomplete component and a configure flag to build it r=Standard8
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Wed, 09 May 2012 22:12:22 +0100
changeset 12086 dff73c42414fbaf39305dd134642c20ad5eda379
parent 12085 f29073e5269d981642518cd836f62700a33ec3a7
child 12087 10f68e70c91a8a20b7f1311f66c7c396cbc8efd1
push idunknown
push userunknown
push dateunknown
reviewersStandard8
bugs452232
Bug 452232 Add support for an incomplete toolkit LDAP autocomplete component and a configure flag to build it r=Standard8
calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
config/autoconf.mk.in
configure.in
mail/components/compose/content/messengercompose.xul
mailnews/addrbook/public/Makefile.in
mailnews/addrbook/src/Makefile.in
mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
mailnews/addrbook/src/nsAddrbook.manifest
mailnews/build/Makefile.in
mailnews/build/nsMailModule.cpp
suite/mailnews/compose/addressingWidgetOverlay.xul
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.xml
@@ -62,17 +62,17 @@
             <xul:image id="attendeeCol1#1" anonid="icon"/>
           </xul:listcell>
           <xul:listcell class="addressingWidgetCell">
             <xul:textbox id="attendeeCol2#1"
                          anonid="input"
                          class="plain textbox-addressingWidget uri-element"
                          type="autocomplete"
                          flex="1"
-                         autocompletesearch="addrbook"
+                         autocompletesearch="addrbook ldap"
                          timeout="300"
                          maxrows="4"
                          completedefaultindex="true"
                          forcecomplete="true"
                          minresultsforpopup="1"
                          onblur="if (this.localName == 'textbox') document.getBindingParent(this).onBlurInput(event);"
                          ignoreblurwhilesearching="true"
                          oninput="this.setAttribute('dirty', 'true');">
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -501,16 +501,17 @@ MOZ_GLUE_LDFLAGS = @MOZ_GLUE_LDFLAGS@
 MOZ_GLUE_PROGRAM_LDFLAGS = @MOZ_GLUE_PROGRAM_LDFLAGS@
 WIN32_CRT_LIBS = @WIN32_CRT_LIBS@
 
 # Codesighs tools option, enables win32 mapfiles.
 MOZ_MAPINFO	= @MOZ_MAPINFO@
 
 MOZ_THUNDERBIRD = @MOZ_THUNDERBIRD@
 MOZ_INCOMPLETE_EXTERNAL_LINKAGE = @MOZ_INCOMPLETE_EXTERNAL_LINKAGE@
+MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE = @MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE@
 MOZ_SUNBIRD	= @MOZ_SUNBIRD@
 MOZ_SUITE	= @MOZ_SUITE@
 WINCE		= @WINCE@
 WINCE_WINDOWS_MOBILE = @WINCE_WINDOWS_MOBILE@
 
 MOZ_DISTRIBUTION_ID = @MOZ_DISTRIBUTION_ID@
 
 MACOS_SDK_DIR	= @MACOS_SDK_DIR@
--- a/configure.in
+++ b/configure.in
@@ -6705,16 +6705,21 @@ if test -n "$MOZ_SERVICES_SYNC"; then
 fi
 
 dnl ========================================================
 if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC"; then
     MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
 fi
 
 if test "$MOZ_LDAP_XPCOM"; then
+    MOZ_ARG_ENABLE_BOOL(incomplete-toolkit-ldap-autocomplete,
+    [  --enable-incomplete-toolkit-ldap-autocomplete Builds a JavaScript component that implements an LDAP autocomplete component using the toolkit interfaces rather than the C++ component using the obsolete XPFE interfaces. ],
+        MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE=1,
+        MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE= )
+
     LDAP_CFLAGS='-I${DIST}/public/ldap'
     if test "$OS_ARCH" = "WINNT"; then
         if test -n "$GNU_CC"; then
             LDAP_LIBS='-L$(DIST)/lib -lnsldap32v60 -lnsldappr32v60 -lnsldif32v60'
         else
             LDAP_LIBS='$(DIST)/lib/$(LIB_PREFIX)nsldap32v60.${IMPORT_LIB_SUFFIX} $(DIST)/lib/$(LIB_PREFIX)nsldappr32v60.${IMPORT_LIB_SUFFIX} $(DIST)/lib/$(LIB_PREFIX)nsldif32v60.${IMPORT_LIB_SUFFIX}'
         fi
     elif test "$OS_ARCH" = "OS2"; then
@@ -6727,16 +6732,17 @@ if test "$MOZ_LDAP_XPCOM"; then
     dnl linking libxul in the mozilla-central build system, we'll have the LDAP
     dnl LIBS
     export LDAP_LIBS
     LDAP_COMPONENT=mozldap
     export LDAP_COMPONENT
     LDAP_MODULE="MODULE(nsLDAPProtocolModule)"
     export LDAP_MODULE
 fi
+AC_SUBST(MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE)
 
 
 dnl ========================================================
 dnl =
 dnl = Maintainer debug option (no --enable equivalent)
 dnl =
 dnl ========================================================
 
--- a/mail/components/compose/content/messengercompose.xul
+++ b/mail/components/compose/content/messengercompose.xul
@@ -802,17 +802,17 @@
                     <menuitem value="addr_followup" label="&followupAddr.label;"/>
                   </menupopup>
                 </menulist>
               </listcell>
               <listcell class="addressingWidgetCell">
                 <textbox id="addressCol2#1" class="plain textbox-addressingWidget uri-element"
                          aria-labelledby="addressCol1#1"
                          type="autocomplete" flex="1" 
-                         autocompletesearch="mydomain addrbook"
+                         autocompletesearch="mydomain addrbook ldap"
                          timeout="300" maxrows="4"
                          completedefaultindex="true" forcecomplete="true"
                          minresultsforpopup="3" ignoreblurwhilesearching="true"
                          ontextentered="awRecipientTextCommand(eventParam, this)"
                          onerrorcommand="awRecipientErrorCommand(eventParam, this)"
                          oninput="gContentChanged=true; setupAutocomplete();" disableonsend="true"
                          onkeypress="awRecipientKeyPress(event, this)"
                          onkeydown="awRecipientKeyDown(event, this)">
--- a/mailnews/addrbook/public/Makefile.in
+++ b/mailnews/addrbook/public/Makefile.in
@@ -68,20 +68,24 @@ XPIDLSRCS	= \
 		nsIMsgVCardService.idl \
 		nsIAbLDIFService.idl \
 		nsIAbLDAPAttributeMap.idl \
 		nsIAbDirSearchListener.idl \
 		nsIAbAutoCompleteResult.idl \
 		$(NULL)
 
 ifdef MOZ_LDAP_XPCOM
+ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
 XPIDLSRCS       += \
 		nsILDAPAutoCompleteSession.idl \
 		nsILDAPAutoCompFormatter.idl \
 		nsIAbLDAPAutoCompFormatter.idl \
+		$(NULL)
+endif
+XPIDLSRCS       += \
                 nsIAbLDAPReplicationService.idl \
                 nsIAbLDAPReplicationQuery.idl \
                 nsIAbLDAPReplicationData.idl \
 		nsIAbLDAPDirectory.idl \
 		nsIAbLDAPCard.idl \
 		$(NULL)
 endif
 
--- a/mailnews/addrbook/src/Makefile.in
+++ b/mailnews/addrbook/src/Makefile.in
@@ -76,16 +76,19 @@ CPPSRCS		= \
                 nsAbLDIFService.cpp \
                 nsAbContentHandler.cpp \
 		$(NULL)
 
 EXTRA_COMPONENTS += \
 		nsAbLDAPAttributeMap.js \
 		nsAbAutoCompleteMyDomain.js \
 		nsAbAutoCompleteSearch.js \
+		$(NULL)
+
+EXTRA_PP_COMPONENTS += \
 		nsAddrbook.manifest \
 		$(NULL)
 
 EXPORTS		= \
                 nsVCardObj.h \
 		$(NULL)
 
 ifeq ($(OS_ARCH),WINNT)
@@ -108,22 +111,32 @@ DEFINES += -DMOZ_LDAP_XPCOM
 CPPSRCS		+= \
 		nsAbLDAPDirectory.cpp \
 		nsAbLDAPDirFactory.cpp	\
 		nsAbLDAPCard.cpp \
 		nsAbLDAPListenerBase.cpp \
 		nsAbLDAPDirectoryQuery.cpp \
 		nsAbLDAPDirectoryModify.cpp \
 		nsAbBoolExprToLDAPFilter.cpp \
-		nsAbLDAPAutoCompFormatter.cpp \
 		nsAbLDAPReplicationService.cpp \
 		nsAbLDAPReplicationQuery.cpp \
 		nsAbLDAPReplicationData.cpp \
+		$(NULL)
+
+ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+CPPSRCS		+= \
+		nsAbLDAPAutoCompFormatter.cpp \
 		nsLDAPAutoCompleteSession.cpp \
 		$(NULL)
+else
+DEFINES += -DMOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+EXTRA_COMPONENTS += \
+		nsAbLDAPAutoCompleteSearch.js \
+		$(NULL)
+endif
 
 # XXX These files are not being built as they don't work. Bug 311632 should
 # fix them.
 # nsAbLDAPChangeLogQuery.cpp
 # nsAbLDAPChangeLogData.cpp
 endif
 
 ifeq ($(OS_ARCH),Darwin)
new file mode 100644
--- /dev/null
+++ b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
@@ -0,0 +1,399 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org Address Book code
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mark Banner <bugzilla@standard8.plus.com> (Original Author)
+ *   Neil Rashbrook <neil@parkwaycc.co.uk>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const ACR = Components.interfaces.nsIAutoCompleteResult;
+const nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult;
+const nsIAbDirectoryQueryResultListener =
+  Components.interfaces.nsIAbDirectoryQueryResultListener;
+
+// nsAbLDAPAutoCompleteResult
+// Derived from nsIAbAutoCompleteResult, provides a LDAP specific result
+// implementation.
+
+function nsAbLDAPAutoCompleteResult(aSearchString) {
+  // Can't create this in the prototype as we'd get the same array for
+  // all instances
+  this._searchResults = [];
+  this.searchString = aSearchString;
+}
+
+nsAbLDAPAutoCompleteResult.prototype = {
+  _searchResults: null,
+  _commentColumn: "",
+
+  // nsIAutoCompleteResult
+
+  searchString: null,
+  searchResult: ACR.RESULT_NOMATCH,
+  defaultIndex: -1,
+  errorDescription: null,
+
+  get matchCount() {
+    return this._searchResults.length;
+  },
+
+  getValueAt: function getValueAt(aIndex) {
+    return this._searchResults[aIndex].value;
+  },
+
+  getCommentAt: function getCommentAt(aIndex) {
+    return this._commentColumn;
+  },
+
+  getStyleAt: function getStyleAt(aIndex) {
+    return this.searchResult == ACR.RESULT_FAILURE ? "remote-err" :
+                                                     "remote-abook";
+  },
+
+  getImageAt: function getImageAt(aIndex) {
+    return "";
+  },
+
+  removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+  },
+
+  // nsIAbAutoCompleteResult
+
+  getCardAt: function getCardAt(aIndex) {
+    return this._searchResults[aIndex].card;
+  },
+
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult])
+}
+
+function nsAbLDAPAutoCompleteSearch() {
+  Components.classes["@mozilla.org/observer-service;1"]
+            .getService(Components.interfaces.nsIObserverService)
+            .addObserver(this, "quit-application", false);
+}
+
+nsAbLDAPAutoCompleteSearch.prototype = {
+  // For component registration
+  classID: Components.ID("227e6482-fe9f-441f-9b7d-7b60375e7449"),
+
+  // A cache of Address Books, directories and search contexts.
+  // The cache is indexed by the address book URI. Each item in the cache
+  // then has three items:
+  // book - the address book associated with the URI.
+  // query - the nsAbLDAPDirectoryQuery in use for the URI.
+  // context - the search context for the URI (used for cancelling the search).
+  _cachedQueries: {},
+
+  // The URI of the currently active query.
+  _activeQuery: null,
+
+  // Cache of the identity to save getting it each time if it doesn't change
+  _cachedParam: null,
+  _cachedIdentity: null,
+
+  // The current search result.
+  _result: null,
+  // The listener to pass back results to.
+  _listener: null,
+
+  _parser: Components.classes["@mozilla.org/messenger/headerparser;1"]
+                     .getService(Components.interfaces.nsIMsgHeaderParser),
+
+  // Private methods
+  // fullString is the full search string.
+  // rest is anything after the first word.
+  _checkEntry: function _checkEntry(card, search) {
+    return card.displayName.toLocaleLowerCase().lastIndexOf(search, 0) == 0 ||
+           card.primaryEmail.toLocaleLowerCase().lastIndexOf(search, 0) == 0;
+  },
+
+  _checkDuplicate: function _checkDuplicate(card, emailAddress) {
+    var lcEmailAddress = emailAddress.toLocaleLowerCase();
+
+    return this._result._searchResults.some(function(result) {
+      return result.value.toLocaleLowerCase() == lcEmailAddress;
+    });
+  },
+
+  _addToResult: function _addToResult(card) {
+    var emailAddress =
+      this._parser.makeFullAddress(card.displayName, card.isMailList ?
+        card.getProperty("Notes", "") || card.displayName : card.primaryEmail);
+
+    // The old code used to try it manually. I think if the parser can't work
+    // out the address from what we've supplied, then its busted and we're not
+    // going to do any better doing it manually.
+    if (!emailAddress)
+      return;
+
+    // If it is a duplicate, then just return and don't add it. The
+    // _checkDuplicate function deals with it all for us.
+    if (this._checkDuplicate(card, emailAddress))
+      return;
+
+    // Find out where to insert the card.
+    var insertPosition = 0;
+
+    // Next sort on full address
+    while (insertPosition < this._result._searchResults.length &&
+           emailAddress > this._result._searchResults[insertPosition].value)
+      ++insertPosition;
+
+    this._result._searchResults.splice(insertPosition, 0, {
+      value: emailAddress,
+      card: card,
+    });
+  },
+
+  // nsIObserver
+
+  observe: function observer(subject, topic, data) {
+    if (topic == "quit-application") {
+      // Force the individual query items to null, so that the memory
+      // gets collected straight away.
+      for (var item in this._cachedQueries) {
+        this._cachedQueries[item].query = null;
+        this._cachedQueries[item].book = null;
+        this._cachedQueries[item].attributes = null;
+      }
+      this._cachedQueries = {};
+      Components.classes["@mozilla.org/observer-service;1"]
+                .getService(Components.interfaces.nsIObserverService)
+                .removeObserver(this, "quit-application");
+    }
+  },
+
+  // nsIAutoCompleteSearch
+
+  startSearch: function startSearch(aSearchString, aParam,
+                                    aPreviousResult, aListener) {
+    this._result = new nsAbLDAPAutoCompleteResult(aSearchString);
+    aSearchString = aSearchString.toLocaleLowerCase();
+
+    // If the search string isn't value, or contains a comma, or the user
+    // hasn't enabled autocomplete, then just return no matches / or the
+    // result ignored.
+    // The comma check is so that we don't autocomplete against the user
+    // entering multiple addresses.
+    if (!aSearchString || /,/.test(aSearchString)) {
+      this._result.searchResult = ACR.RESULT_IGNORED;
+      aListener.onSearchResult(this, this._result);
+      return;
+    }
+
+    // Compare lowercase strings, because autocomplete may mangle the case
+    // depending on the previous results.
+    if (aPreviousResult instanceof nsIAbAutoCompleteResult &&
+        aSearchString.lastIndexOf(
+          aPreviousResult.searchString.toLocaleLowerCase(), 0) == 0 &&
+        aPreviousResult.searchResult == ACR.RESULT_SUCCESS) {
+      // We have successful previous matches, therefore iterate through the
+      // list and reduce as appropriate.
+      for (var i = 0; i < aPreviousResult.matchCount; ++i) {
+        var card = aPreviousResult.getCardAt(i);
+        if (this._checkEntry(card, aSearchString)) {
+          // If it matches, just add it straight onto the array, these will
+          // already be in order because the previous search returned them
+          // in the correct order.
+          this._result._searchResults.push({
+            value: aPreviousResult.getValueAt(i),
+            card: card
+          });
+        }
+      }
+
+      if (this._result.matchCount) {
+        this._result.searchResult = ACR.RESULT_SUCCESS;
+        // For LDAP results, the comment column is always the same, so just
+        // use the first value from the previous results to set the value.
+        this._result._commentColumn = aPreviousResult.getCommentAt(0);
+      }
+      aListener.onSearchResult(this, this._result);
+      return;
+    }
+
+    // Here we need to do the real search, as we haven't got any previous
+    // results to fall back on.
+
+    if (aParam != this._cachedParam) {
+      this._cachedIdentity =
+        Components.classes['@mozilla.org/messenger/account-manager;1']
+                  .getService(Components.interfaces.nsIMsgAccountManager)
+                  .getIdentity(aParam);
+      this._cachedParam = aParam;
+    }
+
+    // The rules here: If the current identity has a directoryServer set, then
+    // use that, otherwise, try the global preference instead.
+    var acDirURI = null;
+
+    // Does the current identity override the global preference?
+    if (this._cachedIdentity.overrideGlobalPref)
+      acDirURI = this._cachedIdentity.directoryServer;
+    else {
+      // Try the global one
+      var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+                              .getService(Components.interfaces.nsIPrefBranch);
+      if (prefSvc.getBoolPref("ldap_2.autoComplete.useDirectory"))
+        acDirURI = prefSvc.getCharPref("ldap_2.autoComplete.directoryServer");
+    }
+
+    if (!acDirURI) {
+      // No directory to search, send a no match and return.
+      aListener.onSearchResult(this, this._result);
+      return;
+    }
+
+    var abMgr = Components.classes["@mozilla.org/abmanager;1"]
+                          .getService(Components.interfaces.nsIAbManager);
+
+    // If we don't already have a cached query for this URI, build a new one.
+    if (!(acDirURI in this._cachedQueries)) {
+      var query =
+        Components.classes["@mozilla.org/addressbook/ldap-directory-query;1"]
+                  .createInstance(Components.interfaces.nsIAbDirectoryQuery);
+      var book = abMgr.getDirectory("moz-abldapdirectory://" + acDirURI)
+                      .QueryInterface(Components.interfaces.nsIAbLDAPDirectory);
+
+      // Create a minimal map just for the display name and primary email.
+      var attributes =
+        Components.classes["@mozilla.org/addressbook/ldap-attribute-map;1"]
+                  .createInstance(Components.interfaces.nsIAbLDAPAttributeMap);
+      attributes.setAttributeList("DisplayName",
+        book.attributeMap.getAttributeList("DisplayName", {}), true);
+      attributes.setAttributeList("PrimaryEmail",
+        book.attributeMap.getAttributeList("PrimaryEmail", {}), true);
+
+      this._cachedQueries[acDirURI] = {
+        attributes: attributes,
+        book: book,
+        context: -1,
+        query: query
+      };
+    }
+
+    this.stopSearch();
+
+    this._activeQuery = acDirURI;
+
+    var queryObject = this._cachedQueries[acDirURI];
+
+    this._result._commentColumn = queryObject.book.dirName;
+    this._listener = aListener;
+
+    var args =
+      Components.classes["@mozilla.org/addressbook/directory/query-arguments;1"]
+                .createInstance(Components.interfaces.nsIAbDirectoryQueryArguments);
+
+    // These are hard-coded for now and the _checkEntry function should match
+    // what these statements do.
+    var searchStr = "(or(PrimaryEmail,bw,@V)(DisplayName,bw,@V))";
+    searchStr = searchStr.replace(/@V/g, encodeURIComponent(aSearchString));
+
+    args.expression = abMgr.convertQueryStringToExpression(searchStr);
+    args.typeSpecificArg = queryObject.attributes;
+    args.querySubDirectories = true;
+
+    // Start the actual search
+    queryObject.context =
+      queryObject.query.doQuery(queryObject.book, args, this,
+                                queryObject.book.maxHits, 0);
+  },
+
+  stopSearch: function stopSearch() {
+    if (this._activeQuery) {
+      this._cachedQueries[this._activeQuery].query
+          .stopQuery(this._cachedQueries[this._activeQuery].context);
+      this._listener = null;
+      this._activeQuery = null;
+    }
+  },
+
+  // nsIAbDirSearchListener
+
+  onSearchFinished: function onSearchFinished(aResult, aErrorMsg) {
+    if (!this._listener)
+      return;
+
+    if (aResult == nsIAbDirectoryQueryResultListener.queryResultComplete) {
+      if (this._result.matchCount) {
+        this._result.searchResult = ACR.RESULT_SUCCESS;
+        this._result.defaultIndex = 0;
+      }
+      else
+        this._result.searchResult = ACR.RESULT_NOMATCH;
+    }
+    else if (aResult == nsIAbDirectoryQueryResultListener.queryResultError) {
+      this._result.searchResult = ACR.RESULT_FAILURE;
+      this._result.defaultIndex = 0;
+    }
+    //    const long queryResultStopped  = 2;
+    //    const long queryResultError    = 3;
+    this._activeQuery = null;
+    this._listener.onSearchResult(this, this._result);
+    this._listener = null;
+  },
+
+  onSearchFoundCard: function onSearchFoundCard(aCard) {
+    if (!this._listener)
+      return;
+
+    this._addToResult(aCard);
+
+    /* XXX autocomplete doesn't expect you to rearrange while searching
+    if (this._result.matchCount)
+      this._result.searchResult = ACR.RESULT_SUCCESS_ONGOING;
+    else
+      this._result.searchResult = ACR.RESULT_NOMATCH_ONGOING;
+
+    this._listener.onSearchResult(this, this._result);
+    */
+  },
+
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver,
+                                         Components.interfaces
+                                                   .nsIAutoCompleteSearch,
+                                         Components.interfaces
+                                                   .nsIAbDirSearchListener])
+};
+
+// Module
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAutoCompleteSearch]);
--- a/mailnews/addrbook/src/nsAddrbook.manifest
+++ b/mailnews/addrbook/src/nsAddrbook.manifest
@@ -1,8 +1,12 @@
 component {5b259db2-e451-4de9-8a6f-cfba91402973} nsAbAutoCompleteMyDomain.js
 contract @mozilla.org/autocomplete/search;1?name=mydomain {5b259db2-e451-4de9-8a6f-cfba91402973}
 component {2f946df9-114c-41fe-8899-81f10daf4f0c} nsAbAutoCompleteSearch.js
 contract @mozilla.org/autocomplete/search;1?name=addrbook {2f946df9-114c-41fe-8899-81f10daf4f0c}
 component {127b341a-bdda-4270-85e1-edff569a9b85} nsAbLDAPAttributeMap.js
 contract @mozilla.org/addressbook/ldap-attribute-map;1 {127b341a-bdda-4270-85e1-edff569a9b85}
 component {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e} nsAbLDAPAttributeMap.js
 contract @mozilla.org/addressbook/ldap-attribute-map-service;1 {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}
+#ifdef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+component {227e6482-fe9f-441f-9b7d-7b60375e7449} nsAbLDAPAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=ldap {227e6482-fe9f-441f-9b7d-7b60375e7449}
+#endif
--- a/mailnews/build/Makefile.in
+++ b/mailnews/build/Makefile.in
@@ -54,16 +54,19 @@ FORCE_SHARED_LIB = 1
 endif
 GRE_MODULE	= 1
 FORCE_USE_PIC   = 1
 
 MODULE_NAME	= nsMailModule
 
 ifdef MOZ_LDAP_XPCOM
 DEFINES		+= -DMOZ_LDAP_XPCOM
+ifdef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+DEFINES += -DMOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+endif
 endif
 
 ifeq ($(OS_ARCH),WINNT)
 DEFINES		+= -DZLIB_DLL
 endif
 
 CPPSRCS		= nsMailModule.cpp
 
--- a/mailnews/build/nsMailModule.cpp
+++ b/mailnews/build/nsMailModule.cpp
@@ -175,26 +175,28 @@
 #include "nsMsgVCardService.h"
 #include "nsAbLDIFService.h"
 
 #if defined(MOZ_LDAP_XPCOM)
 #include "nsAbLDAPDirectory.h"
 #include "nsAbLDAPDirectoryQuery.h"
 #include "nsAbLDAPCard.h"
 #include "nsAbLDAPDirFactory.h"
-#include "nsAbLDAPAutoCompFormatter.h"
 #include "nsAbLDAPReplicationService.h"
 #include "nsAbLDAPReplicationQuery.h"
 #include "nsAbLDAPReplicationData.h"
 // XXX These files are not being built as they don't work. Bug 311632 should
 // fix them.
 //#include "nsAbLDAPChangeLogQuery.h"
 //#include "nsAbLDAPChangeLogData.h"
+#ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+#include "nsAbLDAPAutoCompFormatter.h"
 #include "nsLDAPAutoCompleteSession.h"
 #endif
+#endif
 
 
 #if defined(XP_WIN) && !defined(__MINGW32__)
 #include "nsAbOutlookDirFactory.h"
 #include "nsAbOutlookDirectory.h"
 #endif
 
 #ifdef XP_MACOSX
@@ -488,26 +490,28 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbBoole
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbBooleanExpression)
 
 
 #if defined(MOZ_LDAP_XPCOM)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirectory)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirectoryQuery)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPCard)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPDirFactory)
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPAutoCompFormatter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPReplicationService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPReplicationQuery)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPProcessReplicationData)
 // XXX These files are not being built as they don't work. Bug 311632 should
 // fix them.
 //NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPChangeLogQuery)
 //NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPProcessChangeLogData)
+#ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDAPAutoCompFormatter)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsLDAPAutoCompleteSession)
 #endif
+#endif
 
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbDirectoryQueryProxy)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbView)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgVCardService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDIFService)
 
 #ifdef XP_MACOSX
@@ -540,19 +544,21 @@ NS_DEFINE_NAMED_CID(NS_ABOUTLOOKDIRFACTO
 #if defined(MOZ_LDAP_XPCOM)
 NS_DEFINE_NAMED_CID(NS_ABLDAPDIRECTORY_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAPDIRECTORYQUERY_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAPCARD_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAPDIRFACTORY_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAP_REPLICATIONSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAP_REPLICATIONQUERY_CID);
 NS_DEFINE_NAMED_CID(NS_ABLDAP_PROCESSREPLICATIONDATA_CID);
+#ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
 NS_DEFINE_NAMED_CID(NS_ABLDAPAUTOCOMPFORMATTER_CID);
 NS_DEFINE_NAMED_CID(NS_LDAPAUTOCOMPLETESESSION_CID);
 #endif
+#endif
 NS_DEFINE_NAMED_CID(NS_ABDIRECTORYQUERYPROXY_CID);
 #ifdef XP_MACOSX
 NS_DEFINE_NAMED_CID(NS_ABOSXDIRECTORY_CID);
 NS_DEFINE_NAMED_CID(NS_ABOSXCARD_CID);
 NS_DEFINE_NAMED_CID(NS_ABOSXDIRFACTORY_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_ABVIEW_CID);
 NS_DEFINE_NAMED_CID(NS_MSGVCARDSERVICE_CID);
@@ -893,19 +899,21 @@ const mozilla::Module::CIDEntry kMailNew
 #if defined(MOZ_LDAP_XPCOM)
   { &kNS_ABLDAPDIRECTORY_CID, false, NULL, nsAbLDAPDirectoryConstructor },
   { &kNS_ABLDAPDIRECTORYQUERY_CID, false, NULL, nsAbLDAPDirectoryQueryConstructor },
   { &kNS_ABLDAPCARD_CID, false, NULL, nsAbLDAPCardConstructor },
   { &kNS_ABLDAP_REPLICATIONSERVICE_CID, false, NULL, nsAbLDAPReplicationServiceConstructor },
   { &kNS_ABLDAP_REPLICATIONQUERY_CID, false, NULL, nsAbLDAPReplicationQueryConstructor },
   { &kNS_ABLDAP_PROCESSREPLICATIONDATA_CID, false, NULL, nsAbLDAPProcessReplicationDataConstructor },
   { &kNS_ABLDAPDIRFACTORY_CID, false, NULL, nsAbLDAPDirFactoryConstructor },
+#ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
   { &kNS_ABLDAPAUTOCOMPFORMATTER_CID, false, NULL, nsAbLDAPAutoCompFormatterConstructor },
   { &kNS_LDAPAUTOCOMPLETESESSION_CID, false, NULL, nsLDAPAutoCompleteSessionConstructor },
 #endif
+#endif
   { &kNS_ABDIRECTORYQUERYPROXY_CID, false, NULL, nsAbDirectoryQueryProxyConstructor },
 #ifdef XP_MACOSX
   { &kNS_ABOSXDIRECTORY_CID, false, NULL, nsAbOSXDirectoryConstructor },
   { &kNS_ABOSXCARD_CID, false, NULL, nsAbOSXCardConstructor },
   { &kNS_ABOSXDIRFACTORY_CID, false, NULL, nsAbOSXDirFactoryConstructor },
 #endif
   { &kNS_ABVIEW_CID, false, NULL, nsAbViewConstructor },
   { &kNS_MSGVCARDSERVICE_CID, false, NULL, nsMsgVCardServiceConstructor },
@@ -1096,19 +1104,21 @@ const mozilla::Module::ContractIDEntry k
   { NS_ABLDAPDIRECTORYQUERY_CONTRACTID, &kNS_ABLDAPDIRECTORYQUERY_CID },
   { NS_ABLDAPCARD_CONTRACTID, &kNS_ABLDAPCARD_CID },
   { NS_ABLDAPDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
   { NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID, &kNS_ABLDAP_REPLICATIONSERVICE_CID },
   { NS_ABLDAP_REPLICATIONQUERY_CONTRACTID, &kNS_ABLDAP_REPLICATIONQUERY_CID },
   { NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID, &kNS_ABLDAP_PROCESSREPLICATIONDATA_CID },
   { NS_ABLDAPACDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
   { NS_ABLDAPSACDIRFACTORY_CONTRACTID, &kNS_ABLDAPDIRFACTORY_CID },
+#ifndef MOZ_INCOMPLETE_TOOLKIT_LDAP_AUTOCOMPLETE
   { NS_ABLDAPAUTOCOMPFORMATTER_CONTRACTID, &kNS_ABLDAPAUTOCOMPFORMATTER_CID },
   { "@mozilla.org/autocompleteSession;1?type=ldap", &kNS_LDAPAUTOCOMPLETESESSION_CID },
 #endif
+#endif
 
   { NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &kNS_ABDIRECTORYQUERYPROXY_CID },
 #ifdef XP_MACOSX
   { NS_ABOSXDIRECTORY_CONTRACTID, &kNS_ABOSXDIRECTORY_CID },
   { NS_ABOSXCARD_CONTRACTID, &kNS_ABOSXCARD_CID },
   { NS_ABOSXDIRFACTORY_CONTRACTID, &kNS_ABOSXDIRFACTORY_CID },
 #endif
   { NS_ABVIEW_CONTRACTID, &kNS_ABVIEW_CID },
--- a/suite/mailnews/compose/addressingWidgetOverlay.xul
+++ b/suite/mailnews/compose/addressingWidgetOverlay.xul
@@ -74,17 +74,17 @@
         </menulist>
       </listcell>
       
       <listcell class="addressingWidgetCell">
         <textbox id="addressCol2#1" class="plain textbox-addressingWidget uri-element"
                  aria-labelledby="addressCol1#1"
                  type="autocomplete" flex="1" maxrows="4"
                  newlines="replacewithcommas"
-                 autocompletesearch="mydomain addrbook" timeout="300"
+                 autocompletesearch="mydomain addrbook ldap" timeout="300"
                  completedefaultindex="true" forcecomplete="true"
                  minresultsforpopup="3" ignoreblurwhilesearching="true"
                  ontextentered="awRecipientTextCommand(eventParam, this)"
                  onerrorcommand="awRecipientErrorCommand(eventParam, this)"
                  oninput="gContentChanged=true; setupAutocomplete();" disableonsend="true"
                  onkeypress="awRecipientKeyPress(event, this)"
                  onkeydown="awRecipientKeyDown(event, this)">
           <image class="person-icon" onclick="this.parentNode.select();"/>