addcards idl draft
authorGeoff Lankow <geoff@darktrojan.net>
Mon, 18 Nov 2019 20:59:19 +1300
changeset 82238 7f57656523576f5463489331460ed0af586315ff
parent 82237 825c14a55e122cec28b5e1f7d3ce83007d89f32c
push id9937
push usergeoff@darktrojan.net
push dateWed, 04 Dec 2019 09:12:17 +0000
treeherdertry-comm-central@7f5765652357 [default view] [failures only]
addcards idl
mail/base/modules/MailMigrator.jsm
mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
mailnews/addrbook/public/nsIAbDirectory.idl
mailnews/addrbook/src/nsAbDirProperty.cpp
mailnews/import/becky/src/nsBeckyAddressBooks.cpp
mailnews/import/becky/src/nsBeckyAddressBooks.h
mailnews/import/outlook/src/nsOutlookImport.cpp
mailnews/import/outlook/src/nsOutlookMail.cpp
mailnews/import/outlook/src/nsOutlookMail.h
mailnews/import/public/nsIImportAddressBooks.idl
mailnews/import/src/nsImportAddressBooks.cpp
mailnews/import/test/unit/resources/import_helper.js
mailnews/import/text/src/nsTextAddress.cpp
mailnews/import/text/src/nsTextAddress.h
mailnews/import/text/src/nsTextImport.cpp
mailnews/import/vcard/src/nsVCardAddress.cpp
mailnews/import/vcard/src/nsVCardAddress.h
mailnews/import/vcard/src/nsVCardImport.cpp
try_task_config.json
--- a/mail/base/modules/MailMigrator.jsm
+++ b/mail/base/modules/MailMigrator.jsm
@@ -699,57 +699,69 @@ var MailMigrator = {
 
       console.log(`Creating new ${fileName}.sqlite`);
       let newBook = new AddrBookDirectory();
       newBook.init(`jsaddrbook://${fileName}.sqlite`);
 
       let database = Cc[
         "@mozilla.org/addressbook/carddatabase;1"
       ].createInstance(Ci.nsIAddrDatabase);
-      database.dbPath = oldFile;
-      database.openMDB(oldFile, false);
-
-      let directory = Cc[
-        "@mozilla.org/addressbook/directory;1?type=moz-abmdbdirectory"
-      ].createInstance(Ci.nsIAbMDBDirectory);
+      try {
+        database.dbPath = oldFile;
+        database.openMDB(oldFile, false);
 
-      let cardMap = new Map();
-      for (let card of database.enumerateCards(directory)) {
-        if (!card.isMailList) {
-          cardMap.set(card.localId, card);
-        }
-      }
-      if (cardMap.size > 0) {
-        await newBook._bulkAddCards(cardMap.values());
+        let directory = Cc[
+          "@mozilla.org/addressbook/directory;1?type=moz-abmdbdirectory"
+        ].createInstance(Ci.nsIAbMDBDirectory);
 
+        let cardMap = new Map();
         for (let card of database.enumerateCards(directory)) {
-          if (card.isMailList) {
-            let mailList = Cc[
-              "@mozilla.org/addressbook/directoryproperty;1"
-            ].createInstance(Ci.nsIAbDirectory);
-            mailList.isMailList = true;
-            mailList.dirName = card.displayName;
-            mailList.listNickName = card.getProperty("NickName", "");
-            mailList.description = card.getProperty("Notes", "");
-            mailList = newBook.addMailList(mailList);
+          if (!card.isMailList) {
+            cardMap.set(card.localId, card);
+          }
+        }
+        if (cardMap.size > 0) {
+          await new Promise((resolve, reject) => {
+            newBook.addCards(cardMap.values(), {
+              onOperationComplete(status) {
+                if (status === Cr.NS_OK) {
+                  resolve();
+                } else {
+                  reject(status);
+                }
+              }
+            });
+          });
 
-            directory.dbRowID = card.localId;
-            for (let listCard of database.enumerateListAddresses(directory)) {
-              listCard.QueryInterface(Ci.nsIAbCard);
-              if (cardMap.has(listCard.localId)) {
-                mailList.addCard(cardMap.get(listCard.localId));
+          for (let card of database.enumerateCards(directory)) {
+            if (card.isMailList) {
+              let mailList = Cc[
+                "@mozilla.org/addressbook/directoryproperty;1"
+              ].createInstance(Ci.nsIAbDirectory);
+              mailList.isMailList = true;
+              mailList.dirName = card.displayName;
+              mailList.listNickName = card.getProperty("NickName", "");
+              mailList.description = card.getProperty("Notes", "");
+              mailList = newBook.addMailList(mailList);
+
+              directory.dbRowID = card.localId;
+              for (let listCard of database.enumerateListAddresses(directory)) {
+                listCard.QueryInterface(Ci.nsIAbCard);
+                if (cardMap.has(listCard.localId)) {
+                  mailList.addCard(cardMap.get(listCard.localId));
+                }
               }
             }
           }
         }
+      } finally {
+        database.closeMDB(false);
+        database.forceClosed();
       }
 
-      database.closeMDB(false);
-      database.forceClosed();
-
       let backupFile = profileDir.clone();
       backupFile.append(`${fileName}.mab.bak`);
       backupFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
       console.log(`Renaming ${fileName}.mab to ${backupFile.leafName}`);
       oldFile.renameTo(profileDir, backupFile.leafName);
     }
 
     let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
--- a/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
@@ -391,94 +391,16 @@ AddrBookDirectoryInner.prototype = {
     this._lists.set(list._uid, {
       uid: list._uid,
       localId: list._localId,
       name: list._name,
       nickName: list._nickName,
       description: list._description,
     });
   },
-  async _bulkAddCards(cards) {
-    let cardStatement = this._dbConnection.createStatement(
-      "INSERT INTO cards (uid, localId) VALUES (:uid, :localId)"
-    );
-    let propertiesStatement = this._dbConnection.createStatement(
-      "INSERT INTO properties VALUES (:card, :name, :value)"
-    );
-    let cardArray = cardStatement.newBindingParamsArray();
-    let propertiesArray = propertiesStatement.newBindingParamsArray();
-    for (let card of cards) {
-      let uid = card.UID || newUID();
-      let localId = this._getNextCardId();
-      let cardParams = cardArray.newBindingParams();
-      cardParams.bindByName("uid", uid);
-      cardParams.bindByName("localId", localId);
-      cardArray.addParams(cardParams);
-
-      let cachedCard;
-      if (this._inner.hasOwnProperty("_cards")) {
-        cachedCard = {
-          uid,
-          localId,
-          properties: new Map(),
-        };
-        this._inner._cards.set(uid, cachedCard);
-      }
-
-      for (let { name, value } of fixIterator(
-        card.properties,
-        Ci.nsIProperty
-      )) {
-        if (
-          [
-            "DbRowID",
-            "LowercasePrimaryEmail",
-            "LowercaseSecondEmail",
-            "RecordKey",
-            "UID",
-          ].includes(name)
-        ) {
-          continue;
-        }
-        let propertiesParams = propertiesArray.newBindingParams();
-        propertiesParams.bindByName("card", uid);
-        propertiesParams.bindByName("name", name);
-        propertiesParams.bindByName("value", value);
-        propertiesArray.addParams(propertiesParams);
-
-        if (cachedCard) {
-          cachedCard.properties.set(name, value);
-        }
-      }
-    }
-    cardStatement.bindParameters(cardArray);
-    await new Promise(resolve => {
-      cardStatement.executeAsync({
-        handleError(error) {
-          Cu.reportError(error);
-        },
-        handleCompletion() {
-          resolve();
-        },
-      });
-    });
-    cardStatement.finalize();
-    propertiesStatement.bindParameters(propertiesArray);
-    await new Promise(resolve => {
-      propertiesStatement.executeAsync({
-        handleError(error) {
-          Cu.reportError(error);
-        },
-        handleCompletion() {
-          resolve();
-        },
-      });
-    });
-    propertiesStatement.finalize();
-  },
 
   /* nsIAbCollection */
 
   get readOnly() {
     return false;
   },
   get isRemote() {
     return false;
@@ -758,16 +680,109 @@ AddrBookDirectoryInner.prototype = {
         return true;
       }
     }
     return false;
   },
   addCard(card) {
     return this.dropCard(card, false);
   },
+  async addCards(cards, listener) {
+    try {
+      let cardStatement = this._dbConnection.createStatement(
+        "INSERT INTO cards (uid, localId) VALUES (:uid, :localId)"
+      );
+      let propertiesStatement = this._dbConnection.createStatement(
+        "INSERT INTO properties VALUES (:card, :name, :value)"
+      );
+      let cardArray = cardStatement.newBindingParamsArray();
+      let propertiesArray = propertiesStatement.newBindingParamsArray();
+      for (let card of cards) {
+        let uid = card.UID || newUID();
+        let localId = this._getNextCardId();
+        let cardParams = cardArray.newBindingParams();
+        cardParams.bindByName("uid", uid);
+        cardParams.bindByName("localId", localId);
+        cardArray.addParams(cardParams);
+
+        let cachedCard;
+        if (this._inner.hasOwnProperty("_cards")) {
+          cachedCard = {
+            uid,
+            localId,
+            properties: new Map(),
+          };
+          this._inner._cards.set(uid, cachedCard);
+        }
+
+        for (let { name, value } of fixIterator(
+          card.properties,
+          Ci.nsIProperty
+        )) {
+          if (
+            [
+              "DbRowID",
+              "LowercasePrimaryEmail",
+              "LowercaseSecondEmail",
+              "RecordKey",
+              "UID",
+            ].includes(name)
+          ) {
+            continue;
+          }
+          let propertiesParams = propertiesArray.newBindingParams();
+          propertiesParams.bindByName("card", uid);
+          propertiesParams.bindByName("name", name);
+          propertiesParams.bindByName("value", value);
+          propertiesArray.addParams(propertiesParams);
+
+          if (cachedCard) {
+            cachedCard.properties.set(name, value);
+          }
+        }
+      }
+      if (cardArray.length > 0) {
+        cardStatement.bindParameters(cardArray);
+        await new Promise(resolve => {
+          cardStatement.executeAsync({
+            handleError(error) {
+              Cu.reportError(error);
+            },
+            handleCompletion() {
+              resolve();
+            },
+          });
+        });
+        cardStatement.finalize();
+      }
+      if (propertiesArray.length > 0) {
+        propertiesStatement.bindParameters(propertiesArray);
+        await new Promise(resolve => {
+          propertiesStatement.executeAsync({
+            handleError(error) {
+              Cu.reportError(error);
+            },
+            handleCompletion() {
+              resolve();
+            },
+          });
+        });
+        propertiesStatement.finalize();
+      }
+
+      if (listener) {
+        listener.onOperationComplete(Cr.NS_OK);
+      }
+    } catch (ex) {
+      Cu.reportError(ex);
+      if (listener) {
+        listener.onOperationComplete(ex.result || Cr.NS_ERROR_FAILURE);
+      }
+    }
+  },
   modifyCard(card) {
     let oldProperties = this._loadCardProperties(card.UID);
     let newProperties = new Map();
     for (let { name, value } of fixIterator(card.properties, Ci.nsIProperty)) {
       newProperties.set(name, value);
     }
     this._saveCardProperties(card);
     for (let [name, oldValue] of oldProperties.entries()) {
--- a/mailnews/addrbook/public/nsIAbDirectory.idl
+++ b/mailnews/addrbook/public/nsIAbDirectory.idl
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIAbCollection.idl"
 #include "nsIAbCard.idl"
 
 interface nsISimpleEnumerator;
 interface nsIArray;
 interface nsIMutableArray;
+interface nsIAbOperationListener;
 
 /* moz-abdirectory:// is the URI to access nsAbBSDirectory,
  * which is the root directory for all types of address books
  * this is used to get all address book directories. */
 
 %{C++
 #define kAllDirectoryRoot          "moz-abdirectory://"
 
@@ -141,16 +142,23 @@ interface nsIAbDirectory : nsIAbCollecti
    * can add an nsIAbLDAPCard to an nsIAbMDBDirectory.
    *
    * @return "Real" card (eg nsIAbLDAPCard) that can be used for some
    *         extra functions.
    */
   nsIAbCard addCard(in nsIAbCard card);
 
   /**
+   * Adds an array of cards to the database.
+   *
+   * @param  aCards  The cards to add to the database.
+   */
+  void addCards(in Array<nsIAbCard> cards, [optional] in nsIAbOperationListener listener);
+
+  /**
    * Modifies a card in the database to match that supplied.
    */
   void modifyCard(in nsIAbCard modifiedCard);
 
   /**
    * Deletes the array of cards from the database.
    *
    * @param  aCards  The cards to delete from the database.
@@ -300,8 +308,16 @@ interface nsIAbDirectory : nsIAbCollecti
   //@{
   void setIntValue(in string aName, in long aValue);
   void setBoolValue(in string aName, in boolean aValue);
   void setStringValue(in string aName, in ACString aValue);
   void setLocalizedStringValue(in string aName, in AUTF8String aValue);
   //@}
 
 };
+
+[scriptable, uuid(e77bb0a3-bf4b-43d8-b443-21516791a4c6)]
+interface nsIAbOperationListener : nsISupports {
+  /**
+   * Called when the operation stops.
+   */
+  void onOperationComplete(in nsresult status);
+};
--- a/mailnews/addrbook/src/nsAbDirProperty.cpp
+++ b/mailnews/addrbook/src/nsAbDirProperty.cpp
@@ -372,16 +372,21 @@ NS_IMETHODIMP nsAbDirProperty::EditMailL
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsAbDirProperty::AddCard(nsIAbCard *childCard,
                                        nsIAbCard **addedCard) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+NS_IMETHODIMP nsAbDirProperty::AddCards(const nsTArray<RefPtr<nsIAbCard>> &cards,
+                                        nsIAbOperationListener *listener) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 NS_IMETHODIMP nsAbDirProperty::ModifyCard(nsIAbCard *aModifiedCard) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsAbDirProperty::DeleteCards(nsIArray *cards) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
--- a/mailnews/import/becky/src/nsBeckyAddressBooks.cpp
+++ b/mailnews/import/becky/src/nsBeckyAddressBooks.cpp
@@ -15,17 +15,18 @@
 #include "nsIImportABDescriptor.h"
 #include "nsMsgUtils.h"
 #include "nsVCardAddress.h"
 
 #include "nsBeckyAddressBooks.h"
 #include "nsBeckyStringBundle.h"
 #include "nsBeckyUtils.h"
 
-NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks)
+NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks,
+                  nsIAbOperationListener)
 
 nsresult nsBeckyAddressBooks::Create(nsIImportAddressBooks **aImport) {
   NS_ENSURE_ARG_POINTER(aImport);
   NS_ADDREF(*aImport = new nsBeckyAddressBooks());
   return NS_OK;
 }
 
 nsBeckyAddressBooks::nsBeckyAddressBooks() : mReadBytes(0) {}
@@ -257,16 +258,18 @@ nsBeckyAddressBooks::ImportAddressBook(
     char16_t **aErrorLog, char16_t **aSuccessLog, bool *aFatalError) {
   NS_ENSURE_ARG_POINTER(aSource);
   NS_ENSURE_ARG_POINTER(aDestination);
   NS_ENSURE_ARG_POINTER(aErrorLog);
   NS_ENSURE_ARG_POINTER(aSuccessLog);
   NS_ENSURE_ARG_POINTER(aFatalError);
 
   mReadBytes = 0;
+  mComplete = false;
+  mFatalError = aFatalError;
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = aSource->GetAbFile(getter_AddRefs(file));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIDirectoryEnumerator> entries;
   rv = file->GetDirectoryEntries(getter_AddRefs(entries));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -280,38 +283,51 @@ nsBeckyAddressBooks::ImportAddressBook(
 
     if (!IsAddressBookFile(file)) continue;
 
     bool aborted = false;
     nsAutoString name;
     aSource->GetPreferredName(name);
     nsVCardAddress vcard;
     rv = vcard.ImportAddresses(&aborted, name.get(), file, aDestination, error,
-                               &mReadBytes);
+                               &mReadBytes, this);
     if (NS_FAILED(rv)) {
       break;
     }
   }
 
   if (!error.IsEmpty())
     *aErrorLog = ToNewUnicode(error);
-  else
-    *aSuccessLog =
-        nsBeckyStringBundle::GetStringByName("BeckyImportAddressSuccess");
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsBeckyAddressBooks::GetImportProgress(uint32_t *_retval) {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = mReadBytes;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsBeckyAddressBooks::GetImportIsComplete(bool *_retval) {
+  NS_ENSURE_ARG_POINTER(_retval);
+  *_retval = mComplete;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsBeckyAddressBooks::SetSampleLocation(nsIFile *aLocation) { return NS_OK; }
 
 NS_IMETHODIMP
 nsBeckyAddressBooks::GetSampleData(int32_t aRecordNumber, bool *aRecordExists,
                                    char16_t **_retval) {
   return NS_ERROR_FAILURE;
 }
+
+NS_IMETHODIMP nsBeckyAddressBooks::OnOperationComplete(nsresult status) {
+  if (NS_SUCCEEDED(status)) {
+    mComplete = true;
+  } else {
+    *mFatalError = true;
+  }
+  return NS_OK;
+}
--- a/mailnews/import/becky/src/nsBeckyAddressBooks.h
+++ b/mailnews/import/becky/src/nsBeckyAddressBooks.h
@@ -1,32 +1,37 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef nsBeckyAddressBooks_h___
 #define nsBeckyAddressBooks_h___
 
+#include "nsIAbDirectory.h"
 #include "nsIImportAddressBooks.h"
 #include "nsIFile.h"
 #include "nsIMutableArray.h"
 
-class nsBeckyAddressBooks final : public nsIImportAddressBooks {
+class nsBeckyAddressBooks final : public nsIImportAddressBooks,
+                                  nsIAbOperationListener {
  public:
   nsBeckyAddressBooks();
   static nsresult Create(nsIImportAddressBooks **aImport);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIIMPORTADDRESSBOOKS
+  NS_DECL_NSIABOPERATIONLISTENER
 
  private:
   virtual ~nsBeckyAddressBooks();
 
   uint32_t mReadBytes;
+  bool mComplete;
+  bool *mFatalError;
 
   nsresult CollectAddressBooks(nsIFile *aTarget, nsIMutableArray *aCollected);
   nsresult FindAddressBookDirectory(nsIFile **aAddressBookDirectory);
   nsresult AppendAddressBookDescriptor(nsIFile *aEntry,
                                        nsIMutableArray *aCollected);
   uint32_t CountAddressBookSize(nsIFile *aDirectory);
   bool HasAddressBookFile(nsIFile *aDirectory);
   bool IsAddressBookFile(nsIFile *aFile);
--- a/mailnews/import/outlook/src/nsOutlookImport.cpp
+++ b/mailnews/import/outlook/src/nsOutlookImport.cpp
@@ -68,72 +68,36 @@ class ImportOutlookMailImpl : public nsI
                       char16_t **pSuccess);
 
  private:
   virtual ~ImportOutlookMailImpl();
   nsOutlookMail m_mail;
   uint32_t m_bytesDone;
 };
 
-class ImportOutlookAddressImpl : public nsIImportAddressBooks {
+class ImportOutlookAddressImpl : public nsIImportAddressBooks,
+                                 nsIAbOperationListener {
  public:
   ImportOutlookAddressImpl();
 
   static nsresult Create(nsIImportAddressBooks **aImport);
 
-  // nsISupports interface
   NS_DECL_THREADSAFE_ISUPPORTS
-
-  // nsIImportAddressBooks interface
-
-  NS_IMETHOD GetSupportsMultiple(bool *_retval) {
-    *_retval = true;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval);
-
-  NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) {
-    *_retval = false;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found,
-                                bool *userVerify) {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval);
-
-  NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
-                               nsIAbDirectory *destination,
-                               nsIImportFieldMap *fieldMap,
-                               nsISupports *aSupportService,
-                               char16_t **errorLog, char16_t **successLog,
-                               bool *fatalError);
-
-  NS_IMETHOD GetImportProgress(uint32_t *_retval);
-
-  NS_IMETHOD GetSampleData(int32_t index, bool *pFound, char16_t **pStr) {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_IMETHOD SetSampleLocation(nsIFile *) { return NS_OK; }
+  NS_DECL_NSIIMPORTADDRESSBOOKS
+  NS_DECL_NSIABOPERATIONLISTENER
 
  private:
   virtual ~ImportOutlookAddressImpl();
   void ReportSuccess(nsString &name, nsString *pStream);
 
  private:
   uint32_t m_msgCount;
   uint32_t m_msgTotal;
+  bool m_complete;
+  bool *m_fatalError;
   nsOutlookMail m_address;
 };
 ////////////////////////////////////////////////////////////////////////
 
 ////////////////////////////////////////////////////////////////////////
 
 nsOutlookImport::nsOutlookImport() {
   IMPORT_LOG0("nsOutlookImport Module Created\n");
@@ -422,43 +386,67 @@ ImportOutlookAddressImpl::ImportOutlookA
   m_msgCount = 0;
   m_msgTotal = 0;
 }
 
 ImportOutlookAddressImpl::~ImportOutlookAddressImpl() {}
 
 NS_IMPL_ISUPPORTS(ImportOutlookAddressImpl, nsIImportAddressBooks)
 
+NS_IMETHODIMP ImportOutlookAddressImpl::GetSupportsMultiple(bool *_retval) {
+  *_retval = true;
+  return NS_OK;
+}
+
 NS_IMETHODIMP ImportOutlookAddressImpl::GetAutoFind(char16_t **description,
                                                     bool *_retval) {
   NS_ASSERTION(description != nullptr, "null ptr");
   NS_ASSERTION(_retval != nullptr, "null ptr");
   if (!description || !_retval) return NS_ERROR_NULL_POINTER;
 
   *_retval = true;
   nsString str;
   nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRNAME, str);
   *description = ToNewUnicode(str);
   return NS_OK;
 }
 
+NS_IMETHODIMP ImportOutlookAddressImpl::GetNeedsFieldMap(nsIFile *location,
+                                                         bool *_retval) {
+  *_retval = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetDefaultLocation(nsIFile **location,
+                                                           bool *found,
+                                                           bool *userVerify) {
+  return NS_ERROR_FAILURE;
+}
+
 NS_IMETHODIMP ImportOutlookAddressImpl::FindAddressBooks(nsIFile *location,
                                                          nsIArray **_retval) {
   NS_ASSERTION(_retval != nullptr, "null ptr");
   if (!_retval) return NS_ERROR_NULL_POINTER;
 
   return m_address.GetAddressBooks(_retval);
 }
 
+NS_IMETHODIMP ImportOutlookAddressImpl::InitFieldMap(
+    nsIImportFieldMap *fieldMap) {
+  return NS_ERROR_FAILURE;
+}
+
 NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook(
     nsIImportABDescriptor *source, nsIAbDirectory *destination,
     nsIImportFieldMap *fieldMap, nsISupports *aSupportService,
     char16_t **pErrorLog, char16_t **pSuccessLog, bool *fatalError) {
   m_msgCount = 0;
   m_msgTotal = 0;
+  m_complete = false;
+  m_fatalError = fatalError;
   NS_ASSERTION(source != nullptr, "null ptr");
   NS_ASSERTION(destination != nullptr, "null ptr");
   NS_ASSERTION(fatalError != nullptr, "null ptr");
 
   nsString success;
   nsString error;
   if (!source || !destination || !fatalError) {
     IMPORT_LOG0("*** Bad param passed to outlook address import\n");
@@ -476,22 +464,21 @@ NS_IMETHODIMP ImportOutlookAddressImpl::
     ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE,
                                        name, &error);
     ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv = NS_OK;
   rv = m_address.ImportAddresses(&m_msgCount, &m_msgTotal, name.get(), id,
-                                 destination, error);
-  if (NS_SUCCEEDED(rv) && error.IsEmpty())
-    ReportSuccess(name, &success);
-  else
+                                 destination, error, this);
+  if (NS_FAILED(rv) || !error.IsEmpty()) {
     ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_CONVERTERROR, name,
                                        &error);
+  }
 
   ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
   IMPORT_LOG0("*** Returning from outlook address import\n");
   return NS_OK;
 }
 
 NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t *_retval) {
   NS_ASSERTION(_retval != nullptr, "null ptr");
@@ -506,20 +493,45 @@ NS_IMETHODIMP ImportOutlookAddressImpl::
 
   if (result > 100) result = 100;
 
   *_retval = result;
 
   return NS_OK;
 }
 
+NS_IMETHODIMP ImportOutlookAddressImpl::GetImportIsComplete(bool *_retval) {
+  NS_ENSURE_ARG_POINTER(_retval);
+  *_retval = m_complete;
+  return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::SetSampleLocation(nsIFile *) {
+  return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetSampleData(int32_t index,
+                                                      bool *pFound,
+                                                      char16_t **pStr) {
+  return NS_ERROR_FAILURE;
+}
+
 void ImportOutlookAddressImpl::ReportSuccess(nsString &name,
                                              nsString *pStream) {
   if (!pStream) return;
   // load the success string
   char16_t *pFmt =
       nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_SUCCESS);
   nsString pText;
   nsTextFormatter::ssprintf(pText, pFmt, name.get());
   pStream->Append(pText);
   nsOutlookStringBundle::FreeString(pFmt);
   ImportOutlookMailImpl::AddLinebreak(pStream);
 }
+
+NS_IMETHODIMP ImportOutlookAddressImpl::OnOperationComplete(nsresult status) {
+  if (NS_SUCCEEDED(status)) {
+    m_complete = true;
+  } else {
+    *m_fatalError = true;
+  }
+  return NS_OK;
+}
--- a/mailnews/import/outlook/src/nsOutlookMail.cpp
+++ b/mailnews/import/outlook/src/nsOutlookMail.cpp
@@ -491,17 +491,18 @@ BOOL nsOutlookMail::WriteData(nsIOutputS
   uint32_t written;
   nsresult rv = pDest->Write(pData, len, &written);
   return NS_SUCCEEDED(rv) && written == len;
 }
 
 nsresult nsOutlookMail::ImportAddresses(uint32_t *pCount, uint32_t *pTotal,
                                         const char16_t *pName, uint32_t id,
                                         nsIAbDirectory *pDirectory,
-                                        nsString &errors) {
+                                        nsString &errors,
+                                        nsIAbOperationListener *listener) {
   if (id >= (uint32_t)(m_addressList.GetSize())) {
     IMPORT_LOG0("*** Bad address identifier, unable to import\n");
     return NS_ERROR_FAILURE;
   }
 
   uint32_t dummyCount = 0;
   if (pCount)
     *pCount = 0;
@@ -550,16 +551,17 @@ nsresult nsOutlookMail::ImportAddresses(
   ULONG cbEid;
   LPENTRYID lpEid;
   ULONG oType;
   LPMESSAGE lpMsg;
   nsCString type;
   LPSPropValue pVal;
   nsString subject;
 
+  nsTArray<RefPtr<nsIAbCard>> cards;
   while (!done) {
     (*pCount)++;
 
     if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) {
       IMPORT_LOG1("*** Error iterating address book: %S\n", pName);
       return NS_ERROR_FAILURE;
     }
 
@@ -578,37 +580,37 @@ nsresult nsOutlookMail::ImportAddresses(
         type.Truncate();
         m_mapi.GetStringFromProp(pVal, type);
         if (type.EqualsLiteral("IPM.Contact")) {
           // This is a contact, add it to the address book!
           subject.Truncate();
           pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
           if (pVal) m_mapi.GetStringFromProp(pVal, subject);
 
-          nsCOMPtr<nsIAbCard> newCard =
+          RefPtr<nsIAbCard> newCard =
               do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
           if (newCard) {
             if (BuildCard(subject.get(), pDirectory, newCard, lpMsg,
                           pFieldMap)) {
-              nsIAbCard *outCard;
-              pDirectory->AddCard(newCard, &outCard);
+              cards.AppendElement(newCard);
             }
           }
         } else if (type.EqualsLiteral("IPM.DistList")) {
           // This is a list/group, add it to the address book!
           subject.Truncate();
           pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
           if (pVal) m_mapi.GetStringFromProp(pVal, subject);
           CreateList(subject, pDirectory, lpMsg, pFieldMap);
         }
       }
 
       lpMsg->Release();
     }
   }
+  pDirectory->AddCards(cards, listener);
 
   return rv;
 }
 nsresult nsOutlookMail::CreateList(const nsString &pName,
                                    nsIAbDirectory *pDirectory,
                                    LPMAPIPROP pUserList,
                                    nsIImportFieldMap *pFieldMap) {
   // If no name provided then we're done.
--- a/mailnews/import/outlook/src/nsOutlookMail.h
+++ b/mailnews/import/outlook/src/nsOutlookMail.h
@@ -24,17 +24,18 @@ class nsOutlookMail {
 
   nsresult GetMailFolders(nsIArray **pArray);
   nsresult GetAddressBooks(nsIArray **pArray);
   nsresult ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, int32_t index,
                          const char16_t *pName, nsIMsgFolder *pDest,
                          int32_t *pMsgCount);
   nsresult ImportAddresses(uint32_t *pCount, uint32_t *pTotal,
                            const char16_t *pName, uint32_t id,
-                           nsIAbDirectory *pDirectory, nsString &errors);
+                           nsIAbDirectory *pDirectory, nsString &errors,
+                           nsIAbOperationListener *listener);
   void OpenMessageStore(CMapiFolder *pNextFolder);
   static BOOL WriteData(nsIOutputStream *pDest, const char *pData, int32_t len);
 
  private:
   bool IsAddressBookNameUnique(nsString &name, nsString &list);
   void MakeAddressBookNameUnique(nsString &name, nsString &list);
   void SanitizeValue(nsString &val);
   void SplitString(nsString &val1, nsString &val2);
--- a/mailnews/import/public/nsIImportAddressBooks.idl
+++ b/mailnews/import/public/nsIImportAddressBooks.idl
@@ -118,16 +118,18 @@ interface nsIImportAddressBooks : nsISup
   /*
     Return the amount of the address book that has been imported so far.  This number
     is used to present progress information and must never be larger than the
     size specified in nsIImportABDescriptor.GetSize();  May be called from
     a different thread than ImportAddressBook()
   */
   unsigned long GetImportProgress();
 
+  boolean GetImportIsComplete();
+
   /*
     Set the location for reading sample data, this should be the same
     as what is passed later to FindAddressBooks
   */
   void SetSampleLocation(in nsIFile location);
 
   /*
    * Return a string of sample data for a record, each field
--- a/mailnews/import/src/nsImportAddressBooks.cpp
+++ b/mailnews/import/src/nsImportAddressBooks.cpp
@@ -449,21 +449,30 @@ NS_IMETHODIMP nsImportGenericAddressBook
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(int32_t *_retval) {
   // This returns the progress from the the currently
   // running import mail or import address book thread.
-  NS_ASSERTION(_retval != nullptr, "null ptr");
+  NS_ENSURE_ARG_POINTER(_retval);
   if (!_retval) return NS_ERROR_NULL_POINTER;
 
-  if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
-    *_retval = 100;
+  if (m_pInterface) {
+    bool complete = false;
+    m_pInterface->GetImportIsComplete(&complete);
+    if (complete) {
+      *_retval = 100;
+      return NS_OK;
+    }
+  }
+
+  *_retval = 0;
+  if (!m_pThreadData) {
     return NS_OK;
   }
 
   uint32_t sz = 0;
   if (m_pThreadData->currentSize && m_pInterface) {
     if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) sz = 0;
   }
 
--- a/mailnews/import/test/unit/resources/import_helper.js
+++ b/mailnews/import/test/unit/resources/import_helper.js
@@ -100,18 +100,20 @@ GenericImportHelper.prototype = {
     if (this.mInterface.GetProgress() != 100) {
       // use the helper object to check the progress of the import after 200 ms
       gGenericImportHelper = this;
       do_timeout(200, function() {
         gGenericImportHelper.checkProgress();
       });
     } else {
       // if it is done, check the results or finish the test.
-      this.checkResults();
-      do_test_finished();
+      do_timeout(0, () => {
+        this.checkResults();
+        do_test_finished();
+      });
     }
   },
 
   /**
    * GenericImportHelper.checkResults
    * Checks the results of the import.
    * Child class should implement this method.
    */
--- a/mailnews/import/text/src/nsTextAddress.cpp
+++ b/mailnews/import/text/src/nsTextAddress.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; 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/. */
 
 #include "nsAbBaseCID.h"
 #include "nsTextAddress.h"
-#include "nsIAbDirectory.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsNetUtil.h"
 #include "nsMsgI18N.h"
 #include "nsMsgUtils.h"
 #include "nsIConverterInputStream.h"
 #include "nsIUnicharLineInputStream.h"
@@ -49,17 +48,18 @@ nsresult nsTextAddress::GetUnicharLineSt
 
   return CallQueryInterface(converterStream, aStream);
 }
 
 nsresult nsTextAddress::ImportAddresses(bool *pAbort, const char16_t *pName,
                                         nsIFile *pSrc,
                                         nsIAbDirectory *pDirectory,
                                         nsIImportFieldMap *fieldMap,
-                                        nsString &errors, uint32_t *pProgress) {
+                                        nsString &errors, uint32_t *pProgress,
+                                        nsIAbOperationListener *listener) {
   // Open the source file for reading, read each line and process it!
   m_directory = pDirectory;
   m_fieldMap = fieldMap;
 
   nsCOMPtr<nsIInputStream> inputStream;
   nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
   if (NS_FAILED(rv)) {
     IMPORT_LOG0("*** Error opening address file for reading\n");
@@ -94,37 +94,45 @@ nsresult nsTextAddress::ImportAddresses(
     IMPORT_LOG0("*** Error opening converter stream for importer\n");
     return rv;
   }
 
   bool more = true;
   nsAutoString line;
 
   // Skip the first record if the user has requested it.
-  if (skipRecord) rv = ReadRecord(lineStream, line, &more);
+  if (skipRecord) {
+    rv = ReadRecord(lineStream, line, &more);
+    bytesLeft -= line.Length() + 1;
+  }
 
+  nsTArray<RefPtr<nsIAbCard>> cards;
   while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
     // Read the line in
     rv = ReadRecord(lineStream, line, &more);
     if (NS_SUCCEEDED(rv)) {
       // Now process it to add it to the database
-      rv = ProcessLine(line, errors);
+      RefPtr<nsIAbCard> card;
+      rv = ProcessLine(line, getter_AddRefs(card), errors);
 
-      if (NS_FAILED(rv)) {
+      if (NS_SUCCEEDED(rv)) {
+        cards.AppendElement(card);
+      } else {
         IMPORT_LOG0("*** Error processing text record.\n");
       }
     }
     if (NS_SUCCEEDED(rv) && pProgress) {
       // This won't be totally accurate, but its the best we can do
       // considering that lineStream won't give us how many bytes
       // are actually left.
-      bytesLeft -= line.Length();
+      bytesLeft -= line.Length() + 1;
       *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX));
     }
   }
+  m_directory->AddCards(cards, listener);
 
   inputStream->Close();
 
   if (NS_FAILED(rv)) {
     IMPORT_LOG0(
         "*** Error reading the address book - probably incorrect ending\n");
     return NS_ERROR_FAILURE;
   }
@@ -376,17 +384,18 @@ nsresult nsTextAddress::DetermineDelim(n
 
   return rv;
 }
 
 /*
     This is where the real work happens!
     Go through the field map and set the data in a new database row
 */
-nsresult nsTextAddress::ProcessLine(const nsAString &aLine, nsString &errors) {
+nsresult nsTextAddress::ProcessLine(const nsAString &aLine, nsIAbCard **outCard,
+                                    nsString &errors) {
   if (!m_fieldMap) {
     IMPORT_LOG0("*** Error, text import needs a field map\n");
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
 
   // Wait until we get our first non-empty field, then create a new row,
@@ -415,13 +424,11 @@ nsresult nsTextAddress::ProcessLine(cons
       } else {
         break;
       }
     } else if (active) {
       IMPORT_LOG1("*** Error getting field map for index %ld\n", (long)i);
     }
   }
 
-  nsIAbCard *outCard;
-  rv = m_directory->AddCard(newCard, &outCard);
-
+  newCard.forget(outCard);
   return rv;
 }
--- a/mailnews/import/text/src/nsTextAddress.h
+++ b/mailnews/import/text/src/nsTextAddress.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef nsTextAddress_h__
 #define nsTextAddress_h__
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
+#include "nsIAbDirectory.h"
 #include "nsIImportFieldMap.h"
 #include "nsIImportService.h"
 
 class nsIAbDirectory;
 class nsIFile;
 class nsIInputStream;
 class nsIUnicharLineInputStream;
 
@@ -24,28 +25,30 @@ class nsIUnicharLineInputStream;
 class nsTextAddress {
  public:
   nsTextAddress();
   virtual ~nsTextAddress();
 
   nsresult ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc,
                            nsIAbDirectory *pDirectory,
                            nsIImportFieldMap *fieldMap, nsString &errors,
-                           uint32_t *pProgress);
+                           uint32_t *pProgress,
+                           nsIAbOperationListener *listener);
 
   nsresult DetermineDelim(nsIFile *pSrc);
   char16_t GetDelim(void) { return m_delim; }
 
   static nsresult ReadRecordNumber(nsIFile *pSrc, nsAString &aLine,
                                    int32_t rNum);
   static bool GetField(const nsAString &aLine, int32_t index, nsString &field,
                        char16_t delim);
 
  private:
