Bug 61491 - Autocomplete newsgroup names (based on a patch by Jim Porter). r=neil/mconley, moa=neil, sr=Standard8; small followup fix for test: r=jcranmer
authorJens Mueller <blog@tessarakt.de>
Wed, 13 Nov 2013 08:16:42 -0500
changeset 16898 2523fe46615fc744ca64a5b938f1ed42ecb53f22
parent 16897 34f8c2ebcc73ea6093064bc85e5628eae0e94469
child 16899 549a83dc09a2decf770042d0aa6bc62fe40a9994
push id1074
push userbugzilla@standard8.plus.com
push dateMon, 03 Feb 2014 22:47:23 +0000
treeherdercomm-beta@6b791b5369ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, mconley, Standard8, small, jcranmer
bugs61491
Bug 61491 - Autocomplete newsgroup names (based on a patch by Jim Porter). r=neil/mconley, moa=neil, sr=Standard8; small followup fix for test: r=jcranmer CLOSED TREE
mail/components/compose/content/MsgComposeCommands.js
mail/components/compose/content/addressingWidgetOverlay.js
mail/components/compose/content/messengercompose.xul
mail/installer/package-manifest.in
mail/themes/linux/mail/compose/messengercompose.css
mail/themes/osx/mail/compose/messengercompose.css
mail/themes/windows/mail/compose/messengercompose-aero.css
mail/themes/windows/mail/compose/messengercompose.css
mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
mailnews/addrbook/src/nsAbAutoCompleteSearch.js
mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteMyDomain.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch1.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch2.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch3.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch4.js
mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js
mailnews/news/src/moz.build
mailnews/news/src/nsNewsAutoCompleteSearch.js
mailnews/news/src/nsNewsAutoCompleteSearch.manifest
mailnews/news/test/unit/head_server_setup.js
mailnews/news/test/unit/test_newsAutocomplete.js
mailnews/news/test/unit/xpcshell.ini
mailnews/test/resources/localAccountUtils.js
suite/installer/package-manifest.in
suite/mailnews/compose/MsgComposeCommands.js
suite/mailnews/compose/addressingWidgetOverlay.js
suite/mailnews/compose/addressingWidgetOverlay.xul
suite/themes/classic/mac/messenger/messengercompose/messengercompose.css
suite/themes/classic/messenger/messengercompose/messengercompose.css
suite/themes/modern/messenger/messengercompose/messengercompose.css
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -3948,26 +3948,32 @@ function LoadIdentity(startup)
 {
     var identityElement = document.getElementById("msgIdentity");
     var prevIdentity = gCurrentIdentity;
 
     if (identityElement) {
         var idKey = identityElement.value;
         gCurrentIdentity = MailServices.accounts.getIdentity(idKey);
 
+        let accountKey = null;
         // Set the account key value on the menu list.
         if (identityElement.selectedItem) {
-          let accountKey = identityElement.selectedItem.getAttribute("accountkey");
+          accountKey = identityElement.selectedItem.getAttribute("accountkey");
           identityElement.setAttribute("accountkey", accountKey);
           hideIrrelevantAddressingOptions(accountKey);
         }
 
         let maxRecipients = awGetMaxRecipients();
         for (let i = 1; i <= maxRecipients; i++)
-          awGetInputElement(i).setAttribute("autocompletesearchparam", idKey);
+        {
+          let params = JSON.parse(awGetInputElement(i).searchParam);
+          params.idKey = idKey;
+          params.accountKey = accountKey;
+          awGetInputElement(i).searchParam = JSON.stringify(params);
+        }
 
         if (!startup && prevIdentity && idKey != prevIdentity.key)
         {
           var prevReplyTo = prevIdentity.replyTo;
           var prevCc = "";
           var prevBcc = "";
           var prevReceipt = prevIdentity.requestReturnReceipt;
           var prevDSN = prevIdentity.DSN;
@@ -4065,20 +4071,16 @@ function LoadIdentity(startup)
           addRecipientsToIgnoreList(gCurrentIdentity.identityName);  // only do this if we aren't starting up....it gets done as part of startup already
       }
     }
 }
 
 function setupAutocomplete()
 {
   var autoCompleteWidget = document.getElementById("addressCol2#1");
-  // When autocompleteToMyDomain is off there is no default entry with the domain
-  // appended so reduce the minimum results for a popup to 2 in this case.
-  if (!gCurrentIdentity.autocompleteToMyDomain)
-    autoCompleteWidget.minResultsForPopup = 2;
 
   // if the pref is set to turn on the comment column, honor it here.
   // this element then gets cloned for subsequent rows, so they should
   // honor it as well
   //
   try
   {
     if (getPref("mail.autoComplete.highlightNonMatches"))
--- a/mail/components/compose/content/addressingWidgetOverlay.js
+++ b/mail/components/compose/content/addressingWidgetOverlay.js
@@ -769,19 +769,19 @@ function DropOnAddressingWidget(event)
 function DropRecipient(target, recipient)
 {
   // break down and add each address
   return parseAndAddAddresses(recipient, awGetPopupElement(top.MAX_RECIPIENTS).selectedItem.getAttribute("value"));
 }
 
 function _awSetAutoComplete(selectElem, inputElem)
 {
-  inputElem.disableAutoComplete = selectElem.value == 'addr_newsgroups' ||
-                                  selectElem.value == 'addr_followup' ||
-                                  selectElem.value == 'addr_other';
+  let params = JSON.parse(inputElem.getAttribute('autocompletesearchparam'));
+  params.type = selectElem.value;
+  inputElem.setAttribute('autocompletesearchparam', JSON.stringify(params));
 }
 
 function awSetAutoComplete(rowNumber)
 {
     var inputElem = awGetInputElement(rowNumber);
     var selectElem = awGetPopupElement(rowNumber);
     _awSetAutoComplete(selectElem, inputElem)
 }
--- a/mail/components/compose/content/messengercompose.xul
+++ b/mail/components/compose/content/messengercompose.xul
@@ -870,20 +870,21 @@
                     <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 ldap"
+                         autocompletesearch="mydomain addrbook ldap news"
+                         autocompletesearchparam="{}"
                          timeout="300" maxrows="4"
                          completedefaultindex="true" forcecomplete="true"
-                         minresultsforpopup="3" ignoreblurwhilesearching="true"
+                         minresultsforpopup="2" ignoreblurwhilesearching="true"
                          ontextentered="awRecipientTextCommand(eventParam, this)"
                          onerrorcommand="awRecipientErrorCommand(eventParam, this)"
                          onchange="onRecipientsInput();"
                          oninput="onRecipientsInput();"
                          onkeypress="awRecipientKeyPress(event, this)"
                          onkeydown="awRecipientKeyDown(event, this)"
                          disableonsend="true">
                   <image class="person-icon" onclick="this.parentNode.select();"/>
--- a/mail/installer/package-manifest.in
+++ b/mail/installer/package-manifest.in
@@ -195,16 +195,18 @@
 @BINPATH@/components/nsActivity.js
 @BINPATH@/components/nsActivityManager.js
 @BINPATH@/components/nsActivityManagerUI.js
 @BINPATH@/components/nsYouSendIt.js
 @BINPATH@/components/nsUbuntuOne.js
 @BINPATH@/components/nsBox.js
 @BINPATH@/components/nsAddrbook.manifest
 @BINPATH@/components/nsMailNewsCommandLineHandler.js
+@BINPATH@/components/nsNewsAutoCompleteSearch.js
+@BINPATH@/components/nsNewsAutoCompleteSearch.manifest
 @BINPATH@/components/services-crypto-component.xpt
 #ifndef XP_OS2
 @BINPATH@/components/shellservice.xpt
 #endif
 @BINPATH@/components/xpcom_base.xpt
 @BINPATH@/components/xpcom_system.xpt
 @BINPATH@/components/xpcom_components.xpt
 @BINPATH@/components/xpcom_ds.xpt
--- a/mail/themes/linux/mail/compose/messengercompose.css
+++ b/mail/themes/linux/mail/compose/messengercompose.css
@@ -281,16 +281,23 @@ treechildren::-moz-tree-image(remote-abo
 }
 
 treechildren::-moz-tree-image(remote-err) {
   -moz-margin-start: 3px;
   -moz-margin-end: 2px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/remote-addrbook-error.png");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 3px;
+  -moz-margin-end: 2px;
+  list-style-image: url("chrome://messenger/skin/icons/folder-pane.png");
+  -moz-image-region: rect(208px 16px 224px 0px);
+}
+
 /* ::::: addressing widget ::::: */
 
 .autocomplete-treebody::-moz-tree-cell-text(comment) {
   color: #555566;
 }
 
 #addressingWidget {
   -moz-user-focus: none;
--- a/mail/themes/osx/mail/compose/messengercompose.css
+++ b/mail/themes/osx/mail/compose/messengercompose.css
@@ -522,16 +522,25 @@ treechildren::-moz-tree-image(remote-err
   treechildren::-moz-tree-image(remote-abook) {
     width: 16px;
     height: 16px;
     list-style-image: url("chrome://messenger/skin/addressbook/icons/addressbook@2x.png");
     -moz-image-region: rect(0px 96px 32px 64px);
   }
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  margin-top: 2px;
+  margin-bottom: 2px;
+  -moz-margin-start: 2px;
+  -moz-margin-end: -3px;
+  list-style-image: url("chrome://messenger/skin/icons/folder-pane.png");
+  -moz-image-region: rect(0 160px 16px 144px);
+}
+
 /* ::::: addressing widget ::::: */
 
 #addressingWidget {
   -moz-user-focus: none;
   -moz-appearance: none;
   width: 0;
   margin: 5px 0px;
   border: none;
--- a/mail/themes/windows/mail/compose/messengercompose-aero.css
+++ b/mail/themes/windows/mail/compose/messengercompose-aero.css
@@ -431,16 +431,23 @@ treechildren::-moz-tree-image(remote-abo
 }
 
 treechildren::-moz-tree-image(remote-err) {
   -moz-margin-start: 2px;
   -moz-margin-end: 5px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/remote-addrbook-error.png");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 2px;
+  -moz-margin-end: 5px;
+  list-style-image: url("chrome://messenger/skin/icons/folder.png");
+  -moz-image-region: rect(0 160px 16px 144px);
+}
+
 /* ::::: addressing widget ::::: */
 
 .autocomplete-treebody::-moz-tree-cell-text(comment) {
   color: #555566;
 }
 
 @media (-moz-windows-default-theme) {
   .autocomplete-treebody::-moz-tree-cell-text(selected) {
--- a/mail/themes/windows/mail/compose/messengercompose.css
+++ b/mail/themes/windows/mail/compose/messengercompose.css
@@ -370,16 +370,23 @@ treechildren::-moz-tree-image(remote-abo
 }
 
 treechildren::-moz-tree-image(remote-err) {
   -moz-margin-start: 2px;
   -moz-margin-end: 3px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/remote-addrbook-error.png");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 2px;
+  -moz-margin-end: 3px;
+  list-style-image: url("chrome://messenger/skin/icons/folder.png");
+  -moz-image-region: rect(0 160px 16px 144px);
+}
+
 /* ::::: addressing widget ::::: */
 
 .autocomplete-treebody::-moz-tree-cell-text(comment) {
   color: #555566;
 }
 
 #addressingWidget {
   -moz-user-focus: none;
--- a/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
+++ b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js
@@ -7,26 +7,30 @@ Components.utils.import("resource://gre/
 
 function nsAbAutoCompleteMyDomain() {}
 
 nsAbAutoCompleteMyDomain.prototype = {
   classID: Components.ID("{5b259db2-e451-4de9-8a6f-cfba91402973}"),
   QueryInterface: XPCOMUtils.generateQI([
       Components.interfaces.nsIAutoCompleteSearch]),
 
-  cachedParam: "",
+  cachedIdKey: "",
   cachedIdentity: null,
 
-  startSearch: function(aString, aParam, aResult, aListener) {
+  applicableHeaders: Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
+  startSearch: function(aString, aSearchParam, aResult, aListener) {
+    let params = JSON.parse(aSearchParam);
+    let applicable = this.applicableHeaders.has(params.type);
     const ACR = Components.interfaces.nsIAutoCompleteResult;
     var address = null;
-    if (aString && !aString.contains(",")) {
-      if (aParam != this.cachedParam) {
-        this.cachedIdentity = MailServices.accounts.getIdentity(aParam);
-        this.cachedParam = aParam;
+    if (applicable && aString && !aString.contains(",")) {
+      if (params.idKey != this.cachedIdKey) {
+        this.cachedIdentity = MailServices.accounts.getIdentity(params.idKey);
+        this.cachedIdKey = params.idKey;
       }
       if (this.cachedIdentity.autocompleteToMyDomain)
         address = aString.contains("@") ? aString :
                   this.cachedIdentity.email.replace(/[^@]*/, aString);
     }
 
     var result = {
       searchString: aString,
--- a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
+++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js
@@ -75,16 +75,17 @@ nsAbAutoCompleteSearch.prototype = {
   classID: Components.ID("2f946df9-114c-41fe-8899-81f10daf4f0c"),
 
   // This is set from a preference,
   // 0 = no comment column, 1 = name of address book this card came from
   // Other numbers currently unused (hence default to zero)
   _commentColumn: 0,
   _parser: MailServices.headerParser,
   _abManager: MailServices.ab,
+  applicableHeaders: Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
 
   // Private methods
 
   /**
    * Returns the popularity index for a given card. This takes account of a
    * translation bug whereby Thunderbird 2 stores its values in mork as
    * hexadecimal, and Thunderbird 3 stores as decimal.
    *
@@ -340,17 +341,23 @@ nsAbAutoCompleteSearch.prototype = {
    *
    * @see nsIAutoCompleteSearch for parameter details.
    *
    * It is expected that aSearchParam contains the identity (if any) to use
    * for determining if an address book should be autocompleted against.
    */
   startSearch: function startSearch(aSearchString, aSearchParam,
                                     aPreviousResult, aListener) {
+    let params = JSON.parse(aSearchParam);
     var result = new nsAbAutoCompleteResult(aSearchString);
+    if (!this.applicableHeaders.has(params.type)) {
+      result.searchResult = ACR.RESULT_IGNORED;
+      aListener.onSearchResult(this, result);
+      return;
+    }
 
     // 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 || aSearchString.contains(",")) {
       result.searchResult = ACR.RESULT_IGNORED;
@@ -424,17 +431,17 @@ nsAbAutoCompleteSearch.prototype = {
       // We're not going to bother searching sub-directories, currently the
       // architecture forces all cards that are in mailing lists to be in ABs as
       // well, therefore by searching sub-directories (aka mailing lists) we're
       // just going to find duplicates.
       while (allABs.hasMoreElements()) {
         var dir = allABs.getNext();
 
         if (dir instanceof Components.interfaces.nsIAbDirectory &&
-            dir.useForAutocomplete(aSearchParam)) {
+            dir.useForAutocomplete(params.idKey)) {
           this._searchCards(searchQuery, dir, result);
           this._searchWithinEmails(emailSearchQuery, fullString, dir, result);
         }
       }
     }
 
     if (result.matchCount) {
       result.searchResult = ACR.RESULT_SUCCESS;
--- a/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
+++ b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js
@@ -94,16 +94,18 @@ nsAbLDAPAutoCompleteSearch.prototype = {
 
   // The current search result.
   _result: null,
   // The listener to pass back results to.
   _listener: null,
 
   _parser: MailServices.headerParser,
 
+  applicableHeaders: Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]),
+
   // Private methods
 
   _checkDuplicate: function _checkDuplicate(card, emailAddress) {
     var lcEmailAddress = emailAddress.toLocaleLowerCase();
 
     return this._result._searchResults.some(function(result) {
       return result.value.toLocaleLowerCase() == lcEmailAddress;
     });
@@ -154,38 +156,42 @@ nsAbLDAPAutoCompleteSearch.prototype = {
       Services.obs.removeObserver(this, "quit-application");
     }
   },
 
   // nsIAutoCompleteSearch
 
   startSearch: function startSearch(aSearchString, aParam,
                                     aPreviousResult, aListener) {
+    let params = JSON.parse(aParam);
+    let applicable = this.applicableHeaders.has(params.type);
+    let idKey = params.idKey;
+
     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 || aSearchString.contains(",")) {
+    if (!applicable || !aSearchString || aSearchString.contains(",")) {
       this._result.searchResult = ACR.RESULT_IGNORED;
       aListener.onSearchResult(this, this._result);
       return;
     }
 
     // The rules here: If the current identity has a directoryServer set, then
     // use that, otherwise, try the global preference instead.
     var acDirURI = null;
     var identity;
 
-    if (aParam) {
+    if (idKey) {
       try {
-        identity = MailServices.accounts.getIdentity(aParam);
+        identity = MailServices.accounts.getIdentity(idKey);
       }
       catch(ex) {
         Components.utils.reportError("Couldn't get specified identity, falling " +
                                      "back to global settings");
       }
     }
 
     // Does the current identity override the global preference?
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteMyDomain.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteMyDomain.js
@@ -19,83 +19,99 @@ acObserver.prototype = {
 
 function run_test() {
   // Test - Create a new search component
 
   var acs = Components.classes["@mozilla.org/autocomplete/search;1?name=mydomain"]
     .getService(Components.interfaces.nsIAutoCompleteSearch);
 
   var obs = new acObserver();
+  let obsNews = new acObserver();
+  let obsFollowup = new acObserver();
 
   // Set up an identity in the account manager with the default settings
   let identity = MailServices.accounts.createIdentity();
 
   // Initially disable autocomplete
   identity.autocompleteToMyDomain = false;
   identity.email = "myemail@foo.invalid";
 
+  // Set up autocomplete parameters
+  let params = JSON.stringify({ idKey: identity.key, type: "addr_to" });
+  let paramsNews = JSON.stringify({ idKey: identity.key, type: "addr_newsgroups" });
+  let paramsFollowup = JSON.stringify({ idKey: identity.key, type: "addr_followup" });
+
   // Test - Valid search - this should return no results (autocomplete disabled)
-  acs.startSearch("test", identity.key, null, obs);
+  acs.startSearch("test", params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "test");
   do_check_eq(obs._result.searchResult, ACR.RESULT_FAILURE);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
 
   // Now enable autocomplete for this identity
   identity.autocompleteToMyDomain = true;
 
   // Test - Search with empty string
 
-  acs.startSearch(null, identity.key, null, obs);
+  acs.startSearch(null, params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, null);
   do_check_eq(obs._result.searchResult, ACR.RESULT_FAILURE);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
 
-  acs.startSearch("", identity.key, null, obs);
+  acs.startSearch("", params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "");
   do_check_eq(obs._result.searchResult, ACR.RESULT_FAILURE);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
 
   // Test - Check ignoring result with comma
 
-  acs.startSearch("a,b", identity.key, null, obs);
+  acs.startSearch("a,b", params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "a,b");
   do_check_eq(obs._result.searchResult, ACR.RESULT_FAILURE);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
 
   // Test - Check returning search string with @ sign
 
-  acs.startSearch("a@b", identity.key, null, obs);
+  acs.startSearch("a@b", params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "a@b");
   do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 1);
 
   do_check_eq(obs._result.getValueAt(0), "a@b");
   do_check_eq(obs._result.getLabelAt(0), "a@b");
   do_check_eq(obs._result.getCommentAt(0), null);
   do_check_eq(obs._result.getStyleAt(0), "default-match");
   do_check_eq(obs._result.getImageAt(0), null);
 
+  // No autocomplete for addr_newsgroups!
+  acs.startSearch("a@b", paramsNews, null, obsNews);
+  do_check_true(obsNews._result == null || obsNews._result.matchCount == 0);
+
+  // No autocomplete for addr_followup!
+  acs.startSearch("a@b", paramsFollowup, null, obsFollowup);
+  do_check_true(obsFollowup._result == null || obsFollowup._result.matchCount == 0);
+
+
   // Test - Add default domain
 
-  acs.startSearch("test1", identity.key, null, obs);
+  acs.startSearch("test1", params, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "test1");
   do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 1);
 
   do_check_eq(obs._result.getValueAt(0), "test1@foo.invalid");
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch1.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch1.js
@@ -101,102 +101,116 @@ function run_test() {
   testAB.copyTo(do_get_profile(), kCABData.fileName);
 
   // Test - Create a new search component
 
   var acs = Components.classes["@mozilla.org/autocomplete/search;1?name=addrbook"]
     .getService(Components.interfaces.nsIAutoCompleteSearch);
 
   var obs = new acObserver();
+  let obsNews = new acObserver();
+  let obsFollowup = new acObserver();
 
   // Test - Check disabling of autocomplete
 
   Services.prefs.setBoolPref("mail.enable_autocomplete", false);
 
-  acs.startSearch("abc", null, null, obs);
+  let param = JSON.stringify({ type: "addr_to"  });
+  let paramNews = JSON.stringify({ type: "addr_newsgroups"  });
+  let paramFollowup = JSON.stringify({ type: "addr_followup"  });
+
+  acs.startSearch("abc", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "abc");
   do_check_eq(obs._result.searchResult, ACR.RESULT_NOMATCH);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
 
   // Test - Check Enabling of autocomplete, but with empty string.
 
   Services.prefs.setBoolPref("mail.enable_autocomplete", true);
 
-  acs.startSearch(null, null, null, obs);
+  acs.startSearch(null, param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, null);
   do_check_eq(obs._result.searchResult, ACR.RESULT_IGNORED);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
   do_check_eq(obs._result.defaultIndex, -1);
 
   // Test - Check ignoring result with comma
 
-  acs.startSearch("a,b", null, null, obs);
+  acs.startSearch("a,b", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "a,b");
   do_check_eq(obs._result.searchResult, ACR.RESULT_IGNORED);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
   do_check_eq(obs._result.defaultIndex, -1);
 
   // Test - No matches
 
-  acs.startSearch("asjdkljdgfjglkfg", null, null, obs);
+  acs.startSearch("asjdkljdgfjglkfg", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "asjdkljdgfjglkfg");
   do_check_eq(obs._result.searchResult, ACR.RESULT_NOMATCH);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 0);
   do_check_eq(obs._result.defaultIndex, -1);
 
   // Test - Matches
 
   // Basic quick-check
-  acs.startSearch("email", null, null, obs);
+  acs.startSearch("email", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "email");
   do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 2);
   do_check_eq(obs._result.defaultIndex, 0);
 
   do_check_eq(obs._result.getValueAt(0), "DisplayName1 <PrimaryEmail1@test.invalid>");
   do_check_eq(obs._result.getLabelAt(0), "DisplayName1 <PrimaryEmail1@test.invalid>");
   do_check_eq(obs._result.getCommentAt(0), "");
   do_check_eq(obs._result.getStyleAt(0), "local-abook");
   do_check_eq(obs._result.getImageAt(0), "");
 
+  // quick-check that nothing is found for addr_newsgroups
+  acs.startSearch("email", paramNews, null, obsNews);
+  do_check_true(obsNews._result == null || obsNews._result.matchCount == 0);
+
+  // quick-check that nothing is found for  addr_followup
+  acs.startSearch("a@b", paramFollowup, null, obsFollowup);
+  do_check_true(obsFollowup._result == null || obsFollowup._result.matchCount == 0);
+
   // Now quick-check with the address book name in the comment column.
   Services.prefs.setIntPref("mail.autoComplete.commentColumn", 1);
 
-  acs.startSearch("email", null, null, obs);
+  acs.startSearch("email", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "email");
   do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 2);
   do_check_eq(obs._result.defaultIndex, 0);
 
   do_check_eq(obs._result.getValueAt(0), "DisplayName1 <PrimaryEmail1@test.invalid>");
   do_check_eq(obs._result.getLabelAt(0), "DisplayName1 <PrimaryEmail1@test.invalid>");
   do_check_eq(obs._result.getCommentAt(0), kCABData.dirName);
   do_check_eq(obs._result.getStyleAt(0), "local-abook");
   do_check_eq(obs._result.getImageAt(0), "");
 
   // Check input with different case
-  acs.startSearch("EMAIL", null, null, obs);
+  acs.startSearch("EMAIL", param, null, obs);
 
   do_check_eq(obs._search, acs);
   do_check_eq(obs._result.searchString, "EMAIL");
   do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
   do_check_eq(obs._result.errorDescription, null);
   do_check_eq(obs._result.matchCount, 2);
   do_check_eq(obs._result.defaultIndex, 0);
 
@@ -205,17 +219,17 @@ function run_test() {
   do_check_eq(obs._result.getCommentAt(0), kCABData.dirName);
   do_check_eq(obs._result.getStyleAt(0), "local-abook");
   do_check_eq(obs._result.getImageAt(0), "");
 
 
   // Now check multiple matches
   function checkInputItem(element, index, array) {
     print("Checking " + element.search);
-    acs.startSearch(element.search, null, null, obs);
+    acs.startSearch(element.search, param, null, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element.search);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, element.expected.length);
     do_check_eq(obs._result.defaultIndex, 0);
 
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch2.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch2.js
@@ -166,17 +166,17 @@ function run_test() {
     });
   }
 
 
   // Test - Matches
 
   // Now check multiple matches
   function checkInputItem(element, index, array) {
-    acs.startSearch(element.search, null, lastResult, obs);
+    acs.startSearch(element.search, JSON.stringify({ type: "addr_to"  }), lastResult, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element.search);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, element.expected.length);
 
     for (var i = 0; i < element.expected.length; ++i) {
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch3.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch3.js
@@ -68,17 +68,17 @@ function run_test()
 
   var acs = Components.classes["@mozilla.org/autocomplete/search;1?name=addrbook"]
     .getService(Components.interfaces.nsIAutoCompleteSearch);
 
   var obs = new acObserver();
 
   function checkInputItem(element, index, array) {
     print("Checking " + element.search);
-    acs.startSearch(element.search, null, null, obs);
+    acs.startSearch(element.search, JSON.stringify({ type: "addr_to"  }), null, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element.search);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, element.expected.length);
 
     for (var i = 0; i < element.expected.length; ++i)
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch4.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch4.js
@@ -123,17 +123,17 @@ function run_test()
     .getService(Components.interfaces.nsIAutoCompleteSearch);
 
   var obs = new acObserver();
 
   print("Checking Initial Searches");
 
   function checkSearch(element, index, array) {
     print("Checking " + element);
-    acs.startSearch(element, null, null, obs)
+    acs.startSearch(element, JSON.stringify({ type: "addr_to"  }), null, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, expectedResults[index].length);
 
     for (var i = 0; i < expectedResults[index].length; ++i) {
@@ -148,17 +148,17 @@ function run_test()
 
   searches.forEach(checkSearch);
 
   print("Checking Reduction of Search Results");
 
   var lastResult = null;
 
   function checkReductionSearch(element, index, array) {
-    acs.startSearch(element, null, lastResult, obs);
+    acs.startSearch(element, JSON.stringify({ type: "addr_to"  }), lastResult, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, reductionExpectedResults[index].length);
 
     for (var i = 0; i < reductionExpectedResults[index].length; ++i) {
--- a/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js
+++ b/mailnews/addrbook/test/unit/test_nsAbAutoCompleteSearch5.js
@@ -59,17 +59,17 @@ function run_test() {
   // Ensure we've got the comment column set up for extra checking.
   Services.prefs.setIntPref("mail.autoComplete.commentColumn", 1);
 
   // Test - Matches
 
   // Now check multiple matches
   function checkInputItem(element, index, array) {
     print("Checking " + element.search);
-    acs.startSearch(element.search, null, null, obs);
+    acs.startSearch(element.search, JSON.stringify({ type: "addr_to" }), null, obs);
 
     do_check_eq(obs._search, acs);
     do_check_eq(obs._result.searchString, element.search);
     do_check_eq(obs._result.searchResult, ACR.RESULT_SUCCESS);
     do_check_eq(obs._result.errorDescription, null);
     do_check_eq(obs._result.matchCount, element.expected.length);
     do_check_eq(obs._result.defaultIndex, 0);
 
--- a/mailnews/news/src/moz.build
+++ b/mailnews/news/src/moz.build
@@ -15,15 +15,20 @@ SOURCES += [
     'nsNntpMockChannel.cpp',
     'nsNNTPNewsgroupList.cpp',
     'nsNNTPNewsgroupPost.cpp',
     'nsNNTPProtocol.cpp',
     'nsNntpService.cpp',
     'nsNntpUrl.cpp',
 ]
 
+EXTRA_COMPONENTS += [
+    'nsNewsAutoCompleteSearch.js',
+    'nsNewsAutoCompleteSearch.manifest',
+]
+
 if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']:
     FORCE_STATIC_LIB = True
 else:
     LIBXUL_LIBRARY = True
 
 LIBRARY_NAME = 'msgnews_s'
 
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.js
@@ -0,0 +1,137 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const kACR = Ci.nsIAutoCompleteResult;
+const kSupportedTypes = Set(["addr_newsgroups", "addr_followup"]);
+
+function nsNewsAutoCompleteResult(aSearchString) {
+  // Can't create this in the prototype as we'd get the same array for
+  // all instances
+  this._searchResults = [];
+  this.searchString = aSearchString;
+}
+
+nsNewsAutoCompleteResult.prototype = {
+  _searchResults: null,
+
+  // nsIAutoCompleteResult
+
+  searchString: null,
+  searchResult: kACR.RESULT_NOMATCH,
+  defaultIndex: -1,
+  errorDescription: null,
+
+  get matchCount() {
+    return this._searchResults.length;
+  },
+
+  getValueAt: function getValueAt(aIndex) {
+    return this._searchResults[aIndex].value;
+  },
+
+  getLabelAt: function getLabelAt(aIndex) {
+    return this._searchResults[aIndex].value;
+  },
+
+  getCommentAt: function getCommentAt(aIndex) {
+    return this._searchResults[aIndex].comment;
+  },
+
+  getStyleAt: function getStyleAt(aIndex) {
+    return "subscribed-news";
+  },
+
+  getImageAt: function getImageAt(aIndex) {
+    return "";
+  },
+
+  removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) {
+  },
+
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([kACR])
+}
+
+function nsNewsAutoCompleteSearch() {}
+
+nsNewsAutoCompleteSearch.prototype = {
+  // For component registration
+  classDescription: "Newsgroup Autocomplete",
+  classID: Components.ID("e9bb3330-ac7e-11de-8a39-0800200c9a66"),
+
+  cachedAccountKey: "",
+  cachedServer: null,
+
+  /**
+   * Find the newsgroup server associated with the given accountKey.
+   *
+   * @param accountKey  The key of the account.
+   * @return            The incoming news server (or null if one does not exist).
+   */
+  _findServer: function _findServer(accountKey) {
+    let account = MailServices.accounts.getAccount(accountKey);
+
+    if (account.incomingServer.type == 'nntp')
+      return account.incomingServer;
+    else
+      return null;
+  },
+
+  // nsIAutoCompleteSearch
+  startSearch: function startSearch(aSearchString, aSearchParam,
+                                    aPreviousResult, aListener) {
+    let params = JSON.parse(aSearchParam);
+    let result = new nsNewsAutoCompleteResult(aSearchString);
+    if (!params.type || !params.accountKey ||
+        !kSupportedTypes.has(params.type)) {
+      result.searchResult = kACR.RESULT_IGNORED;
+      aListener.onSearchResult(this, result);
+      return;
+    }
+
+    if (params.accountKey != this.cachedAccountKey) {
+      this.cachedAccountKey  = params.accountKey;
+      this.cachedServer = this._findServer(params.accountKey);
+    }
+
+    if (this.cachedServer) {
+      let groups = this.cachedServer.rootFolder.subFolders;
+      while (groups.hasMoreElements()) {
+        let curr = groups.getNext().QueryInterface(Ci.nsIMsgFolder);
+        if (curr.prettiestName.contains(aSearchString)) {
+          result._searchResults.push({
+            value: curr.prettiestName,
+            comment: this.cachedServer.prettyName
+          });
+        }
+      }
+    }
+
+    if (result.matchCount) {
+      result.searchResult = kACR.RESULT_SUCCESS;
+      // If the user does not select anything, use the first entry:
+      result.defaultIndex = 0;
+    }
+    aListener.onSearchResult(this, result);
+  },
+
+  stopSearch: function stopSearch() {
+  },
+
+  // nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch])
+};
+
+// Module
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsNewsAutoCompleteSearch]);
new file mode 100644
--- /dev/null
+++ b/mailnews/news/src/nsNewsAutoCompleteSearch.manifest
@@ -0,0 +1,2 @@
+component {e9bb3330-ac7e-11de-8a39-0800200c9a66} nsNewsAutoCompleteSearch.js
+contract @mozilla.org/autocomplete/search;1?name=news {e9bb3330-ac7e-11de-8a39-0800200c9a66}
--- a/mailnews/news/test/unit/head_server_setup.js
+++ b/mailnews/news/test/unit/head_server_setup.js
@@ -90,37 +90,40 @@ function makeServer(handler, daemon) {
 Services.prefs.setBoolPref("mail.strict_threading", true);
 
 
 // Make sure we don't try to use a protected port. I like adding 1024 to the
 // default port when doing so...
 const NNTP_PORT = 1024+119;
 
 var _server = null;
+let _account = null;
 
 function subscribeServer(incomingServer) {
   // Subscribe to newsgroups
   incomingServer.QueryInterface(Ci.nsINntpIncomingServer);
   groups.forEach(function (element) {
       if (element[1])
         incomingServer.subscribeToNewsgroup(element[0]);
     });
   // Only allow one connection
   incomingServer.maximumConnectionsNumber = 1;
 }
 
 // Sets up the client-side portion of fakeserver
 function setupLocalServer(port) {
   if (_server != null)
     return _server;
-  let server = localAccountUtils.create_incoming_server("nntp", port,
-							null, null);
+  let serverAndAccount =
+    localAccountUtils.create_incoming_server_and_account("nntp", port, null, null);
+  let server = serverAndAccount.server;
   subscribeServer(server);
 
   _server = server;
+  _account = serverAndAccount.account;
 
   return server;
 }
 
 const URLCreator = MailServices.nntp.QueryInterface(Ci.nsIProtocolHandler);
 
 // Sets up a protocol object and prepares to run the test for the news url
 function setupProtocolTest(port, newsUrl, incomingServer) {
new file mode 100644
--- /dev/null
+++ b/mailnews/news/test/unit/test_newsAutocomplete.js
@@ -0,0 +1,107 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+// The basic daemon to use for testing nntpd.js implementations
+var gDaemon = setupNNTPDaemon();
+
+const ACR = Components.interfaces.nsIAutoCompleteResult;
+
+function acObserver() {}
+
+acObserver.prototype = {
+  _search: null,
+  _result: null,
+
+  onSearchResult: function (aSearch, aResult) {
+    this._search = aSearch;
+    this._result = aResult;
+  }
+};
+
+function run_test() {
+  let type = "RFC 977";
+  let localserver = setupLocalServer(NNTP_PORT);
+  let server = makeServer(NNTP_RFC977_handler, gDaemon);
+
+  // create identity
+  let identity = MailServices.accounts.createIdentity();
+  _account.addIdentity(identity);
+
+  let acs = Components.classes["@mozilla.org/autocomplete/search;1?name=news"]
+    .getService(Components.interfaces.nsIAutoCompleteSearch);
+  let obs;
+
+  let paramsN = JSON.stringify({
+    idKey: identity.key,
+    accountKey: _account.key,
+    type: "addr_newsgroups" });
+  let paramsF = JSON.stringify({
+    idKey: identity.key,
+    accountKey: _account.key,
+    type: "addr_followup" });
+  let paramsMail = JSON.stringify({
+    idKey: identity.key,
+    accountKey: _account.key,
+    type: "addr_to" });
+
+  // misc.test is not subscribed
+  obs = new acObserver();
+  acs.startSearch("misc", paramsN, null, obs);
+  do_check_true(obs._result == null || obs._result.matchCount == 0);
+
+  obs = new acObserver();
+  acs.startSearch("misc", paramsF, null, obs);
+  do_check_true(obs._result == null || obs._result.matchCount == 0);
+
+  obs = new acObserver();
+  acs.startSearch("misc", paramsMail, null, obs);
+  do_check_true(obs._result == null || obs._result.matchCount == 0);
+
+  // test.filter is subscribed
+  obs = new acObserver();
+  acs.startSearch("filter", paramsN, null, obs);
+  do_check_eq(obs._result.matchCount, 1);
+
+  obs = new acObserver();
+  acs.startSearch("filter", paramsF, null, obs);
+  do_check_eq(obs._result.matchCount, 1);
+
+  // ... but no auto-complete should occur for addr_to
+  obs = new acObserver();
+  acs.startSearch("filter", paramsMail, null, obs);
+  do_check_true(obs._result == null || obs._result.matchCount == 0);
+
+  // test.subscribe.empty and test.subscribe.simple are subscribed
+  obs = new acObserver();
+  acs.startSearch("subscribe", paramsN, null, obs);
+  do_check_eq(obs._result.matchCount, 2);
+
+  obs = new acObserver();
+  acs.startSearch("subscribe", paramsF, null, obs);
+  do_check_eq(obs._result.matchCount, 2);
+
+  // ... but no auto-complete should occur for addr_to
+  obs = new acObserver();
+  acs.startSearch("subscribe", paramsMail, null, obs);
+  do_check_true(obs._result == null || obs._result.matchCount == 0);
+
+  // test.subscribe.empty is subscribed, test.empty is not
+  obs = new acObserver();
+  acs.startSearch("empty", paramsN, null, obs);
+  do_check_eq(obs._result.matchCount, 1);
+
+  obs = new acObserver();
+  acs.startSearch("empty", paramsF, null, obs);
+  do_check_eq(obs._result.matchCount, 1);
+
+  let thread = gThreadManager.currentThread;
+  while (thread.hasPendingEvents())
+    thread.processNextEvent(true);
+};
--- a/mailnews/news/test/unit/xpcshell.ini
+++ b/mailnews/news/test/unit/xpcshell.ini
@@ -10,16 +10,17 @@ support-files = postings/*
 [test_bug540288.js]
 [test_bug695309.js]
 [test_filter.js]
 [test_getNewsMessage.js]
 [test_internalUris.js]
 [test_nntpContentLength.js]
 # The server doesn't support returning sizes! (bug 782629)
 skip-if = true
+[test_newsAutocomplete.js]
 [test_nntpGroupPassword.js]
 [test_nntpPassword.js]
 [test_nntpPassword2.js]
 [test_nntpPassword3.js]
 [test_nntpPasswordFailure.js]
 [test_nntpPost.js]
 [test_nntpProtocols.js]
 [test_nntpUrl.js]
--- a/mailnews/test/resources/localAccountUtils.js
+++ b/mailnews/test/resources/localAccountUtils.js
@@ -54,16 +54,33 @@ var localAccountUtils = {
    *
    * @param aType The type of the server (pop3, imap etc).
    * @param aPort The port the server is on.
    * @param aUsername The username for the server.
    * @param aPassword The password for the server.
    * @return The newly-created nsIMsgIncomingServer.
    */
   create_incoming_server: function(aType, aPort, aUsername, aPassword) {
+    let serverAndAccount = localAccountUtils.
+      create_incoming_server_and_account(aType, aPort, aUsername, aPassword);
+    return serverAndAccount.server;
+  },
+
+  /**
+   * Create an nsIMsgIncomingServer and an nsIMsgAccount to go with it.
+   *
+   * @param aType The type of the server (pop3, imap etc).
+   * @param aPort The port the server is on.
+   * @param aUsername The username for the server.
+   * @param aPassword The password for the server.
+   * @return An object with the newly-created nsIMsgIncomingServer as the
+             "server" property and the newly-created nsIMsgAccount as the
+             "account" property.
+   */
+  create_incoming_server_and_account: function (aType, aPort, aUsername, aPassword) {
     let server = MailServices.accounts.createIncomingServer(aUsername, "localhost",
                                                             aType);
     server.port = aPort;
     if (aUsername != null)
       server.username = aUsername;
     if (aPassword != null)
       server.password = aPassword;
 
@@ -75,17 +92,17 @@ var localAccountUtils = {
       // Several tests expect that mail is deferred to the local folders account,
       // so do that.
       this.loadLocalMailAccount();
       server.QueryInterface(Ci.nsIPop3IncomingServer);
       server.deferredToAccount = this.msgAccount.key;
     }
     server.valid = true;
 
-    return server;
+    return {server: server, account: account};
   },
 
   /**
    * Create an outgoing nsISmtpServer with the given parameters.
    *
    * @param aPort The port the server is on.
    * @param aUsername The username for the server
    * @param aPassword The password for the server
--- a/suite/installer/package-manifest.in
+++ b/suite/installer/package-manifest.in
@@ -755,16 +755,18 @@ bin/libfreebl_32int64_3.so
 @BINPATH@/components/newsblog.manifest
 @BINPATH@/components/nsAbAutoCompleteMyDomain.js
 @BINPATH@/components/nsAbAutoCompleteSearch.js
 @BINPATH@/components/nsAbLDAPAttributeMap.js
 @BINPATH@/components/nsAbLDAPAutoCompleteSearch.js
 @BINPATH@/components/nsMailNewsCommandLineHandler.js
 @BINPATH@/components/nsMsgTraitService.js
 @BINPATH@/components/nsMsgTraitService.manifest
+@BINPATH@/components/nsNewsAutoCompleteSearch.js
+@BINPATH@/components/nsNewsAutoCompleteSearch.manifest
 @BINPATH@/components/nsSMTPProtocolHandler.js
 @BINPATH@/components/nsSMTPProtocolHandler.manifest
 @BINPATH@/components/offlineStartup.js
 @BINPATH@/components/offlineStartup.manifest
 @BINPATH@/components/smime-service.js
 @BINPATH@/components/smime-service.manifest
 
 ; MailNews chrome
--- a/suite/mailnews/compose/MsgComposeCommands.js
+++ b/suite/mailnews/compose/MsgComposeCommands.js
@@ -2456,19 +2456,28 @@ function LoadIdentity(startup)
 {
     var identityElement = GetMsgIdentityElement();
     var prevIdentity = gCurrentIdentity;
 
     if (identityElement) {
         var idKey = identityElement.value;
         gCurrentIdentity = gAccountManager.getIdentity(idKey);
 
+        let accountKey = null;
+        if (identityElement.selectedItem)
+          accountKey = identityElement.selectedItem.getAttribute("accountkey");
+
         let maxRecipients = awGetMaxRecipients();
         for (let i = 1; i <= maxRecipients; i++)
-          awGetInputElement(i).setAttribute("autocompletesearchparam", idKey);
+        {
+          let params = JSON.parse(awGetInputElement(i).searchParam);
+          params.idKey = idKey;
+          params.accountKey = accountKey;
+          awGetInputElement(i).searchParam = JSON.stringify(params);
+        }
 
         if (!startup && prevIdentity && idKey != prevIdentity.key)
         {
           var prevReplyTo = prevIdentity.replyTo;
           var prevCc = "";
           var prevBcc = "";
           var prevReceipt = prevIdentity.requestReturnReceipt;
           var prevDSN = prevIdentity.requestDSN;
@@ -2567,20 +2576,16 @@ function LoadIdentity(startup)
           addRecipientsToIgnoreList(gCurrentIdentity.identityName);
       }
     }
 }
 
 function setupAutocomplete()
 {
   var autoCompleteWidget = document.getElementById("addressCol2#1");
-  // When autocompleteToMyDomain is off, there is no default entry with the domain
-  // appended, so reduce the minimum results for a popup to 2 in this case.
-  if (!gCurrentIdentity.autocompleteToMyDomain)
-    autoCompleteWidget.minResultsForPopup = 2;
 
   // if the pref is set to turn on the comment column, honor it here.
   // this element then gets cloned for subsequent rows, so they should
   // honor it as well
   //
   try {
       if (getPref("mail.autoComplete.highlightNonMatches"))
         autoCompleteWidget.highlightNonMatches = true;
--- a/suite/mailnews/compose/addressingWidgetOverlay.js
+++ b/suite/mailnews/compose/addressingWidgetOverlay.js
@@ -716,19 +716,19 @@ function awGetNumberOfRecipients()
 function DropRecipient(recipient)
 {
   // break down and add each address
   return parseAndAddAddresses(recipient, awGetPopupElement(top.MAX_RECIPIENTS).selectedItem.getAttribute("value"));
 }
 
 function _awSetAutoComplete(selectElem, inputElem)
 {
-  inputElem.disableAutoComplete = selectElem.value == 'addr_newsgroups' ||
-                                  selectElem.value == 'addr_followup' ||
-                                  selectElem.value == 'addr_other';
+  let params = JSON.parse(inputElem.getAttribute('autocompletesearchparam'));
+  params.type = selectElem.value;
+  inputElem.setAttribute('autocompletesearchparam', JSON.stringify(params));
 }
 
 function awSetAutoComplete(rowNumber)
 {
     var inputElem = awGetInputElement(rowNumber);
     var selectElem = awGetPopupElement(rowNumber);
     _awSetAutoComplete(selectElem, inputElem)
 }
--- a/suite/mailnews/compose/addressingWidgetOverlay.xul
+++ b/suite/mailnews/compose/addressingWidgetOverlay.xul
@@ -41,19 +41,20 @@
         </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 ldap" timeout="300"
+                 autocompletesearch="mydomain addrbook ldap news" timeout="300"
+                 autocompletesearchparam="{}"
                  completedefaultindex="true" forcecomplete="true"
-                 minresultsforpopup="3" ignoreblurwhilesearching="true"
+                 minresultsforpopup="2" 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();"/>
         </textbox>
       </listcell>
--- a/suite/themes/classic/mac/messenger/messengercompose/messengercompose.css
+++ b/suite/themes/classic/mac/messenger/messengercompose/messengercompose.css
@@ -210,16 +210,22 @@ treechildren::-moz-tree-image(remote-abo
 treechildren::-moz-tree-image(remote-err) {
   margin-top: 2px;
   margin-bottom: 2px;
   -moz-margin-start: 2px;
   -moz-margin-end: -3px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/remote-addrbook-error.gif");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 2px;
+  -moz-margin-end: -3px;
+  list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup.png");
+}
+
 /* ::::: lightweight themes ::::: */
 
 #MsgHeadersToolbar:-moz-lwtheme,
 #FormatToolbar:-moz-lwtheme,
 #compose-toolbar-sizer:-moz-lwtheme {
   text-shadow: none;
   color: -moz-dialogtext;
   background-color: -moz-dialog;
--- a/suite/themes/classic/messenger/messengercompose/messengercompose.css
+++ b/suite/themes/classic/messenger/messengercompose/messengercompose.css
@@ -251,16 +251,22 @@ treechildren::-moz-tree-image(remote-abo
 treechildren::-moz-tree-image(remote-err) {
   margin-top: 2px;
   margin-bottom: 2px;
   -moz-margin-start: 2px;
   -moz-margin-end: -3px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/remote-addrbook-error.gif");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 2px;
+  -moz-margin-end: -3px;
+  list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup.png");
+}
+
 /* ::::: lightweight themes ::::: */
 
 #MsgHeadersToolbar:-moz-lwtheme,
 #FormatToolbar:-moz-lwtheme,
 #compose-toolbar-sizer:-moz-lwtheme {
   text-shadow: none;
   color: -moz-dialogtext;
   background-color: -moz-dialog;
--- a/suite/themes/modern/messenger/messengercompose/messengercompose.css
+++ b/suite/themes/modern/messenger/messengercompose/messengercompose.css
@@ -195,8 +195,13 @@ treechildren::-moz-tree-image(remote-abo
 treechildren::-moz-tree-image(remote-err) {
   margin-top: 2px;
   margin-bottom: 2px;
   -moz-margin-start: 3px;
   -moz-margin-end: -4px;
   list-style-image: url("chrome://messenger/skin/addressbook/icons/directory-down.gif");
 }
 
+treechildren::-moz-tree-image(subscribed-news) {
+  -moz-margin-start: 3px;
+  -moz-margin-end: -4px;
+  list-style-image: url("chrome://messenger/skin/icons/folder-newsgroup.gif");
+}