Bug 592710 - Account manager permanently disables unrecognized extension-added account. r=standard8
authorKent James <kent@caspia.com>
Fri, 24 Aug 2012 21:12:09 -0700
changeset 13345 5faefe5943784abcf73a3f1d4527e7da6b6d70b5
parent 13344 df20a841d4e37ae8d9fc2c2262f5f8a72f72f19f
child 13346 3bb8032572f1761c8da1e4d681f46770bc84d620
push id701
push userbugzilla@standard8.plus.com
push dateMon, 08 Oct 2012 19:06:59 +0000
treeherdercomm-beta@c9696e33af8e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstandard8
bugs592710
Bug 592710 - Account manager permanently disables unrecognized extension-added account. r=standard8
mailnews/base/public/nsIMsgAccount.idl
mailnews/base/src/nsMsgAccount.cpp
mailnews/base/src/nsMsgAccount.h
mailnews/base/src/nsMsgAccountManager.cpp
mailnews/base/src/nsMsgAccountManager.h
mailnews/base/test/unit/test_accountMgrCustomTypes.js
mailnews/base/test/unit/xpcshell.ini
--- a/mailnews/base/public/nsIMsgAccount.idl
+++ b/mailnews/base/public/nsIMsgAccount.idl
@@ -10,17 +10,17 @@
 
 /**
  * An account consists of an incoming server and one or more
  * outgoing identities. An account is identified by a key,
  * which is the <account> string in the account preferences,
  * such as in mail.account.<account>.identities.
  */
 