-  nsresult ProcessLine(const nsAString &aLine, nsString &errors);
+  nsresult ProcessLine(const nsAString &aLine, nsIAbCard **outCard,
+                       nsString &errors);
 
   static int32_t CountFields(const nsAString &aLine, char16_t delim);
   static nsresult ReadRecord(nsIUnicharLineInputStream *pSrc, nsAString &aLine,
                              bool *aMore);
   static nsresult GetUnicharLineStreamForFile(
       nsIFile *aFile, nsIInputStream *aInputStream,
       nsIUnicharLineInputStream **aStream);
 
--- a/mailnews/import/text/src/nsTextImport.cpp
+++ b/mailnews/import/text/src/nsTextImport.cpp
@@ -33,57 +33,27 @@
 #define TEXTIMPORT_NAME 2000
 #define TEXTIMPORT_DESCRIPTION 2001
 #define TEXTIMPORT_ADDRESS_NAME 2002
 #define TEXTIMPORT_ADDRESS_SUCCESS 2003
 #define TEXTIMPORT_ADDRESS_BADPARAM 2004
 #define TEXTIMPORT_ADDRESS_BADSOURCEFILE 2005
 #define TEXTIMPORT_ADDRESS_CONVERTERROR 2006
 
-class ImportAddressImpl final : public nsIImportAddressBooks {
+class ImportAddressImpl final : public nsIImportAddressBooks,
+                                nsIAbOperationListener {
  public:
   explicit ImportAddressImpl(nsIStringBundle *aStringBundle);
 
   static nsresult Create(nsIImportAddressBooks **aImport,
                          nsIStringBundle *aStringBundle);
 
-  // nsISupports interface
   NS_DECL_THREADSAFE_ISUPPORTS
-
-  // nsIImportAddressBooks interface
-
-  NS_IMETHOD GetSupportsMultiple(bool *_retval) override {
-    *_retval = false;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override;
-
-  NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override;
-
-  NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found,
-                                bool *userVerify) override;
-
-  NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override;
-
-  NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override;
-
-  NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
-                               nsIAbDirectory *destination,
-                               nsIImportFieldMap *fieldMap,
-                               nsISupports *aSupportService,
-                               char16_t **errorLog, char16_t **successLog,
-                               bool *fatalError) override;
-
-  NS_IMETHOD GetImportProgress(uint32_t *_retval) override;
-
-  NS_IMETHOD GetSampleData(int32_t index, bool *pFound,
-                           char16_t **pStr) override;
-
-  NS_IMETHOD SetSampleLocation(nsIFile *) override;
+  NS_DECL_NSIIMPORTADDRESSBOOKS
+  NS_DECL_NSIABOPERATIONLISTENER
 
  private:
   void ClearSampleFile(void);
   void SaveFieldMap(nsIImportFieldMap *pMap);
 
   static void ReportSuccess(nsString &name, nsString *pStream,
                             nsIStringBundle *pBundle);
   static void SetLogs(nsString &success, nsString &error, char16_t **pError,
@@ -95,16 +65,18 @@ class ImportAddressImpl final : public n
  private:
   ~ImportAddressImpl() {}
   nsTextAddress m_text;
   bool m_haveDelim;
   nsCOMPtr<nsIFile> m_fileLoc;
   nsCOMPtr<nsIStringBundle> m_notProxyBundle;
   char16_t m_delim;
   uint32_t m_bytesImported;
+  bool m_complete;
+  bool *m_fatalError;
 };
 
 ////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////
 
 nsTextImport::nsTextImport() {
   IMPORT_LOG0("nsTextImport Module Created\n");
 
@@ -183,17 +155,23 @@ nsresult ImportAddressImpl::Create(nsIIm
   return NS_OK;
 }
 
 ImportAddressImpl::ImportAddressImpl(nsIStringBundle *aStringBundle)
     : m_notProxyBundle(aStringBundle) {
   m_haveDelim = false;
 }
 
-NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks)
+NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks,
+                  nsIAbOperationListener)
+
+NS_IMETHODIMP ImportAddressImpl::GetSupportsMultiple(bool *_retval) {
+  *_retval = false;
+  return NS_OK;
+}
 
 NS_IMETHODIMP ImportAddressImpl::GetAutoFind(char16_t **addrDescription,
                                              bool *_retval) {
   NS_ASSERTION(addrDescription != nullptr, "null ptr");
   NS_ASSERTION(_retval != nullptr, "null ptr");
   if (!addrDescription || !_retval) return NS_ERROR_NULL_POINTER;
 
   nsString str;
@@ -337,16 +315,18 @@ ImportAddressImpl::ImportAddressBook(nsI
                                      nsISupports *aSupportService,
                                      char16_t **pErrorLog,
                                      char16_t **pSuccessLog, bool *fatalError) {
   NS_ASSERTION(pSource != nullptr, "null ptr");
   NS_ASSERTION(pDestination != nullptr, "null ptr");
   NS_ASSERTION(fatalError != nullptr, "null ptr");
 
   m_bytesImported = 0;
+  m_complete = false;
+  m_fatalError = fatalError;
 
   nsString success, error;
   if (!pSource || !pDestination || !fatalError) {
     IMPORT_LOG0("*** Bad param passed to text address import\n");
     nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_BADPARAM,
                                         m_notProxyBundle, error);
 
     SetLogs(success, error, pErrorLog, pSuccessLog);
@@ -399,46 +379,51 @@ ImportAddressImpl::ImportAddressBook(nsI
   if (NS_FAILED(rv)) {
     ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error,
                 m_notProxyBundle);
     SetLogs(success, error, pErrorLog, pSuccessLog);
     return rv;
   }
 
   if (isLDIF) {
-    if (ldifService)
+    if (ldifService) {
       rv = ldifService->ImportLDIFFile(pDestination, inFile, false,
                                        &m_bytesImported);
-    else
+      OnOperationComplete(NS_OK);
+    } else {
       return NS_ERROR_FAILURE;
+    }
   } else {
     rv = m_text.ImportAddresses(&addrAbort, name.get(), inFile, pDestination,
-                                fieldMap, error, &m_bytesImported);
+                                fieldMap, error, &m_bytesImported, this);
     SaveFieldMap(fieldMap);
   }
 
-  if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
-    ReportSuccess(name, &success, m_notProxyBundle);
-    SetLogs(success, error, pErrorLog, pSuccessLog);
-  } else {
+  if (NS_FAILED(rv) || !error.IsEmpty()) {
     ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error,
                 m_notProxyBundle);
     SetLogs(success, error, pErrorLog, pSuccessLog);
   }
 
   IMPORT_LOG0("*** Text address import done\n");
   return rv;
 }
 
 NS_IMETHODIMP ImportAddressImpl::GetImportProgress(uint32_t *_retval) {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = m_bytesImported;
   return NS_OK;
 }
 
+NS_IMETHODIMP ImportAddressImpl::GetImportIsComplete(bool *_retval) {
+  NS_ENSURE_ARG_POINTER(_retval);
+  *_retval = m_complete;
+  return NS_OK;
+}
+
 NS_IMETHODIMP ImportAddressImpl::GetNeedsFieldMap(nsIFile *aLocation,
                                                   bool *_retval) {
   NS_ENSURE_ARG_POINTER(_retval);
   NS_ENSURE_ARG_POINTER(aLocation);
 
   *_retval = true;
   bool exists = false;
   bool isFile = false;
@@ -645,8 +630,17 @@ void ImportAddressImpl::SaveFieldMap(nsI
   }
 
   // Now also save last used skip first record value.
   bool skipFirstRecord = false;
   rv = pMap->GetSkipFirstRecord(&skipFirstRecord);
   if (NS_SUCCEEDED(rv))
     prefs->SetBoolPref("mailnews.import.text.skipfirstrecord", skipFirstRecord);
 }
+
+NS_IMETHODIMP ImportAddressImpl::OnOperationComplete(nsresult status) {
+  if (NS_SUCCEEDED(status)) {
+    m_complete = true;
+  } else {
+    *m_fatalError = true;
+  }
+  return NS_OK;
+}
--- a/mailnews/import/vcard/src/nsVCardAddress.cpp
+++ b/mailnews/import/vcard/src/nsVCardAddress.cpp
@@ -4,34 +4,33 @@
 
 #include "nsAbBaseCID.h"
 #include "nsNativeCharsetUtils.h"
 #include "nsNetUtil.h"
 #include "nsVCardAddress.h"
 
 #include "nsIAbCard.h"
 #include "nsIAbManager.h"