-[scriptable, uuid(3FE5B45D-ACF8-4348-8951-757D21E983F2)]
+[scriptable, uuid(76048A12-303C-4cf8-B592-1F83BD196B14)]
 interface nsIMsgAccount : nsISupports {
 
   /**
    * Initialize an account.
    *
    * @throws NS_ERROR_ALREADY_OPENED if it is called more then once.
    */
   void init();
@@ -43,9 +43,52 @@ interface nsIMsgAccount : nsISupports {
   /// Remove an identity from this account
   void removeIdentity(in nsIMsgIdentity identity);
 
   /// Clear all user preferences associated with an account.
   void clearAllValues();
 
   /// Name in javascript
   AString toString();
+
+  /**
+   * Create the server, with error returns.
+   *
+   * Normally each valid account also has a valid server.
+   *
+   * If an extension that creates a server type failed to load, then we
+   * may have an existing account without a valid server. We don't want
+   * to simply delete that account, as that would make the user re-enter
+   * all of the account information after what may be a temporary
+   * update glitch. But we also don't want to leave junk lying around
+   * forever. So what we do is note the time when we first noticed
+   * that the server was unavailable. After a period of time set
+   * in a preference, if the server is still unavailable then delete
+   * the associated account.
+   *
+   * Accounts with invalid server are not shown in any way by the account
+   * manager. But if the server becomes available (for example if an extension
+   * is loaded), then the account will reappear when accounts are loaded.
+   *
+   * Preference definitions:
+   *
+   * mail.server.serverN.secondsToLeaveUnavailable
+   * mail.server.serverN.timeFoundUnavailable
+   *
+   * secondsToLeaveUnavailable: is set by the extension to indicate the
+   * delay, in seconds, between first detection that a server is
+   * unavailable, and the time it can be deleted. This should be set
+   * by the extension when the server is created. If missing, treat as 0.
+   * A typical value would be 2592000 (30 days)(24 hr/day)(3600 seconds/hr)
+   *
+   * timeFoundUnavailable: is set by core code the first time that a
+   * server is detected as unavailable, using now() converted to seconds.
+   * If that time + secondsToLeaveUnavailable is exceeded, core code may
+   * delete the server and its associated account (though for now we default
+   * to the previous behavior, which is to just delete the account).
+   *
+   * @throws NS_ERROR_NOT_AVAILABLE if the server component type could not
+   *           be created
+   *         NS_ERROR_ALREADY_INITIALIZED if the server is already created
+   *         (Other errors may also be possible)
+  */
+  void createServer();
 };
--- a/mailnews/base/src/nsMsgAccount.cpp
+++ b/mailnews/base/src/nsMsgAccount.cpp
@@ -23,16 +23,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsMemory.h"
 #include "nsComponentManagerUtils.h"
 #include "nsMsgUtils.h"
 
 NS_IMPL_ISUPPORTS1(nsMsgAccount, nsIMsgAccount)
 
 nsMsgAccount::nsMsgAccount()
+  : mTriedToGetServer(false)
 {
 }
 
 nsMsgAccount::~nsMsgAccount()
 {
 }
 
 NS_IMETHODIMP
@@ -54,27 +55,36 @@ nsMsgAccount::getPrefService()
 }
 
 NS_IMETHODIMP
 nsMsgAccount::GetIncomingServer(nsIMsgIncomingServer **aIncomingServer)
 {
   NS_ENSURE_ARG_POINTER(aIncomingServer);
 
   // create the incoming server lazily
-  if (!m_incomingServer) {
-    // ignore the error (and return null), but it's still bad so assert
+  if (!mTriedToGetServer && !m_incomingServer) {
+    mTriedToGetServer = true;
+    // ignore the error (and return null), but it's still bad so warn
     nsresult rv = createIncomingServer();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "couldn't lazily create the server\n");
   }
 
   NS_IF_ADDREF(*aIncomingServer = m_incomingServer);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsMsgAccount::CreateServer()
+{
+  if (m_incomingServer)
+    return NS_ERROR_ALREADY_INITIALIZED;
+  return createIncomingServer();
+}
+
 nsresult
 nsMsgAccount::createIncomingServer()
 {
   NS_ENSURE_FALSE(m_accountKey.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
 
   // from here, load mail.account.myaccount.server
   // Load the incoming server
   //
--- a/mailnews/base/src/nsMsgAccount.h
+++ b/mailnews/base/src/nsMsgAccount.h
@@ -25,10 +25,13 @@ private:
 
   nsCOMPtr<nsIMsgIdentity> m_defaultIdentity;
   nsCOMPtr<nsISupportsArray> m_identities;
 
   nsresult getPrefService();
   nsresult createIncomingServer();
   nsresult createIdentities();
   nsresult addIdentityInternal(nsIMsgIdentity* identity);
+
+  // Have we tried to get the server yet?
+  bool mTriedToGetServer;
 };
 
--- a/mailnews/base/src/nsMsgAccountManager.cpp
+++ b/mailnews/base/src/nsMsgAccountManager.cpp
@@ -361,16 +361,67 @@ nsMsgAccountManager::getUniqueAccountKey
       aResult = ACCOUNT_PREFIX;
       aResult.AppendInt(i++);
       findEntry.key = aResult.get();
       accounts->EnumerateForwards(findAccountByKey, (void *)&findEntry);
     } while (findEntry.account);
   }
 }
 
+void
+nsMsgAccountManager::GetUniqueServerKey(nsACString& aResult)
+{
+  nsCAutoString prefResult;
+  bool usePrefsScan = true;
+  nsresult rv;
+  nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID,
+                                       &rv));
+  if (NS_FAILED(rv))
+    usePrefsScan = false;
+
+  // Loop over existing pref names mail.server.server(lastKey).type
+  nsCOMPtr<nsIPrefBranch> prefBranchServer;
+  if (prefService)
+  {
+    rv = prefService->GetBranch(PREF_MAIL_SERVER_PREFIX, getter_AddRefs(prefBranchServer));
+    if (NS_FAILED(rv))
+      usePrefsScan = false;
+  }
+
+  if (usePrefsScan)
+  {
+    nsCAutoString type;
+    nsCAutoString typeKey;
+    for (PRInt32 lastKey = 1; ; lastKey++)
+    {
+      aResult.AssignLiteral(SERVER_PREFIX);
+      aResult.AppendInt(lastKey);
+      typeKey.Assign(aResult);
+      typeKey.AppendLiteral(".type");
+      prefBranchServer->GetCharPref(typeKey.get(), getter_Copies(type));
+      if (type.IsEmpty()) // a server slot with no type is considered empty
+        return;
+    }
+  }
+  else
+  {
+    // If pref service fails, try to find a free serverX key
+    // by checking which keys exist.
+    nsCAutoString internalResult;
+    nsCOMPtr<nsIMsgIncomingServer> server;
+    PRInt32 i = 1;
+    do {
+      aResult.AssignLiteral(SERVER_PREFIX);
+      aResult.AppendInt(i++);
+      m_incomingServers.Get(aResult, getter_AddRefs(server));
+    } while (server);
+    return;
+  }
+}
+
 nsresult
 nsMsgAccountManager::CreateIdentity(nsIMsgIdentity **_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
   nsresult rv;
   nsCAutoString key;
   nsCOMPtr<nsIMsgIdentity> identity;
   int32_t i = 1;
@@ -430,23 +481,17 @@ nsMsgAccountManager::CreateIncomingServe
                                           nsIMsgIncomingServer **_retval)
 {
   NS_ENSURE_ARG_POINTER(_retval);
 
   nsresult rv = LoadAccounts();
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCAutoString key;
-  nsCOMPtr<nsIMsgIncomingServer> server;
-  int32_t i = 1;
-  do {
-    key.AssignLiteral(SERVER_PREFIX);
-    key.AppendInt(i++);
-    m_incomingServers.Get(key, getter_AddRefs(server));
-  } while (server);
+  GetUniqueServerKey(key);
   rv = createKeyedServer(key, username, hostname, type, _retval);
   if (*_retval)
   {
     nsCString defaultStore;
     m_prefs->GetCharPref("mail.serverDefaultStoreContractID", getter_Copies(defaultStore));
     (*_retval)->SetCharValue("storeContractID", defaultStore);
   }
   return rv;
@@ -488,17 +533,16 @@ nsMsgAccountManager::GetIncomingServer(c
 
   // .hostname
   serverPref = serverPrefPrefix;
   serverPref.AppendLiteral(".hostname");
   nsCString hostname;
   rv = m_prefs->GetCharPref(serverPref.get(), getter_Copies(hostname));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
 
-  // the server type doesn't exist. That's bad.
   return createKeyedServer(key, username, hostname, serverType, _retval);
 }
 
 NS_IMETHODIMP
 nsMsgAccountManager::RemoveIncomingServer(nsIMsgIncomingServer *aServer,
                                           bool aCleanupFiles)
 {
   NS_ENSURE_ARG_POINTER(aServer);
@@ -584,19 +628,20 @@ nsMsgAccountManager::createKeyedServer(c
   nsresult rv;
   *aServer = nullptr;
 
   //construct the contractid
   nsCAutoString serverContractID(NS_MSGINCOMINGSERVER_CONTRACTID_PREFIX);
   serverContractID += type;
 
   // finally, create the server
+  // (This will fail if type is from an extension that has been removed)
   nsCOMPtr<nsIMsgIncomingServer> server =
            do_CreateInstance(serverContractID.get(), &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE);
 
   int32_t port;
   nsCOMPtr <nsIMsgIncomingServer> existingServer;
   server->SetKey(key);
   server->SetType(type);
   server->SetUsername(username);
   server->SetHostName(hostname);
   server->GetPort(&port);
@@ -678,16 +723,18 @@ nsMsgAccountManager::RemoveAccount(nsIMs
     identityArray->Count(&count);
     uint32_t i;
     for (i = 0; i < count; i++)
     {
       nsCOMPtr<nsIMsgIdentity> identity( do_QueryElementAt(identityArray, i, &rv));
       bool identityStillUsed = false;
       // for each identity, see if any existing account still uses it,
       // and if not, clear it.
+      // Note that we are also searching here accounts with missing servers from
+      //  unloaded extension types.
       if (NS_SUCCEEDED(rv))
       {
         uint32_t numAccounts;
         m_accounts->Count(&numAccounts);
         uint32_t index;
         for (index = 0; index < numAccounts && !identityStillUsed; index++)
         {
           nsCOMPtr<nsIMsgAccount> existingAccount;
@@ -758,45 +805,49 @@ nsMsgAccountManager::GetDefaultAccount(n
 
     nsCString defaultKey;
     rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, getter_Copies(defaultKey));
 
     if (NS_SUCCEEDED(rv))
       GetAccount(defaultKey, getter_AddRefs(m_defaultAccount));
 
     if (!m_defaultAccount) {
+      nsCOMPtr<nsIMsgAccount> firstAccount;
       uint32_t index;
       bool foundValidDefaultAccount = false;
       for (index = 0; index < count; index++) {
         nsCOMPtr<nsIMsgAccount> account( do_QueryElementAt(m_accounts, index, &rv));
         if (NS_SUCCEEDED(rv)) {
           // get incoming server
           nsCOMPtr <nsIMsgIncomingServer> server;
-          rv = account->GetIncomingServer(getter_AddRefs(server));
-          NS_ENSURE_SUCCESS(rv,rv);
+          // server could be null if created by an unloaded extension
+          (void) account->GetIncomingServer(getter_AddRefs(server));
 
           bool canBeDefaultServer = false;
           if (server)
+          {
             server->GetCanBeDefaultServer(&canBeDefaultServer);
+            if (!firstAccount)
+              firstAccount = account;
+          }
 
           // if this can serve as default server, set it as default and
           // break outof the loop.
           if (canBeDefaultServer) {
             SetDefaultAccount(account);
             foundValidDefaultAccount = true;
             break;
           }
         }
       }
 
       if (!foundValidDefaultAccount) {
         // get the first account and use it.
         // we need to fix this scenario.
         NS_WARNING("No valid default account found, just using first (FIXME)");
-        nsCOMPtr<nsIMsgAccount> firstAccount( do_QueryElementAt(m_accounts, 0));
         SetDefaultAccount(firstAccount);
       }
     }
   }
 
   NS_ADDREF(*aDefaultAccount = m_defaultAccount);
   return NS_OK;
 }
@@ -876,16 +927,18 @@ nsMsgAccountManager::setDefaultAccountPr
 
   return NS_OK;
 }
 
 // enumaration for sending unload notifications
 PLDHashOperator
 nsMsgAccountManager::hashUnloadServer(nsCStringHashKey::KeyType aKey, nsCOMPtr<nsIMsgIncomingServer>& aServer, void* aClosure)
 {
+  if (!aServer)
+    return PL_DHASH_NEXT;
   nsresult rv;
   nsMsgAccountManager *accountManager = (nsMsgAccountManager*) aClosure;
   accountManager->NotifyServerUnloaded(aServer);
 
   nsCOMPtr<nsIMsgFolder> rootFolder;
   rv = aServer->GetRootFolder(getter_AddRefs(rootFolder));
   if (NS_SUCCEEDED(rv)) {
     accountManager->mFolderListeners->EnumerateForwards(removeListenerFromFolder,
@@ -894,16 +947,18 @@ nsMsgAccountManager::hashUnloadServer(ns
     rootFolder->Shutdown(true);
   }
 
   return PL_DHASH_NEXT;
 }
 
 void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer *aServer)
 {
+  if (!aServer)
+    return;
   nsresult rv = aServer->Shutdown();
   NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed");
   rv = aServer->ForgetSessionPassword();
   NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove the password associated with server");
 }
 
 NS_IMETHODIMP nsMsgAccountManager::GetFolderCache(nsIMsgFolderCache* *aFolderCache)
 {
@@ -939,16 +994,19 @@ hashCleanupOnExit(nsCStringHashKey::KeyT
 {
   bool emptyTrashOnExit = false;
   bool cleanupInboxOnExit = false;
   nsresult rv;
 
   if (WeAreOffline())
     return PL_DHASH_STOP;
 
+  if (!aServer)
+    return PL_DHASH_NEXT;
+
   aServer->GetEmptyTrashOnExit(&emptyTrashOnExit);
   nsCOMPtr <nsIImapIncomingServer> imapserver = do_QueryInterface(aServer);
   if (imapserver)
   {
     imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit);
     imapserver->SetShuttingDown(true);
   }
   if (emptyTrashOnExit || cleanupInboxOnExit)
@@ -1055,24 +1113,26 @@ hashCleanupOnExit(nsCStringHashKey::KeyT
      }
    }
    return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
 hashCloseCachedConnections(nsCStringHashKey::KeyType aKey, nsCOMPtr<nsIMsgIncomingServer>& aServer, void* aClosure)
 {
-  aServer->CloseCachedConnections();
+  if (aServer)
+    aServer->CloseCachedConnections();
   return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
 hashShutdown(nsCStringHashKey::KeyType aKey, nsCOMPtr<nsIMsgIncomingServer>& aServer, void* aClosure)
 {
-  aServer->Shutdown();
+  if (aServer)
+    aServer->Shutdown();
   return PL_DHASH_NEXT;
 }
 
 /* readonly attribute nsISupportsArray accounts; */
 NS_IMETHODIMP
 nsMsgAccountManager::GetAccounts(nsISupportsArray **_retval)
 {
   nsresult rv;
@@ -1084,16 +1144,18 @@ nsMsgAccountManager::GetAccounts(nsISupp
   uint32_t numAccounts;
   m_accounts->Count(&numAccounts);
   for (uint32_t index = 0; index < numAccounts; index++)
   {
     nsCOMPtr<nsIMsgAccount> existingAccount = do_QueryElementAt(m_accounts, index, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIMsgIncomingServer> server;
     existingAccount->GetIncomingServer(getter_AddRefs(server));
+    if (!server)
+      continue;
     if (server)
     {
       bool hidden = false;
       server->GetHidden(&hidden);
       if (hidden)
         continue;
     }
     nsCOMPtr<nsISupports> accountsSupport = do_QueryInterface(existingAccount);
@@ -1183,16 +1245,18 @@ nsMsgAccountManager::getIdentitiesToArra
   return true;
 }
 
 static PLDHashOperator
 hashGetNonHiddenServersToArray(nsCStringHashKey::KeyType aKey,
                                nsCOMPtr<nsIMsgIncomingServer>& aServer,
                                void* aClosure)
 {
+  if (!aServer)
+    return PL_DHASH_NEXT;
   bool hidden = false;
   aServer->GetHidden(&hidden);
   if (hidden)
     return PL_DHASH_NEXT;
 
   nsCString type;
   NS_ENSURE_SUCCESS(aServer->GetType(type), PL_DHASH_NEXT);
 
@@ -1400,25 +1464,66 @@ nsMsgAccountManager::LoadAccounts()
       continue;
 
     if (NS_FAILED(createKeyedAccount(accountsArray[i],
                                      getter_AddRefs(account))) || !account)
     {
       NS_WARNING("unexpected entry in account list; prefs corrupt?");
       continue;
     }
+
+    // See nsIMsgAccount.idl for a description of the secondsToLeaveUnavailable
+    //  and timeFoundUnavailable preferences
+    nsCAutoString toLeavePref(PREF_MAIL_SERVER_PREFIX);
+    toLeavePref.Append(serverKey);
+    nsCAutoString unavailablePref(toLeavePref); // this is the server-specific prefix
+    unavailablePref.AppendLiteral(".timeFoundUnavailable");
+    toLeavePref.AppendLiteral(".secondsToLeaveUnavailable");
+    PRInt32 secondsToLeave = 0;
+    PRInt32 timeUnavailable = 0;
+
+    m_prefs->GetIntPref(toLeavePref.get(), &secondsToLeave);
+
     // force load of accounts (need to find a better way to do this)
     nsCOMPtr<nsISupportsArray> identities;
     account->GetIdentities(getter_AddRefs(identities));
 
-    nsCOMPtr<nsIMsgIncomingServer> server;
-    account->GetIncomingServer(getter_AddRefs(server));
-    // If we couldn't create the server, the account is either horked
-    // or a duplicate. Add it to the list of accounts to be cleaned up.
-    if (!server)
+    rv = account->CreateServer();
+    bool deleteAccount = NS_FAILED(rv);
+
+    if (secondsToLeave)
+    { // we need to process timeUnavailable
+      if (NS_SUCCEEDED(rv)) // clear the time if server is available
+      {
+        m_prefs->ClearUserPref(unavailablePref.get());
+      }
+      // NS_ERROR_NOT_AVAILABLE signifies a server that could not be
+      // instantiated, presumably because of an invalid type.
+      else if (rv == NS_ERROR_NOT_AVAILABLE)
+      {
+        m_prefs->GetIntPref(unavailablePref.get(), &timeUnavailable);
+        if (!timeUnavailable)
+        { // we need to set it, this must be the first time unavailable
+          PRUint32 nowSeconds;
+          PRTime2Seconds(PR_Now(), &nowSeconds);
+          m_prefs->SetIntPref(unavailablePref.get(), nowSeconds);
+          deleteAccount = false;
+        }
+      }
+    }
+
+    if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0)
+    { // Our server is still unavailable. Have we timed out yet?
+      PRUint32 nowSeconds;
+      PRTime2Seconds(PR_Now(), &nowSeconds);
+      if ((PRInt32)nowSeconds < timeUnavailable + secondsToLeave)
+        deleteAccount = false;
+    }
+
+    if (deleteAccount)
     {
       dupAccounts.AppendObject(account);
       m_accounts->RemoveElement(account);
     }
   }
 
   uint32_t numAccounts;
   m_accounts->Count(&numAccounts);
@@ -1588,19 +1693,28 @@ nsMsgAccountManager::UnloadAccounts()
   mFolderFlagAtom = nullptr;
 
   m_defaultAccount=nullptr;
   m_incomingServers.Enumerate(hashUnloadServer, this);
 
   m_accounts->Clear();          // will release all elements
   m_identities.Clear();
   m_incomingServers.Clear();
-  m_accountsLoaded = false;
   mAccountKeyList.Truncate();
   SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, EmptyCString());
+
+  if (m_accountsLoaded)
+  {
+    nsCOMPtr<nsIMsgMailSession> mailSession =
+      do_GetService(NS_MSGMAILSESSION_CONTRACTID);
+    if (mailSession)
+      mailSession->RemoveFolderListener(this);
+    m_accountsLoaded = false;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMsgAccountManager::ShutdownServers()
 {
   m_incomingServers.Enumerate(hashShutdown, nullptr);
   return NS_OK;
--- a/mailnews/base/src/nsMsgAccountManager.h
+++ b/mailnews/base/src/nsMsgAccountManager.h
@@ -209,16 +209,19 @@ private:
 
   nsresult RemoveVFListenerForVF(nsIMsgFolder *virtualFolder,
                                  nsIMsgFolder *folder);
 
   static void getUniqueAccountKey(nsISupportsArray *accounts,
                                   nsCString& aResult);
 
 
+  // Scan the preferences to find a unique server key
+  void GetUniqueServerKey(nsACString& aResult);
+
   nsresult RemoveFolderFromSmartFolder(nsIMsgFolder *aFolder,
                                        uint32_t flagsChanged);
 
   nsresult SetSendLaterUriPref(nsIMsgIncomingServer *server);
 
   nsCOMPtr<nsIPrefBranch> m_prefs;
 
   //
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_accountMgrCustomTypes.js
@@ -0,0 +1,84 @@
+/* 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/. */
+
+/**
+ * This tests that accounts with invalid types, such as could be created
+ *  from an extension, do not disappear immediately when the extension
+ *  is unloaded.
+ *
+ * Adapted from test_AccountMgr.js by Kent James <kent@caspia.com>
+ */
+
+function run_test()
+{
+  Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+  Services.prefs.setCharPref("mail.account.account1.server", "server1");
+  Services.prefs.setCharPref("mail.account.account2.identities", "id2");
+  Services.prefs.setCharPref("mail.account.account2.server", "server2");
+  Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+  Services.prefs.setCharPref("mail.server.server1.type", "none");
+  Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+  Services.prefs.setCharPref("mail.server.server1.directory-rel",
+                             "[ProfD]Mail/Local Folders");
+
+  // Here we are simulating a server and account that is added by an
+  //  extension, but that extension is currently unloaded. The extension
+  //  added "secondsToLeaveUnavailable" (though a typical value would be
+  //  one month, not 2 seconds!) to tell the core code to leave this alone
+  //  for awhile if the extension is unloaded.
+  Services.prefs.setCharPref("mail.server.server2.hostname", "pop3.host.org");
+  Services.prefs.setCharPref("mail.server.server2.type", "invalid");
+  Services.prefs.setIntPref("mail.server.server2.secondsToLeaveUnavailable", 2);
+
+  Services.prefs.setCharPref("mail.accountmanager.accounts",
+                             "account1,account2");
+  Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1");
+
+  // This will force the load of the accounts setup above.
+  // We don't see the invalid account
+  do_check_eq(MailServices.accounts.accounts.Count(), 1);
+
+  // but it is really there
+  do_check_eq(Services.prefs.getCharPref("mail.accountmanager.accounts"),
+              "account1,account2");
+
+  // add a new account (so that we can check if this clobbers the existing
+  //  inactive account or its server)
+  let newAccount = MailServices.accounts.createAccount();
+  let newIdentity = MailServices.accounts.createIdentity();
+  newAccount.addIdentity(newIdentity);
+  newAccount.defaultIdentity = newIdentity;
+  newAccount.incomingServer =
+    MailServices.accounts.createIncomingServer("somename", "somehost.example.com", "pop3");
+
+  // no collisions with the inactive account
+  do_check_neq(newIdentity.key, "id2");
+  do_check_neq(newAccount.incomingServer.key, "server2");
+  do_check_neq(newAccount.key, "account2");
+  do_check_eq(MailServices.accounts.accounts.Count(), 2);
+
+  MailServices.accounts.UnloadAccounts();
+
+  // set the unavailable account to a valid type, and watch it appear.
+  Services.prefs.setCharPref("mail.server.server2.type", "pop3");
+  do_check_eq(MailServices.accounts.accounts.Count(), 3);
+
+  // make it bad again, and reload it to restart the timeout before delete
+  MailServices.accounts.UnloadAccounts();
+  Services.prefs.setCharPref("mail.server.server2.type", "invalid");
+  do_check_eq(MailServices.accounts.accounts.Count(), 2);
+  MailServices.accounts.UnloadAccounts();
+
+  // now let the bad type timeout, and watch it magically disappear!
+  do_test_pending();
+  do_timeout(3000, function () {
+    do_check_eq(MailServices.accounts.accounts.Count(), 2);
+
+    // it is now gone
+    do_check_eq(Services.prefs.getCharPref("mail.accountmanager.accounts"),
+                "account1," + newAccount.key);
+
+    do_test_finished();
+  });
+}
--- a/mailnews/base/test/unit/xpcshell.ini
+++ b/mailnews/base/test/unit/xpcshell.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 head = head_mailbase.js
 tail = tail_base.js
 
 [test_accountMgr.js]
+[test_accountMgrCustomTypes.js]
 [test_accountMigration.js]
 [test_acctRepair.js]
 [test_autoconfigFetchDisk.js]
 [test_autoconfigUtils.js]
 [test_autoconfigXML.js]
 [test_bccInDatabase.js]
 [test_bug366491.js]
 [test_bug404489.js]