-#include "nsIAbDirectory.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 
 #include "plstr.h"
 #include "msgCore.h"
 #include "nsMsgUtils.h"
 
 nsVCardAddress::nsVCardAddress() {}
 
 nsVCardAddress::~nsVCardAddress() {}
 
 nsresult nsVCardAddress::ImportAddresses(bool *pAbort, const char16_t *pName,
                                          nsIFile *pSrc,
                                          nsIAbDirectory *pDirectory,
-                                         nsString &errors,
-                                         uint32_t *pProgress) {
+                                         nsString &errors, uint32_t *pProgress,
+                                         nsIAbOperationListener *listener) {
   // Open the source file for reading, read each line and process it!
   nsCOMPtr<nsIInputStream> inputStream;
   nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
   if (NS_FAILED(rv)) {
     IMPORT_LOG0("*** Error opening address file for reading\n");
     return rv;
   }
 
@@ -51,41 +50,41 @@ nsresult nsVCardAddress::ImportAddresses
   nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(inputStream, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIAbManager> ab = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool more = true;
   nsCString record;
+  nsTArray<RefPtr<nsIAbCard>> cards;
   while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
     rv = ReadRecord(lineStream, record, &more);
     if (NS_SUCCEEDED(rv) && !record.IsEmpty()) {
       // Parse the vCard and build an nsIAbCard from it
-      nsCOMPtr<nsIAbCard> cardFromVCard;
+      RefPtr<nsIAbCard> cardFromVCard;
       rv =
           ab->EscapedVCardToAbCard(record.get(), getter_AddRefs(cardFromVCard));
       NS_ENSURE_SUCCESS(rv, rv);
 
-      nsIAbCard *outCard;
-      rv = pDirectory->AddCard(cardFromVCard, &outCard);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (NS_FAILED(rv)) {
+      if (NS_SUCCEEDED(rv)) {
+        cards.AppendElement(cardFromVCard);
+      } else {
         IMPORT_LOG0("*** Error processing vCard record.\n");
       }
     }
     if (NS_SUCCEEDED(rv) && pProgress) {
       // This won't be totally accurate, but its the best we can do
       // considering that lineStream won't give us how many bytes
       // are actually left.
       bytesLeft -= record.Length();
       *pProgress = totalBytes - bytesLeft;
     }
   }
+  rv = pDirectory->AddCards(cards, listener);
   inputStream->Close();
 
   if (NS_FAILED(rv)) {
     IMPORT_LOG0(
         "*** Error reading the address book - probably incorrect ending\n");
     return NS_ERROR_FAILURE;
   }
 
--- a/mailnews/import/vcard/src/nsVCardAddress.h
+++ b/mailnews/import/vcard/src/nsVCardAddress.h
@@ -1,28 +1,30 @@
 /* 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/. */
 
 #ifndef nsVCardAddress_h__
 #define nsVCardAddress_h__
 
+#include "nsIAbDirectory.h"
 #include "ImportDebug.h"
 
 class nsIAbDirectory;
 class nsIFile;
 class nsILineInputStream;
 
 class nsVCardAddress {
  public:
   nsVCardAddress();
   virtual ~nsVCardAddress();
 
   nsresult ImportAddresses(bool *pAbort, const char16_t *pName, nsIFile *pSrc,
                            nsIAbDirectory *pDirectory, nsString &errors,
-                           uint32_t *pProgress);
+                           uint32_t *pProgress,
+                           nsIAbOperationListener *listener);
 
  private:
   static nsresult ReadRecord(nsILineInputStream *aLineStream,
                              nsCString &aRecord, bool *aMore);
 };
 
 #endif /* nsVCardAddress_h__ */
--- a/mailnews/import/vcard/src/nsVCardImport.cpp
+++ b/mailnews/import/vcard/src/nsVCardImport.cpp
@@ -18,80 +18,44 @@
 #include "nsImportStringBundle.h"
 #include "nsMsgUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTextFormatter.h"
 #include "nsVCardAddress.h"
 #include "nsVCardImport.h"
 
-class ImportVCardAddressImpl : public nsIImportAddressBooks {
+class ImportVCardAddressImpl final : public nsIImportAddressBooks,
+                                     nsIAbOperationListener {
  public:
   explicit ImportVCardAddressImpl(nsIStringBundle *aStringBundle);
 
   static nsresult Create(nsIImportAddressBooks **aImport,
                          nsIStringBundle *aStringBundle);
 
-  // nsISupports interface
   NS_DECL_THREADSAFE_ISUPPORTS
-
-  // nsIImportAddressBooks interface
-
-  // TODO: support multiple vCard files in future - shouldn't be too hard,
-  // since you just import each file in turn.
-  NS_IMETHOD GetSupportsMultiple(bool *_retval) override {
-    *_retval = false;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetAutoFind(char16_t **description, bool *_retval) override;
-
-  NS_IMETHOD GetNeedsFieldMap(nsIFile *location, bool *_retval) override {
-    *_retval = false;
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetDefaultLocation(nsIFile **location, bool *found,
-                                bool *userVerify) override;
-
-  NS_IMETHOD FindAddressBooks(nsIFile *location, nsIArray **_retval) override;
-
-  NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) override {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source,
-                               nsIAbDirectory *destination,
-                               nsIImportFieldMap *fieldMap,
-                               nsISupports *aSupportService,
-                               char16_t **errorLog, char16_t **successLog,
-                               bool *fatalError) override;
-
-  NS_IMETHOD GetImportProgress(uint32_t *_retval) override;
-
-  NS_IMETHOD GetSampleData(int32_t index, bool *pFound,
-                           char16_t **pStr) override {
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_IMETHOD SetSampleLocation(nsIFile *) override { return NS_ERROR_FAILURE; }
+  NS_DECL_NSIIMPORTADDRESSBOOKS
+  NS_DECL_NSIABOPERATIONLISTENER
 
  private:
   virtual ~ImportVCardAddressImpl();
   static void ReportSuccess(nsString &name, nsString *pStream,
                             nsIStringBundle *pBundle);
   static void SetLogs(nsString &success, nsString &error, char16_t **pError,
                       char16_t **pSuccess);
   static void ReportError(const char *errorName, nsString &name,
                           nsString *pStream, nsIStringBundle *pBundle);
 
  private:
+  nsString m_name;
   nsVCardAddress m_vCard;
   nsCOMPtr<nsIFile> m_fileLoc;
   uint32_t m_bytesImported;
+  bool m_complete;
+  bool *m_fatalError;
   nsCOMPtr<nsIStringBundle> m_notProxyBundle;
 };
 
 nsVCardImport::nsVCardImport() {
   nsImportStringBundle::GetStringBundle(VCARDIMPORT_MSGS_URL,
                                         getter_AddRefs(m_stringBundle));
 
   IMPORT_LOG0("nsVCardImport Module Created\n");
@@ -165,17 +129,23 @@ nsresult ImportVCardAddressImpl::Create(
   return NS_OK;
 }
 
 ImportVCardAddressImpl::ImportVCardAddressImpl(nsIStringBundle *aStringBundle)
     : m_notProxyBundle(aStringBundle) {}
 
 ImportVCardAddressImpl::~ImportVCardAddressImpl() {}
 
-NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks)
+NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks,
+                  nsIAbOperationListener)
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetSupportsMultiple(bool *_retval) {
+  *_retval = false;
+  return NS_OK;
+}
 
 NS_IMETHODIMP ImportVCardAddressImpl::GetAutoFind(char16_t **addrDescription,
                                                   bool *_retval) {
   NS_ENSURE_ARG_POINTER(addrDescription);
   NS_ENSURE_ARG_POINTER(_retval);
 
   nsString str;
   *_retval = false;
@@ -183,16 +153,22 @@ NS_IMETHODIMP ImportVCardAddressImpl::Ge
   if (!m_notProxyBundle) return NS_ERROR_FAILURE;
 
   nsImportStringBundle::GetStringByName("vCardImportAddressName",
                                         m_notProxyBundle, str);
   *addrDescription = ToNewUnicode(str);
   return NS_OK;
 }
 
+NS_IMETHODIMP ImportVCardAddressImpl::GetNeedsFieldMap(nsIFile *location,
+                                                       bool *_retval) {
+  *_retval = false;
+  return NS_OK;
+}
+
 NS_IMETHODIMP ImportVCardAddressImpl::GetDefaultLocation(nsIFile **ppLoc,
                                                          bool *found,
                                                          bool *userVerify) {
   NS_ENSURE_ARG_POINTER(found);
   NS_ENSURE_ARG_POINTER(ppLoc);
   NS_ENSURE_ARG_POINTER(userVerify);
 
   *ppLoc = nullptr;
@@ -244,31 +220,36 @@ NS_IMETHODIMP ImportVCardAddressImpl::Fi
     return rv;
   }
 
   rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
   if (NS_SUCCEEDED(rv)) {
     int64_t sz = 0;
     pLoc->GetFileSize(&sz);
     desc->SetPreferredName(name);
-    desc->SetSize((uint32_t)sz);
+    desc->SetSize((uint32_t)sz + 1000);
     desc->SetAbFile(m_fileLoc);
     nsCOMPtr<nsISupports> pInterface(do_QueryInterface(desc, &rv));
     array->AppendElement(pInterface);
   }
   if (NS_FAILED(rv)) {
     IMPORT_LOG0(
         "*** Error creating address book descriptor for vCard import\n");
     return rv;
   }
 
   array.forget(ppArray);
   return NS_OK;
 }
 
+NS_IMETHODIMP ImportVCardAddressImpl::InitFieldMap(
+    nsIImportFieldMap *fieldMap) {
+  return NS_ERROR_FAILURE;
+}
+
 void ImportVCardAddressImpl::ReportSuccess(nsString &name, nsString *pStream,
                                            nsIStringBundle *pBundle) {
   if (!pStream) return;
 
   // load the success string
   char16_t *pFmt = nsImportStringBundle::GetStringByName(
       "vCardImportAddressSuccess", pBundle);
 
@@ -305,56 +286,79 @@ NS_IMETHODIMP ImportVCardAddressImpl::Im
     char16_t **pErrorLog, char16_t **pSuccessLog, bool *fatalError) {
   NS_ENSURE_ARG_POINTER(pSource);
   NS_ENSURE_ARG_POINTER(pDestination);
   NS_ENSURE_ARG_POINTER(fatalError);
 
   if (!m_notProxyBundle) return NS_ERROR_FAILURE;
 
   m_bytesImported = 0;
+  m_complete = false;
+  m_fatalError = fatalError;
   nsString success, error;
   bool addrAbort = false;
-  nsString name;
-  pSource->GetPreferredName(name);
+  pSource->GetPreferredName(m_name);
 
   uint32_t addressSize = 0;
   pSource->GetSize(&addressSize);
   if (addressSize == 0) {
     IMPORT_LOG0("Address book size is 0, skipping import.\n");
-    ReportSuccess(name, &success, m_notProxyBundle);
+    ReportSuccess(m_name, &success, m_notProxyBundle);
     SetLogs(success, error, pErrorLog, pSuccessLog);
     return NS_OK;
   }
 
   nsCOMPtr<nsIFile> inFile;
   if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) {
-    ReportError("vCardImportAddressBadSourceFile", name, &error,
+    ReportError("vCardImportAddressBadSourceFile", m_name, &error,
                 m_notProxyBundle);
     SetLogs(success, error, pErrorLog, pSuccessLog);
     return NS_ERROR_FAILURE;
   }
 
   if (!aSupportService) {
     IMPORT_LOG0("Missing support service to import call\n");
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = m_vCard.ImportAddresses(&addrAbort, name.get(), inFile,
-                                        pDestination, error, &m_bytesImported);
+  nsresult rv =
+      m_vCard.ImportAddresses(&addrAbort, m_name.get(), inFile, pDestination,
+                              error, &m_bytesImported, this);
 
-  if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
-    ReportSuccess(name, &success, m_notProxyBundle);
-    SetLogs(success, error, pErrorLog, pSuccessLog);
-  } else {
-    ReportError("vCardImportAddressConvertError", name, &error,
+  if (NS_FAILED(rv) || !error.IsEmpty()) {
+    ReportError("vCardImportAddressConvertError", m_name, &error,
                 m_notProxyBundle);
     SetLogs(success, error, pErrorLog, pSuccessLog);
   }
 
   IMPORT_LOG0("*** VCard address import done\n");
   return rv;
 }
 
 NS_IMETHODIMP ImportVCardAddressImpl::GetImportProgress(uint32_t *_retval) {
   NS_ENSURE_ARG_POINTER(_retval);
   *_retval = m_bytesImported;
   return NS_OK;
 }
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetImportIsComplete(bool *_retval) {
+  NS_ENSURE_ARG_POINTER(_retval);
+  *_retval = m_complete;
+  return NS_OK;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetSampleData(int32_t index, bool *pFound,
+                                                    char16_t **pStr) {
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::SetSampleLocation(nsIFile *) {
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::OnOperationComplete(nsresult status) {
+  if (NS_SUCCEEDED(status)) {
+    m_complete = true;
+  } else {
+    *m_fatalError = true;
+  }
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/try_task_config.json
@@ -0,0 +1,8 @@
+{
+  "version": 1,
+  "tasks": [
+    "test-linux64/opt-xpcshell-1",
+    "test-macosx1014-64/opt-xpcshell-1",
+    "test-windows10-64/opt-xpcshell-1"
+  ]
+}