Bug 1576525 - Use JS address book provider for the default address books in new profiles; r=mkmelin
authorGeoff Lankow <geoff@darktrojan.net>
Wed, 28 Aug 2019 12:32:06 +1200
changeset 79099 aa8107b682ba6fd44e9b91e49ac8846d84bf5abb
parent 79044 6542dc3163386308ccb3bd4db7e914d2eb402844
child 79100 1357e506bf3af0afb073d5a221431dd092837c34
push id9346
push usermozilla@jorgk.com
push dateFri, 20 Sep 2019 10:42:33 +0000
treeherdertry-comm-central@0ea0eee10211 [default view] [failures only]
reviewersmkmelin
bugs1576525
Bug 1576525 - Use JS address book provider for the default address books in new profiles; r=mkmelin
mail/base/content/msgHdrView.js
mail/base/modules/MailMigrator.jsm
mail/components/addrbook/content/abCommon.js
mail/components/addrbook/content/abTrees.js
mail/components/addrbook/content/addressbook.js
mail/components/addrbook/content/menulist-addrbooks.js
mail/components/extensions/parent/ext-addressBook.js
mail/components/extensions/test/xpcshell/test_ext_addressBook.js
mail/components/extensions/test/xpcshell/test_ext_messages_query.js
mail/components/migration/src/nsProfileMigratorBase.cpp
mail/test/mozmill/addrbook/test-address-book.js
mail/test/mozmill/addrbook/test-update-mailing-list.js
mail/test/mozmill/composition/test-send-button.js
mail/test/mozmill/message-header/test-message-header.js
mail/test/mozmill/multiple-identities/test-display-names.js
mail/test/mozmill/quick-filter-bar/test-filter-logic.js
mail/test/mozmill/shared-modules/test-address-book-helpers.js
mailnews/addrbook/content/abAddressBookNameDialog.js
mailnews/addrbook/jsaddrbook/AddrBookCard.jsm
mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
mailnews/addrbook/jsaddrbook/AddrBookFactory.jsm
mailnews/addrbook/jsaddrbook/AddrBookMailingList.jsm
mailnews/addrbook/public/nsIAbDirectory.idl
mailnews/addrbook/src/nsAbAddressCollector.cpp
mailnews/addrbook/src/nsAbAddressCollector.h
mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
mailnews/addrbook/src/nsDirPrefs.cpp
mailnews/addrbook/test/unit/data/cardForEmail.sql
mailnews/addrbook/test/unit/data/collect.sql
mailnews/addrbook/test/unit/head_addrbook.js
mailnews/addrbook/test/unit/head_jsaddrbook.js
mailnews/addrbook/test/unit/test_jsaddrbook.js
mailnews/addrbook/test/unit/test_nsAbManager2.js
mailnews/base/search/content/searchWidgets.js
mailnews/base/src/nsMsgDBView.cpp
mailnews/base/test/unit/data/remoteContent.sql
mailnews/base/test/unit/test_accountMigration.js
mailnews/base/test/unit/test_junkWhitelisting.js
mailnews/base/test/unit/test_searchAddressInAb.js
mailnews/compose/test/unit/data/listexpansion.sql
mailnews/compose/test/unit/test_expandMailingLists.js
mailnews/compose/test/unit/test_nsMsgCompose1.js
mailnews/compose/test/unit/test_nsMsgCompose3.js
mailnews/compose/test/unit/test_nsMsgCompose4.js
mailnews/mailnews.js
mailnews/test/data/abLists1.sql
mailnews/test/data/abLists2.sql
mailnews/test/data/tb2hexpopularity.sql
mailnews/test/resources/abSetup.js
--- a/mail/base/content/msgHdrView.js
+++ b/mail/base/content/msgHdrView.js
@@ -1649,17 +1649,21 @@ function AddContact(emailAddressNode) {
   emailAddressNode = emailAddressNode.closest("mail-emailaddress");
   // When we collect an address, it updates the AB which sends out
   // notifications to update the UI. In the add case we don't want to update
   // the UI so that accidentally double-clicking on the star doesn't lead
   // to something strange (i.e star would be moved out from underneath,
   // leaving something else there).
   emailAddressNode.setAttribute("updatingUI", true);
 
-  const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+  let stillUsingMabFiles =
+    Services.prefs.getIntPref("ldap_2.servers.pab.dirType") == 2;
+  let kPersonalAddressbookURI = stillUsingMabFiles
+    ? "moz-abmdbdirectory://abook.mab"
+    : "jsaddrbook://abook.sqlite";
   let addressBook = MailServices.ab.getDirectory(kPersonalAddressbookURI);
 
   let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
     Ci.nsIAbCard
   );
   card.displayName = emailAddressNode.getAttribute("displayName");
   card.primaryEmail = emailAddressNode.getAttribute("emailAddress");
 
--- a/mail/base/modules/MailMigrator.jsm
+++ b/mail/base/modules/MailMigrator.jsm
@@ -413,15 +413,43 @@ var MailMigrator = {
    * Perform any migration work that needs to occur after the Account Wizard
    * has had a chance to appear.
    */
   migratePostAccountWizard() {
     this.migrateToClearTypeFonts();
   },
 
   /**
+   * Migrate address books away from Mork. In time this will do actual
+   * migration, but for now, just set the default pref back to what it was.
+   */
+  _migrateAddressBooks() {
+    let pab = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    pab.append("abook.mab");
+    if (pab.exists()) {
+      let defaultBranch = Services.prefs.getDefaultBranch("");
+      defaultBranch.setIntPref("ldap_2.servers.pab.dirType", 2);
+      defaultBranch.setStringPref("ldap_2.servers.pab.filename", "abook.mab");
+      defaultBranch.setIntPref("ldap_2.servers.history.dirType", 2);
+      defaultBranch.setStringPref(
+        "ldap_2.servers.history.filename",
+        "history.mab"
+      );
+      defaultBranch.setStringPref(
+        "mail.collect_addressbook",
+        "moz-abmdbdirectory://history.mab"
+      );
+      defaultBranch.setStringPref(
+        "mail.server.default.whiteListAbURI",
+        "moz-abmdbdirectory://abook.mab"
+      );
+    }
+  },
+
+  /**
    * Perform any migration work that needs to occur once the user profile has
    * been loaded.
    */
   migrateAtProfileStartup() {
+    this._migrateAddressBooks();
     this._migrateUI();
   },
 };
--- a/mail/components/addrbook/content/abCommon.js
+++ b/mail/components/addrbook/content/abCommon.js
@@ -34,18 +34,24 @@ var kDefaultAscending = "ascending";
 var kDefaultDescending = "descending";
 // kDefaultYear will be used in birthday calculations when no year is given;
 // this is a leap year so that Feb 29th works.
 const kDefaultYear = nearestLeap(new Date().getFullYear());
 const kMaxYear = 9999;
 const kMinYear = 1;
 var kAllDirectoryRoot = "moz-abdirectory://";
 var kLdapUrlPrefix = "moz-abldapdirectory://";
-var kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
-var kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+var stillUsingMabFiles =
+  Services.prefs.getIntPref("ldap_2.servers.pab.dirType") == 2;
+var kPersonalAddressbookURI = stillUsingMabFiles
+  ? "moz-abmdbdirectory://abook.mab"
+  : "jsaddrbook://abook.sqlite";
+var kCollectedAddressbookURI = stillUsingMabFiles
+  ? "moz-abmdbdirectory://history.mab"
+  : "jsaddrbook://history.sqlite";
 // The default, generic contact image is displayed via CSS when the photoURI is
 // blank.
 var defaultPhotoURI = "";
 
 var PERMS_DIRECTORY = parseInt("0755", 8);
 
 // Controller object for Dir Pane
 var DirPaneController = {
@@ -856,24 +862,24 @@ function GenerateAddressFromCard(card) {
 function GetDirectoryFromURI(uri) {
   return MailServices.ab.getDirectory(uri);
 }
 
 // returns null if abURI is not a mailing list URI
 function GetParentDirectoryFromMailingListURI(abURI) {
   var abURIArr = abURI.split("/");
   /*
-   turn turn "moz-abmdbdirectory://abook.mab/MailList6"
-   into ["moz-abmdbdirectory:","","abook.mab","MailList6"]
-   then, turn ["moz-abmdbdirectory:","","abook.mab","MailList6"]
-   into "moz-abmdbdirectory://abook.mab"
+   turn turn "jsaddrbook://abook.sqlite/MailList6"
+   into ["jsaddrbook:","","abook.sqlite","MailList6"]
+   then, turn ["jsaddrbook:","","abook.sqlite","MailList6"]
+   into "jsaddrbook://abook.sqlite"
   */
   if (
     abURIArr.length == 4 &&
-    abURIArr[0] == "moz-abmdbdirectory:" &&
+    ["jsaddrbook:", "moz-abmdbdirectory:"].includes(abURIArr[0]) &&
     abURIArr[3] != ""
   ) {
     return abURIArr[0] + "/" + abURIArr[1] + "/" + abURIArr[2];
   }
 
   return null;
 }
 
--- a/mail/components/addrbook/content/abTrees.js
+++ b/mail/components/addrbook/content/abTrees.js
@@ -11,33 +11,47 @@
  */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
 );
 var { IOUtils } = ChromeUtils.import("resource:///modules/IOUtils.js");
 
+const DIRTYPE_JS = 101;
+
 // Tree Sort helper methods.
-var AB_ORDER = ["aab", "pab", "mork", "ldap", "mapi+other", "anyab", "cab"];
+var AB_ORDER = [
+  "aab",
+  "pab",
+  "mork",
+  "js",
+  "ldap",
+  "mapi+other",
+  "anyab",
+  "cab",
+];
 
 function getDirectoryValue(aDir, aKey) {
   if (aKey == "ab_type") {
     if (aDir._directory.URI == kAllDirectoryRoot + "?") {
       return "aab";
     }
     if (aDir._directory.URI == kPersonalAddressbookURI) {
       return "pab";
     }
     if (aDir._directory.URI == kCollectedAddressbookURI) {
       return "cab";
     }
     if (aDir._directory instanceof Ci.nsIAbMDBDirectory) {
       return "mork";
     }
+    if (aDir._directory.dirType == DIRTYPE_JS) {
+      return "js";
+    }
     if (aDir._directory instanceof Ci.nsIAbLDAPDirectory) {
       return "ldap";
     }
 
     // If there is any other AB type.
     return "mapi+other";
   } else if (aKey == "ab_name") {
     return aDir._directory.dirName;
--- a/mail/components/addrbook/content/addressbook.js
+++ b/mail/components/addrbook/content/addressbook.js
@@ -404,18 +404,18 @@ function AbPrintAddressBookInternal(doPr
 
   var statusFeedback;
   statusFeedback = Cc[
     "@mozilla.org/messenger/statusfeedback;1"
   ].createInstance();
   statusFeedback = statusFeedback.QueryInterface(Ci.nsIMsgStatusFeedback);
 
   /*
-    turn "moz-abmdbdirectory://abook.mab" into
-    "addbook://moz-abmdbdirectory/abook.mab?action=print"
+    turn "jsaddrbook://abook.sqlite" into
+    "addbook://jsaddrbook/abook.sqlite?action=print"
    */
 
   var abURIArr = uri.split("://");
   var printUrl =
     "addbook://" + abURIArr[0] + "/" + abURIArr[1] + "?action=print";
 
   window.openDialog(
     "chrome://messenger/content/msgPrintEngine.xul",
--- a/mail/components/addrbook/content/menulist-addrbooks.js
+++ b/mail/components/addrbook/content/menulist-addrbooks.js
@@ -273,32 +273,40 @@ if (!customElements.get("menulist")) {
         return -1;
       }
 
       if (!b) {
         return 1;
       }
 
       // Personal at the top.
-      const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
-      if (a.URI == kPersonalAddressbookURI) {
+      // Having two possible options here seems illogical, but there can be
+      // only one of them. Once migration happens we can remove this oddity.
+      const kPersonalAddressbookURIs = [
+        "jsaddrbook://abook.sqlite",
+        "moz-abmdbdirectory://abook.mab",
+      ];
+      if (kPersonalAddressbookURIs.includes(a.URI)) {
         return -1;
       }
 
-      if (b.URI == kPersonalAddressbookURI) {
+      if (kPersonalAddressbookURIs.includes(b.URI)) {
         return 1;
       }
 
       // Collected at the bottom.
-      const kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
-      if (a.URI == kCollectedAddressbookURI) {
+      const kCollectedAddressbookURIs = [
+        "jsaddrbook://history.sqlite",
+        "moz-abmdbdirectory://history.mab",
+      ];
+      if (kCollectedAddressbookURIs.includes(a.URI)) {
         return 1;
       }
 
-      if (b.URI == kCollectedAddressbookURI) {
+      if (kCollectedAddressbookURIs.includes(b.URI)) {
         return -1;
       }
 
       // Sort books of the same type by name.
       if (a.dirType == b.dirType) {
         return a.dirName.localeCompare(b.dirName);
       }
 
--- a/mail/components/extensions/parent/ext-addressBook.js
+++ b/mail/components/extensions/parent/ext-addressBook.js
@@ -367,20 +367,19 @@ var addressBookCache = new (class extend
             }
           }
         }
         this.emit("contact-updated", newNode);
         break;
       }
       case "addrbook-list-updated": {
         subject.QueryInterface(Ci.nsIAbDirectory);
-        this.emit(
-          "mailing-list-updated",
-          this.findMailingListById(subject.UID)
-        );
+        let listNode = this.findMailingListById(subject.UID);
+        listNode.item = subject;
+        this.emit("mailing-list-updated", listNode);
         break;
       }
       case "addrbook-list-member-added": {
         let parentNode = this.findMailingListById(data);
         let newNode = this._makeContactNode(subject, parentNode.item);
         if (
           this._mailingLists.has(data) &&
           this._mailingLists.get(data).contacts
--- a/mail/components/extensions/test/xpcshell/test_ext_addressBook.js
+++ b/mail/components/extensions/test/xpcshell/test_ext_addressBook.js
@@ -1,14 +1,17 @@
 /* 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/. */
 
 "use strict";
 
+var { fixIterator } = ChromeUtils.import(
+  "resource:///modules/iteratorUtils.jsm"
+);
 var { ExtensionTestUtils } = ChromeUtils.import(
   "resource://testing-common/ExtensionXPCShellUtils.jsm"
 );
 ExtensionTestUtils.init(this);
 
 add_task(async function test_addressBooks() {
   async function background() {
     let firstBookId, secondBookId, newContactId;
@@ -592,17 +595,17 @@ add_task(async function test_addressBook
       for (let child of parent.childCards) {
         if (child.UID == id) {
           return child;
         }
       }
       return null;
     }
     function findMailingList(id) {
-      for (let list of parent.addressLists.enumerate()) {
+      for (let list of fixIterator(parent.addressLists, Ci.nsIAbDirectory)) {
         if (list.UID == id) {
           return list;
         }
       }
       return null;
     }
 
     let parent = MailServices.ab.directories
@@ -809,8 +812,13 @@ add_task(async function test_quickSearch
     background,
     manifest: { permissions: ["addressBooks"] },
   });
 
   await extension.startup();
   await extension.awaitFinish("addressBooks");
   await extension.unload();
 });
+
+registerCleanupFunction(() => {
+  // Make sure any open database is given a chance to close.
+  Services.obs.notifyObservers(null, "quit-application");
+});
--- a/mail/components/extensions/test/xpcshell/test_ext_messages_query.js
+++ b/mail/components/extensions/test/xpcshell/test_ext_messages_query.js
@@ -172,8 +172,13 @@ add_task(async function() {
     manifest: { permissions: ["accountsRead", "messagesRead"] },
   });
 
   await extension.startup();
   extension.sendMessage(account.key);
   await extension.awaitFinish("finished");
   await extension.unload();
 });
+
+registerCleanupFunction(() => {
+  // Make sure any open address book database is given a chance to close.
+  Services.obs.notifyObservers(null, "quit-application");
+});
--- a/mail/components/migration/src/nsProfileMigratorBase.cpp
+++ b/mail/components/migration/src/nsProfileMigratorBase.cpp
@@ -8,17 +8,17 @@
 #include "nsProfileMigratorBase.h"
 #include "nsIMailProfileMigrator.h"
 
 #include "nsIImportSettings.h"
 #include "nsIImportFilters.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 
-#define kPersonalAddressbookUri "moz-abmdbdirectory://abook.mab"
+#define kPersonalAddressbookUri "jsaddrbook://abook.sqlite"
 
 nsProfileMigratorBase::nsProfileMigratorBase() {
   mObserverService = do_GetService("@mozilla.org/observer-service;1");
   mProcessingMailFolders = false;
 }
 
 nsProfileMigratorBase::~nsProfileMigratorBase() {
   if (mFileIOTimer) mFileIOTimer->Cancel();
--- a/mail/test/mozmill/addrbook/test-address-book.js
+++ b/mail/test/mozmill/addrbook/test-address-book.js
@@ -43,20 +43,20 @@ function setupModule(module) {
 
   // Open the address book main window
   abController = open_address_book_window();
 
   // Let's add some new address books.  I'll add them
   // out of order to properly test the alphabetical
   // ordering of the address books.
   ldapBook = create_ldap_address_book("LDAP Book");
-  addrBook3 = create_mork_address_book("AB 3");
-  addrBook1 = create_mork_address_book("AB 1");
-  addrBook4 = create_mork_address_book("AB 4");
-  addrBook2 = create_mork_address_book("AB 2");
+  addrBook3 = create_address_book("AB 3");
+  addrBook1 = create_address_book("AB 1");
+  addrBook4 = create_address_book("AB 4");
+  addrBook2 = create_address_book("AB 2");
 
   mListA = create_mailing_list("ML A");
   addrBook1.addMailList(mListA);
 
   mListB = create_mailing_list("ML B");
   addrBook2.addMailList(mListB);
 
   mListC = create_mailing_list("ML C");
--- a/mail/test/mozmill/addrbook/test-update-mailing-list.js
+++ b/mail/test/mozmill/addrbook/test-update-mailing-list.js
@@ -25,28 +25,26 @@ function setupModule(module) {
   collector.getModule("address-book-helpers").installInto(module);
 }
 
 function test_contact_in_mailing_list_updated() {
   const kOldAddress = "before@example.com";
   const kNewAddress = "after@example.com";
 
   // Create some address book to work with...
-  let ab = create_mork_address_book("Some Address Book");
+  let ab = create_address_book("Some Address Book");
   // And a contact...
   let contact = create_contact(kOldAddress, "Some Contact", true);
   // And our mailing list.
   let ml = create_mailing_list("Some Mailing List");
 
   // Add the mailing list to the address book, and then the card to the
   // address book, and finally, the card to the mailing list.
-  ml.addressLists.appendElement(contact);
   ml = ab.addMailList(ml);
-
-  contact = ml.addressLists.queryElementAt(0, Ci.nsIAbCard);
+  contact = ml.addCard(contact);
 
   // Open the address book, select our contact...
   let abw = open_address_book_window(mc);
   select_address_book(ab);
   select_contacts(contact);
 
   // Change the primary email address of the contact...
   edit_selected_contact(abw, function(ecw) {
--- a/mail/test/mozmill/composition/test-send-button.js
+++ b/mail/test/mozmill/composition/test-send-button.js
@@ -130,19 +130,17 @@ function test_send_enabled_manual_addres
   setup_msg_contents(cwc, "domain.invalid", "", "");
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // - a mailinglist in addressbook
   // Button is enabled without checking whether it contains valid addresses.
-  let defaultAB = MailServices.ab.getDirectory(
-    "moz-abmdbdirectory://abook.mab"
-  );
+  let defaultAB = MailServices.ab.getDirectory("jsaddrbook://abook.sqlite");
   let ml = create_mailing_list("emptyList");
   defaultAB.addMailList(ml);
 
   setup_msg_contents(cwc, " emptyList", "", "");
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
@@ -232,19 +230,17 @@ function test_send_enabled_prefilled_add
 
 /**
  * Bug 863231
  * Test that the Send buttons are properly enabled if an addressee is populated
  * via the Contacts sidebar.
  */
 function test_send_enabled_address_contacts_sidebar() {
   // Create some contact address book card in the Personal addressbook.
-  let defaultAB = MailServices.ab.getDirectory(
-    "moz-abmdbdirectory://abook.mab"
-  );
+  let defaultAB = MailServices.ab.getDirectory("jsaddrbook://abook.sqlite");
   let contact = create_contact("test@example.com", "Sammy Jenkis", true);
   load_contacts_into_address_book(defaultAB, [contact]);
 
   let cwc = open_compose_new_mail(); // compose controller
   // On an empty window, Send must be disabled.
   check_send_commands_state(cwc, false);
 
   // Open Contacts sidebar and use our contact.
--- a/mail/test/mozmill/message-header/test-message-header.js
+++ b/mail/test/mozmill/message-header/test-message-header.js
@@ -636,27 +636,26 @@ function test_address_book_switch_disabl
   assert_equals(cards.length, 1);
   let card = cards[0];
 
   // Remove the card from any of the address books
   ensure_no_card_exists(targetAddr);
 
   // Add the card to a new address book, and insert it
   // into a mailing list under that address book
-  let ab = create_mork_address_book(ADDRESS_BOOK_NAME);
+  let ab = create_address_book(ADDRESS_BOOK_NAME);
   ab.dropCard(card, false);
   let ml = create_mailing_list(MAILING_LIST_DIRNAME);
   ab.addMailList(ml);
 
   // Now we have to retrieve the mailing list from
   // the address book, in order for us to add and
   // delete cards from it.
   ml = get_mailing_list_from_address_book(ab, MAILING_LIST_DIRNAME);
-
-  ml.addressLists.appendElement(card);
+  ml.addCard(card);
 
   // Re-open the inline contact editing panel
   mc.click(getElement(lastAddr, ".emailStar"));
   mc.waitFor(
     () => contactPanel.state == "open",
     () =>
       "Timeout waiting for contactPanel to open; state=" + contactPanel.state
   );
--- a/mail/test/mozmill/multiple-identities/test-display-names.js
+++ b/mail/test/mozmill/multiple-identities/test-display-names.js
@@ -73,17 +73,17 @@ function setupModule(module) {
   add_message_to_folder(
     folder,
     create_message({ from: [friendName, friendEmail] })
   );
 
   // Ensure all the directories are initialised.
   MailServices.ab.directories;
   collectedAddresses = MailServices.ab.getDirectory(
-    "moz-abmdbdirectory://history.mab"
+    "jsaddrbook://history.sqlite"
   );
 
   let bundle = Services.strings.createBundle(
     "chrome://messenger/locale/messenger.properties"
   );
   headertoFieldMe = bundle.GetStringFromName("headertoFieldMe");
 }
 
--- a/mail/test/mozmill/quick-filter-bar/test-filter-logic.js
+++ b/mail/test/mozmill/quick-filter-bar/test-filter-logic.js
@@ -108,20 +108,17 @@ function add_email_to_address_book(aEmai
   let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
     Ci.nsIAbCard
   );
   card.primaryEmail = aEmailAddr;
 
   let enumerator = MailServices.ab.directories;
   while (enumerator.hasMoreElements()) {
     let addrbook = enumerator.getNext();
-    if (
-      addrbook instanceof Ci.nsIAbMDBDirectory &&
-      addrbook instanceof Ci.nsIAbDirectory
-    ) {
+    if (addrbook instanceof Ci.nsIAbDirectory) {
       addrbook.addCard(card);
       return;
     }
   }
 
   throw new Error("Unable to find any suitable address book.");
 }
 
--- a/mail/test/mozmill/shared-modules/test-address-book-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-address-book-helpers.js
@@ -8,17 +8,17 @@ var MODULE_NAME = "address-book-helpers"
 var RELATIVE_ROOT = "../shared-modules";
 var MODULE_REQUIRES = ["folder-display-helpers", "window-helpers"];
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
 );
 
-var ABMDB_PREFIX = "moz-abmdbdirectory://";
+var ABJS_PREFIX = "jsaddrbook://";
 var ABLDAP_PREFIX = "moz-abldapdirectory://";
 
 var collectedAddresses;
 
 var abController;
 
 var folderDisplayHelper;
 var mc;
@@ -26,29 +26,29 @@ var windowHelper;
 
 function setupModule() {
   folderDisplayHelper = collector.getModule("folder-display-helpers");
   mc = folderDisplayHelper.mc;
   windowHelper = collector.getModule("window-helpers");
   // Ensure all the directories are initialised.
   MailServices.ab.directories;
   collectedAddresses = MailServices.ab.getDirectory(
-    "moz-abmdbdirectory://history.mab"
+    "jsaddrbook://history.sqlite"
   );
 }
 
 function installInto(module) {
   setupModule();
 
   // Now copy helper functions
   module.ensure_card_exists = ensure_card_exists;
   module.ensure_no_card_exists = ensure_no_card_exists;
   module.open_address_book_window = open_address_book_window;
   module.close_address_book_window = close_address_book_window;
-  module.create_mork_address_book = create_mork_address_book;
+  module.create_address_book = create_address_book;
   module.create_ldap_address_book = create_ldap_address_book;
   module.create_contact = create_contact;
   module.create_mailing_list = create_mailing_list;
   module.get_mailing_list_from_address_book = get_mailing_list_from_address_book;
   module.load_contacts_into_address_book = load_contacts_into_address_book;
   module.load_contacts_into_mailing_list = load_contacts_into_mailing_list;
   module.get_cards_in_all_address_books_for_email = get_cards_in_all_address_books_for_email;
   module.get_address_book_tree_view_index = get_address_book_tree_view_index;
@@ -145,24 +145,24 @@ function open_address_book_window(aContr
  */
 function close_address_book_window(abc) {
   windowHelper.plan_for_window_close(abc);
   abc.window.close();
   return windowHelper.wait_for_window_close(abc);
 }
 
 /**
- * Creates and returns a Mork-backed address book.
+ * Creates and returns a SQLite-backed address book.
  * @param aName the name for the address book
  * @returns the nsIAbDirectory address book
  */
-function create_mork_address_book(aName) {
-  let abPrefString = MailServices.ab.newAddressBook(aName, "", 2);
+function create_address_book(aName) {
+  let abPrefString = MailServices.ab.newAddressBook(aName, "", 101);
   let abURI = Services.prefs.getCharPref(abPrefString + ".filename");
-  return MailServices.ab.getDirectory(ABMDB_PREFIX + abURI);
+  return MailServices.ab.getDirectory(ABJS_PREFIX + abURI);
 }
 
 /**
  * Creates and returns an LDAP-backed address book.
  * This function will automatically fill in a dummy
  * LDAP URI if no URI is supplied.
  * @param aName the name for the address book
  * @param aURI an optional URI for the address book
@@ -228,22 +228,23 @@ function get_mailing_list_from_address_b
  * @param aContacts a collection of nsIAbCards, or contacts,
  *                  where each contact has members "email"
  *                  and "displayName"
  *
  *                  Example:
  *                  [{email: 'test@example.com', displayName: 'Sammy Jenkis'}]
  */
 function load_contacts_into_address_book(aAddressBook, aContacts) {
-  for (let contact of aContacts) {
+  for (let i = 0; i < aContacts.length; i++) {
+    let contact = aContacts[i];
     if (!(contact instanceof Ci.nsIAbCard)) {
       contact = create_contact(contact.email, contact.displayName, true);
     }
 
-    aAddressBook.addCard(contact);
+    aContacts[i] = aAddressBook.addCard(contact);
   }
 }
 
 /* Given some mailing list, adds a collection of contacts to that
  * mailing list.
  * @param aMailingList a mailing list to add the contacts to
  * @param aContacts a collection of contacts, where each contact is either
  *                  an nsIAbCard, or an object with members "email" and
@@ -261,17 +262,17 @@ function load_contacts_into_mailing_list
 /* Given some address book, return the row index for that address book
  * in the tree view.  Throws an error if it cannot find the address book.
  * @param aAddrBook an address book to search for
  * @return the row index for that address book
  */
 function get_address_book_tree_view_index(aAddrBook) {
   let addrbooks = abController.window.gDirectoryTreeView._rowMap;
   for (let i = 0; i < addrbooks.length; i++) {
-    if (addrbooks[i]._directory == aAddrBook) {
+    if (addrbooks[i]._directory.URI == aAddrBook.URI) {
       return i;
     }
   }
   throw Error(
     "Could not find the index for the address book named " + aAddrBook.dirName
   );
 }
 
@@ -281,17 +282,17 @@ function get_address_book_tree_view_inde
  * find the contact.
  * @param aContact a contact to search for
  * @return the row index for that contact
  */
 function get_contact_ab_view_index(aContact) {
   let contacts = abController.window.gAbView;
   for (let i = 0; i < contacts.rowCount; i++) {
     let contact = contacts.getCardFromRow(i);
-    if (contact.localId == aContact.localId && !contact.isMailList) {
+    if (contact.equals(aContact)) {
       return i;
     }
   }
   throw Error(
     "Could not find the index for the contact named " + aContact.displayName
   );
 }
 
--- a/mailnews/addrbook/content/abAddressBookNameDialog.js
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.js
@@ -9,18 +9,24 @@ var { fixIterator } = ChromeUtils.import
   "resource:///modules/iteratorUtils.jsm"
 );
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var gOkButton;
 var gNameInput;
 var gDirectory = null;
 
-var kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
-var kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+var stillUsingMabFiles =
+  Services.prefs.getIntPref("ldap_2.servers.pab.dirType") == 2;
+var kPersonalAddressbookURI = stillUsingMabFiles
+  ? "moz-abmdbdirectory://abook.mab"
+  : "jsaddrbook://abook.sqlite";
+var kCollectedAddressbookURI = stillUsingMabFiles
+  ? "moz-abmdbdirectory://history.mab"
+  : "jsaddrbook://history.sqlite";
 var kAllDirectoryRoot = "moz-abdirectory://";
 var kPABDirectory = 2; // defined in nsDirPrefs.h
 
 function abNameOnLoad() {
   // Get the document elements.
   gOkButton = document.documentElement.getButton("accept");
   gNameInput = document.getElementById("name");
 
--- a/mailnews/addrbook/jsaddrbook/AddrBookCard.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookCard.jsm
@@ -19,18 +19,23 @@ ChromeUtils.defineModuleGetter(
  * Prototype for nsIAbCard objects that are not mailing lists.
  *
  * @implements {nsIAbItem}
  * @implements {nsIAbCard}
  */
 function AddrBookCard() {
   this._directoryId = "";
   this._localId = "";
-  this._properties = new Map();
+  this._properties = new Map([
+    ["PreferMailFormat", Ci.nsIAbPreferMailFormat.unknown],
+    ["PopularityIndex", 0],
+    ["LastModifiedDate", 0],
+  ]);
 }
+
 AddrBookCard.prototype = {
   QueryInterface: ChromeUtils.generateQI([Ci.nsIAbCard]),
   classID: Components.ID("{1143991d-31cd-4ea6-9c97-c587d990d724}"),
 
   /* nsIAbItem */
 
   get uuid() {
     return MailServices.ab.generateUUID(this._directoryId, this._localId);
@@ -136,51 +141,72 @@ AddrBookCard.prototype = {
   },
   set primaryEmail(value) {
     this.setProperty("PrimaryEmail", value);
   },
   get isMailList() {
     return false;
   },
   get mailListURI() {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    return "";
   },
 
   getProperty(name, defaultValue) {
     if (this._properties.has(name)) {
       return this._properties.get(name);
     }
     return defaultValue;
   },
   getPropertyAsAString(name) {
+    if (!this._properties.has(name)) {
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
     return this.getProperty(name);
   },
   getPropertyAsAUTF8String(name) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    if (!this._properties.has(name)) {
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
+    return this.getProperty(name);
   },
   getPropertyAsUint32(name) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    let value = this.getProperty(name);
+    if (isNaN(parseInt(value, 10))) {
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
+    }
+    return value;
   },
   getPropertyAsBool(name) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    let value = this.getProperty(name);
+    switch (value) {
+      case false:
+      case 0:
+      case "0":
+        return false;
+      case true:
+      case 1:
+      case "1":
+        return true;
+    }
+    throw Cr.NS_ERROR_NOT_AVAILABLE;
   },
   setProperty(name, value) {
     this._properties.set(name, value);
   },
   setPropertyAsAString(name, value) {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
   setPropertyAsAUTF8String(name, value) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    this.setProperty(name, value);
   },
   setPropertyAsUint32(name, value) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    this.setProperty(name, value);
   },
   setPropertyAsBool(name, value) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    this.setProperty(name, value ? 1 : 0);
   },
   deleteProperty(name) {
     this._properties.delete(name);
   },
   hasEmailAddress(emailAddress) {
     if (this._properties.get("PrimaryEmail") == emailAddress) {
       return true;
     }
--- a/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
@@ -100,17 +100,22 @@ AddrBookDirectory.prototype = {
             child.length - ".filename".length
           );
           break;
         }
       }
       if (!this.dirPrefId) {
         throw Cr.NS_ERROR_UNEXPECTED;
       }
-      this.UID;
+      // Make sure we always have a file. If a file is not created, the
+      // filename may be accidentally reused.
+      let file = FileUtils.getFile("ProfD", [filename]);
+      if (!file.exists()) {
+        file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+      }
     }
   },
 };
 
 // Keep track of all database connections, and close them at shutdown, since
 // nothing else ever tells us to close them.
 
 var connections = new Map();
@@ -120,21 +125,53 @@ var closeObserver = {
       connection.close();
     }
     connections.clear();
   },
 };
 Services.obs.addObserver(closeObserver, "addrbook-reload");
 Services.obs.addObserver(closeObserver, "quit-application");
 
-function closeConnectionTo(path) {
-  let connection = connections.get(path);
+/**
+ * Opens an SQLite connection to `file`, caches the connection, and upgrades
+ * the database schema if necessary.
+ */
+function openConnectionTo(file) {
+  let connection = connections.get(file.path);
+  if (!connection) {
+    connection = Services.storage.openDatabase(file);
+    if (connection.schemaVersion == 0) {
+      connection.executeSimpleSQL("PRAGMA journal_mode=WAL");
+      connection.executeSimpleSQL(
+        "CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER)"
+      );
+      connection.executeSimpleSQL(
+        "CREATE TABLE properties (card TEXT, name TEXT, value TEXT)"
+      );
+      connection.executeSimpleSQL(
+        "CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT)"
+      );
+      connection.executeSimpleSQL(
+        "CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card))"
+      );
+      connection.schemaVersion = 1;
+    }
+    connections.set(file.path, connection);
+  }
+  return connection;
+}
+
+/**
+ * Closes the SQLite connection to `file` and removes it from the cache.
+ */
+function closeConnectionTo(file) {
+  let connection = connections.get(file.path);
   if (connection) {
     connection.close();
-    connections.delete(path);
+    connections.delete(file.path);
   }
 }
 
 /**
  * Prototype for nsIAbDirectory objects that aren't mailing lists.
  *
  * @implements {nsIAbCollection}
  * @implements {nsIAbDirectory}
@@ -145,37 +182,17 @@ var bookPrototype = {
   get _prefBranch() {
     if (!this.dirPrefId) {
       throw Cr.NS_ERROR_NOT_AVAILABLE;
     }
     return Services.prefs.getBranch(`${this.dirPrefId}.`);
   },
   get _dbConnection() {
     let file = FileUtils.getFile("ProfD", [this.fileName]);
-    let connection = connections.get(file.path);
-    if (!connection) {
-      connection = Services.storage.openDatabase(file);
-      if (connection.schemaVersion == 0) {
-        connection.executeSimpleSQL("PRAGMA journal_mode=WAL");
-        connection.executeSimpleSQL(
-          "CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER)"
-        );
-        connection.executeSimpleSQL(
-          "CREATE TABLE properties (card TEXT, name TEXT, value TEXT)"
-        );
-        connection.executeSimpleSQL(
-          "CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT)"
-        );
-        connection.executeSimpleSQL(
-          "CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card))"
-        );
-        connection.schemaVersion = 1;
-      }
-      connections.set(file.path, connection);
-    }
+    let connection = openConnectionTo(file);
 
     delete this._dbConnection;
     Object.defineProperty(this, "_dbConnection", {
       enumerable: true,
       value: connection,
       writable: false,
     });
     return connection;
@@ -209,40 +226,42 @@ var bookPrototype = {
       });
     }
     cardStatement.finalize();
     return results;
   },
 
   _getNextCardId() {
     if (this._nextCardId === null) {
-      let value = 1;
+      let value = 0;
       let selectStatement = this._dbConnection.createStatement(
         "SELECT MAX(localId) AS localId FROM cards"
       );
       if (selectStatement.executeStep()) {
-        value = selectStatement.row.localId + 1;
+        value = selectStatement.row.localId;
       }
       this._nextCardId = value;
       selectStatement.finalize();
     }
+    this._nextCardId++;
     return this._nextCardId.toString();
   },
   _getNextListId() {
     if (this._nextListId === null) {
-      let value = 1;
+      let value = 0;
       let selectStatement = this._dbConnection.createStatement(
         "SELECT MAX(localId) AS localId FROM lists"
       );
       if (selectStatement.executeStep()) {
-        value = selectStatement.row.localId + 1;
+        value = selectStatement.row.localId;
       }
       this._nextListId = value;
       selectStatement.finalize();
     }
+    this._nextListId++;
     return this._nextListId.toString();
   },
   _getCard({ uid, localId = null }) {
     let card = new AddrBookCard();
     card.directoryId = this.uuid;
     card._uid = uid;
     card.localId = localId;
     card._properties = this._loadCardProperties(uid);
@@ -316,21 +335,22 @@ var bookPrototype = {
   },
   getCardFromProperty(property, value, caseSensitive) {
     let sql = caseSensitive
       ? "SELECT card FROM properties WHERE name = :name AND value = :value LIMIT 1"
       : "SELECT card FROM properties WHERE name = :name AND LOWER(value) = LOWER(:value) LIMIT 1";
     let selectStatement = this._dbConnection.createStatement(sql);
     selectStatement.params.name = property;
     selectStatement.params.value = value;
+    let result = null;
     if (selectStatement.executeStep()) {
-      return this._getCard({ uid: selectStatement.row.card });
+      result = this._getCard({ uid: selectStatement.row.card });
     }
     selectStatement.finalize();
-    return null;
+    return result;
   },
   getCardsFromProperty(property, value, caseSensitive) {
     let sql = caseSensitive
       ? "SELECT card FROM properties WHERE name = :name AND value = :value"
       : "SELECT card FROM properties WHERE name = :name AND LOWER(value) = LOWER(:value)";
     let selectStatement = this._dbConnection.createStatement(sql);
     selectStatement.params.name = property;
     selectStatement.params.value = value;
@@ -343,21 +363,21 @@ var bookPrototype = {
   },
 
   /* nsIAbDirectory */
 
   get propertiesChromeURI() {
     return "chrome://messenger/content/addressbook/abAddressBookNameDialog.xul";
   },
   get dirName() {
-    return this._prefBranch.getStringPref("description", "");
+    return this.getLocalizedStringValue("description", "");
   },
   set dirName(value) {
     let oldValue = this.dirName;
-    this._prefBranch.setStringPref("description", value);
+    this.setLocalizedStringValue("description", value);
     MailServices.ab.notifyItemPropertyChanged(this, "DirName", oldValue, value);
   },
   get dirType() {
     return 101;
   },
   get fileName() {
     if (!this._fileName) {
       this._fileName = this._prefBranch.getStringPref("filename", "");
@@ -568,17 +588,17 @@ var bookPrototype = {
   },
   hasCard(card) {
     return this._lists.has(card.UID) || this._cards.has(card.UID);
   },
   hasDirectory(dir) {
     return this._lists.has(dir.UID);
   },
   hasMailListWithName(name) {
-    for (let list of this._lists) {
+    for (let list of this._lists.values()) {
       if (list.name == name) {
         return true;
       }
     }
     return false;
   },
   addCard(card) {
     return this.dropCard(card, false);
@@ -607,41 +627,35 @@ var bookPrototype = {
   deleteCards(cards) {
     if (cards === null) {
       throw Cr.NS_ERROR_INVALID_POINTER;
     }
 
     let deleteCardStatement = this._dbConnection.createStatement(
       "DELETE FROM cards WHERE uid = :uid"
     );
-    let selectListCardStatement = this._dbConnection.createStatement(
-      "SELECT list FROM list_cards WHERE card = :card"
-    );
     for (let card of cards.enumerate(Ci.nsIAbCard)) {
       deleteCardStatement.params.uid = card.UID;
       deleteCardStatement.execute();
       deleteCardStatement.reset();
-      MailServices.ab.notifyDirectoryItemDeleted(this, card);
-
-      selectListCardStatement.params.card = card.UID;
-      while (selectListCardStatement.executeStep()) {
-        let list = new AddrBookMailingList(
-          selectListCardStatement.row.list,
-          this
-        );
-        list.asDirectory.deleteCards(toXPCOMArray([card], Ci.nsIMutableArray));
-      }
     }
-
     this._dbConnection.executeSimpleSQL(
       "DELETE FROM properties WHERE card NOT IN (SELECT DISTINCT uid FROM cards)"
     );
+    for (let card of cards.enumerate(Ci.nsIAbCard)) {
+      MailServices.ab.notifyDirectoryItemDeleted(this, card);
+    }
+
+    // We could just delete all non-existent cards from list_cards, but a
+    // notification should be fired for each one. Let the list handle that.
+    for (let list of this.childNodes) {
+      list.deleteCards(cards);
+    }
 
     deleteCardStatement.finalize();
-    selectListCardStatement.finalize();
   },
   dropCard(card, needToCopyCard) {
     let newCard = new AddrBookCard();
     newCard.directoryId = this.uuid;
     newCard.localId = this._getNextCardId().toString();
     newCard._uid = needToCopyCard || !card.UID ? newUID() : card.UID;
 
     let insertStatement = this._dbConnection.createStatement(
@@ -709,23 +723,31 @@ var bookPrototype = {
   getStringValue(name, defaultValue) {
     return this._prefBranch.getStringPref(name, defaultValue);
   },
   getLocalizedStringValue(name, defaultValue) {
     if (this._prefBranch.getPrefType(name) == Ci.nsIPrefBranch.PREF_INVALID) {
       return defaultValue;
     }
     return this._prefBranch.getComplexValue(name, Ci.nsIPrefLocalizedString)
-      .value;
+      .data;
   },
   setIntValue(name, value) {
     this._prefBranch.setIntPref(name, value);
   },
   setBoolValue(name, value) {
     this._prefBranch.setBoolPref(name, value);
   },
   setStringValue(name, value) {
     this._prefBranch.setStringPref(name, value);
   },
   setLocalizedStringValue(name, value) {
-    this._prefBranch.setComplexValue(name, Ci.nsIPrefLocalizedString, value);
+    let valueLocal = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
+      Ci.nsIPrefLocalizedString
+    );
+    valueLocal.data = value;
+    this._prefBranch.setComplexValue(
+      name,
+      Ci.nsIPrefLocalizedString,
+      valueLocal
+    );
   },
 };
--- a/mailnews/addrbook/jsaddrbook/AddrBookFactory.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookFactory.jsm
@@ -56,13 +56,13 @@ AddrBookFactory.prototype = {
           yield this.getNext();
         }
       },
     };
   },
   deleteDirectory(directory) {
     let file = FileUtils.getFile("ProfD", [directory.fileName]);
     if (file.exists()) {
-      closeConnectionTo(file.path);
+      closeConnectionTo(file);
       file.remove(false);
     }
   },
 };
--- a/mailnews/addrbook/jsaddrbook/AddrBookMailingList.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookMailingList.jsm
@@ -1,16 +1,21 @@
 /* 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.EXPORTED_SYMBOLS = ["AddrBookMailingList"];
 
 ChromeUtils.defineModuleGetter(
   this,
+  "fixIterator",
+  "resource:///modules/iteratorUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
   "MailServices",
   "resource:///modules/MailServices.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "Services",
   "resource://gre/modules/Services.jsm"
 );
@@ -133,16 +138,19 @@ AddrBookMailingList.prototype = {
         selectStatement.finalize();
         return toXPCOMArray(results, Ci.nsIMutableArray);
       },
 
       addCard(card) {
         if (!card.primaryEmail) {
           return card;
         }
+        if (!self._parent.hasCard(card)) {
+          self._parent.addCard(card);
+        }
         let insertStatement = self._parent._dbConnection.createStatement(
           "REPLACE INTO list_cards (list, card) VALUES (:list, :card)"
         );
         insertStatement.params.list = self._uid;
         insertStatement.params.card = card.UID;
         insertStatement.execute();
         MailServices.ab.notifyItemPropertyChanged(card, null, null, null);
         MailServices.ab.notifyItemPropertyChanged(card, null, null, null);
@@ -151,27 +159,29 @@ AddrBookMailingList.prototype = {
         // Services.obs.notifyObservers(card, "addrbook-list-member-added", self._uid);
         insertStatement.finalize();
         return card;
       },
       deleteCards(cards) {
         let deleteCardStatement = self._parent._dbConnection.createStatement(
           "DELETE FROM list_cards WHERE list = :list AND card = :card"
         );
-        for (let card of cards.enumerate()) {
+        for (let card of fixIterator(cards, Ci.nsIAbCard)) {
           deleteCardStatement.params.list = self._uid;
           deleteCardStatement.params.card = card.UID;
           deleteCardStatement.execute();
+          if (self._parent._dbConnection.affectedRows) {
+            MailServices.ab.notifyDirectoryItemDeleted(this, card);
+            Services.obs.notifyObservers(
+              card,
+              "addrbook-list-member-removed",
+              self._uid
+            );
+          }
           deleteCardStatement.reset();
-          MailServices.ab.notifyDirectoryItemDeleted(this, card);
-          Services.obs.notifyObservers(
-            card,
-            "addrbook-list-member-removed",
-            self._uid
-          );
         }
         deleteCardStatement.finalize();
       },
       dropCard(card, needToCopyCard) {
         if (needToCopyCard) {
           card = this._parent.dropCard(card, true);
         }
         this.addCard(card);
@@ -201,16 +211,22 @@ AddrBookMailingList.prototype = {
       },
       get isMailList() {
         return true;
       },
       get mailListURI() {
         return `${self._parent.URI}/MailList${self._localId}`;
       },
 
+      get directoryId() {
+        return self._parent.uuid;
+      },
+      get localId() {
+        return self._localId;
+      },
       get displayName() {
         return self._name;
       },
       set displayName(value) {
         self._name = value;
       },
 
       generateName(generateFormat) {
--- a/mailnews/addrbook/public/nsIAbDirectory.idl
+++ b/mailnews/addrbook/public/nsIAbDirectory.idl
@@ -12,25 +12,27 @@ interface nsIMutableArray;
 
 /* 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://"
 
-#define kPersonalAddressbook       "abook.mab"
-#define kPersonalAddressbookUri    "moz-abmdbdirectory://abook.mab"
-#define kCollectedAddressbook      "history.mab"
-#define kCollectedAddressbookUri   "moz-abmdbdirectory://history.mab"
+#define kPersonalAddressbook       "abook.sqlite"
+#define kPersonalAddressbookUri    "jsaddrbook://abook.sqlite"
+#define kCollectedAddressbook      "history.sqlite"
+#define kCollectedAddressbookUri   "jsaddrbook://history.sqlite"
 
 #define kABFileName_PreviousSuffix ".na2" /* final v2 address book format */
 #define kABFileName_PreviousSuffixLen 4
 #define kABFileName_CurrentSuffix  ".mab" /* v3 address book extension */
 
+#define kMDBAddressBook            "abook.mab"
+
 #define kJSDirectoryRoot           "jsaddrbook://"
 #define kJSAddressBook             "abook.sqlite"
 %}
 
 /**
  * A top-level address book directory.
  *
  * Please note that in order to be properly instantiated by nsIAbManager, every
--- a/mailnews/addrbook/src/nsAbAddressCollector.cpp
+++ b/mailnews/addrbook/src/nsAbAddressCollector.cpp
@@ -34,17 +34,18 @@ nsAbAddressCollector::~nsAbAddressCollec
     pPrefBranchInt->RemoveObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this);
 }
 
 /**
  * Returns the first card found with the specified email address. This
  * returns an already addrefed pointer to the card if the card is found.
  */
 already_AddRefed<nsIAbCard> nsAbAddressCollector::GetCardForAddress(
-    const nsACString &aEmailAddress, nsIAbDirectory **aDirectory) {
+    const char *aProperty, const nsACString &aEmailAddress,
+    nsIAbDirectory **aDirectory) {
   nsresult rv;
   nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   rv = abManager->GetDirectories(getter_AddRefs(enumerator));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
@@ -56,18 +57,18 @@ already_AddRefed<nsIAbCard> nsAbAddressC
     rv = enumerator->GetNext(getter_AddRefs(supports));
     NS_ENSURE_SUCCESS(rv, nullptr);
 
     directory = do_QueryInterface(supports, &rv);
     if (NS_FAILED(rv)) continue;
 
     // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here,
     // so just catch the value and continue.
-    if (NS_FAILED(directory->CardForEmailAddress(aEmailAddress,
-                                                 getter_AddRefs(result)))) {
+    if (NS_FAILED(directory->GetCardFromProperty(
+            aProperty, aEmailAddress, false, getter_AddRefs(result)))) {
       continue;
     }
 
     if (result) {
       if (aDirectory) directory.forget(aDirectory);
       return result.forget();
     }
   }
@@ -106,20 +107,29 @@ nsAbAddressCollector::CollectSingleAddre
                                            bool aCreateCard,
                                            uint32_t aSendFormat,
                                            bool aSkipCheckExisting) {
   if (!mDirectory) return NS_OK;
 
   nsresult rv;
 
   nsCOMPtr<nsIAbDirectory> originDirectory;
-  nsCOMPtr<nsIAbCard> card =
-      (!aSkipCheckExisting)
-          ? GetCardForAddress(aEmail, getter_AddRefs(originDirectory))
-          : nullptr;
+  nsCOMPtr<nsIAbCard> card;
+  if (!aSkipCheckExisting) {
+    card = GetCardForAddress(kPriEmailProperty, aEmail,
+                             getter_AddRefs(originDirectory));
+
+    // If a card has aEmail, but it's the secondary address, we don't want to
+    // update any properties, so just return.
+    if (!card) {
+      card = GetCardForAddress(k2ndEmailProperty, aEmail,
+                               getter_AddRefs(originDirectory));
+      if (card) return NS_OK;
+    }
+  }
 
   if (!card && (aCreateCard || aSkipCheckExisting)) {
     card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv);
     if (NS_SUCCEEDED(rv) && card) {
       // Set up the fields for the new card.
       SetNamesForCard(card, aDisplayName);
       AutoCollectScreenName(card, aEmail);
 
--- a/mailnews/addrbook/src/nsAbAddressCollector.h
+++ b/mailnews/addrbook/src/nsAbAddressCollector.h
@@ -22,17 +22,18 @@ class nsAbAddressCollector : public nsIA
   NS_DECL_ISUPPORTS
   NS_DECL_NSIABADDRESSCOLLECTOR
   NS_DECL_NSIOBSERVER
 
   nsresult Init();
 
  private:
   virtual ~nsAbAddressCollector();
-  already_AddRefed<nsIAbCard> GetCardForAddress(const nsACString &aEmailAddress,
+  already_AddRefed<nsIAbCard> GetCardForAddress(const char *aProperty,
+                                                const nsACString &aEmailAddress,
                                                 nsIAbDirectory **aDirectory);
   void AutoCollectScreenName(nsIAbCard *aCard, const nsACString &aEmail);
   bool SetNamesForCard(nsIAbCard *aSenderCard, const nsACString &aFullName);
   void SplitFullName(const nsCString &aFullName, nsCString &aFirstName,
                      nsCString &aLastName);
   void SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch);
   nsCOMPtr<nsIAbDirectory> mDirectory;
   nsCString mABURI;
--- a/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
+++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp
@@ -175,40 +175,40 @@ nsresult nsAddbookProtocolHandler::Gener
     nsIAddbookUrl *addbookUrl, nsString &aOutput) {
   NS_ENSURE_ARG_POINTER(addbookUrl);
 
   nsAutoCString uri;
   nsresult rv = addbookUrl->GetPathQueryRef(uri);
   NS_ENSURE_SUCCESS(rv, rv);
 
   /* turn
-   "//moz-abmdbdirectory/abook.mab?action=print"
-   into "moz-abmdbdirectory://abook.mab"
+   "//jsaddrbook/abook.sqlite?action=print"
+   into "jsaddrbook://abook.sqlite"
   */
 
   /* step 1:
-   turn "//moz-abmdbdirectory/abook.mab?action=print"
-   into "moz-abmdbdirectory/abook.mab?action=print"
+   turn "//jsaddrbook/abook.sqlite?action=print"
+   into "jsaddrbook/abook.sqlite?action=print"
    */
   if (uri[0] != '/' && uri[1] != '/') return NS_ERROR_UNEXPECTED;
 
   uri.Cut(0, 2);
 
   /* step 2:
-   turn "moz-abmdbdirectory/abook.mab?action=print"
-   into "moz-abmdbdirectory/abook.mab"
+   turn "jsaddrbook/abook.sqlite?action=print"
+   into "jsaddrbook/abook.sqlite"
    */
   int32_t pos = uri.Find("?action=print");
   if (pos == -1) return NS_ERROR_UNEXPECTED;
 
   uri.SetLength(pos);
 
   /* step 2:
-   turn "moz-abmdbdirectory/abook.mab"
-   into "moz-abmdbdirectory://abook.mab"
+   turn "jsaddrbook/abook.sqlite"
+   into "jsaddrbook://abook.sqlite"
    */
   pos = uri.FindChar('/');
   if (pos == -1) return NS_ERROR_UNEXPECTED;
 
   uri.Insert('/', pos);
   uri.Insert(':', pos);
 
   nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv));
--- a/mailnews/addrbook/src/nsDirPrefs.cpp
+++ b/mailnews/addrbook/src/nsDirPrefs.cpp
@@ -257,17 +257,17 @@ nsresult DIR_AddNewAddressBook(const nsA
   if (dir_ServerList) {
     server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName));
     server->position =
         kDefaultPosition;  // don't set position so alphabetic sort will happen.
 
     if (!fileName.IsEmpty())
       server->fileName = ToNewCString(fileName);
     else if (dirType == PABDirectory)
-      DIR_SetFileName(&server->fileName, kPersonalAddressbook);
+      DIR_SetFileName(&server->fileName, kMDBAddressBook);
     else if (dirType == LDAPDirectory)
       DIR_SetFileName(&server->fileName, kMainLdapAddressBook);
     else if (dirType == JSDirectory)
       DIR_SetFileName(&server->fileName, kJSAddressBook);
 
     if (dirType != PABDirectory) {
       if (!uri.IsEmpty()) server->uri = ToNewCString(uri);
     }
@@ -582,18 +582,18 @@ nsresult DIR_DeleteServerFromList(DIR_Se
 
   if (NS_SUCCEEDED(rv)) {
     // close the database, as long as it isn't the special ones
     // (personal addressbook and collected addressbook)
     // which can never be deleted.  There was a bug where we would slap in
     // "abook.mab" as the file name for LDAP directories, which would cause a
     // crash on delete of LDAP directories.  this is just extra protection.
     if (server->fileName && server->dirType != JSDirectory &&
-        strcmp(server->fileName, kPersonalAddressbook) &&
-        strcmp(server->fileName, kCollectedAddressbook)) {
+        strcmp(server->fileName, "abook.mab") &&
+        strcmp(server->fileName, "history.mab")) {
       nsCOMPtr<nsIAddrDatabase> database;
 
       rv = dbPath->AppendNative(nsDependentCString(server->fileName));
       NS_ENSURE_SUCCESS(rv, rv);
 
       // close file before delete it
       nsCOMPtr<nsIAddrDatabase> addrDBFactory =
           do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv);
@@ -907,17 +907,17 @@ void DIR_SetServerFileName(DIR_Server *s
 
   if (server && (!server->fileName || !(*server->fileName))) {
     PR_FREEIF(server->fileName);  // might be one byte empty string.
     /* make sure we have a pref name...*/
     if (!server->prefName || !*server->prefName)
       server->prefName = dir_CreateServerPrefName(server);
 
     /* set default personal address book file name*/
-    if ((server->position == 1) && (server->dirType == PABDirectory))
+    if ((server->position == 1) && (server->dirType == JSDirectory))
       server->fileName = strdup(kPersonalAddressbook);
     else {
       /* now use the pref name as the file name since we know the pref name
          will be unique */
       prefName = server->prefName;
       if (prefName && *prefName) {
         /* extract just the pref name part and not the ldap tree name portion
          * from the string */
@@ -932,22 +932,25 @@ void DIR_SetServerFileName(DIR_Server *s
           PR_Free(tempName);
         }
       }
     }
 
     if (!server->fileName || !*server->fileName) /* when all else has failed,
                                                     generate a default name */
     {
-      if (server->dirType == LDAPDirectory)
+      if (server->dirType == PABDirectory) {
+        DIR_SetFileName(&(server->fileName), kMDBAddressBook);
+      } else if (server->dirType == LDAPDirectory) {
         DIR_SetFileName(
             &(server->fileName),
             kMainLdapAddressBook); /* generates file name with an ldap prefix */
-      else
+      } else {
         DIR_SetFileName(&(server->fileName), kPersonalAddressbook);
+      }
     }
   }
 }
 
 static char *dir_CreateServerPrefName(DIR_Server *server) {
   /* we are going to try to be smart in how we generate our server
      pref name. We'll try to convert the description into a pref name
      and then verify that it is unique. If it is unique then use it... */
--- a/mailnews/addrbook/test/unit/data/cardForEmail.sql
+++ b/mailnews/addrbook/test/unit/data/cardForEmail.sql
@@ -1,8 +1,9 @@
+-- Address book data for use in various tests.
 PRAGMA user_version = 1;
 
 CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
 CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
 CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
 CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
 
 INSERT INTO cards (uid, localId) VALUES
--- a/mailnews/addrbook/test/unit/data/collect.sql
+++ b/mailnews/addrbook/test/unit/data/collect.sql
@@ -1,8 +1,9 @@
+-- Collection address book for use in test_collection_2.js.
 PRAGMA user_version = 1;
 
 CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
 CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
 CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
 CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
 
 INSERT INTO cards (uid, localId) VALUES
--- a/mailnews/addrbook/test/unit/head_addrbook.js
+++ b/mailnews/addrbook/test/unit/head_addrbook.js
@@ -3,22 +3,64 @@ var { MailServices } = ChromeUtils.impor
   "resource:///modules/MailServices.jsm"
 );
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 var CC = Components.Constructor;
 
+Services.prefs.setIntPref("ldap_2.servers.history.dirType", 2);
+Services.prefs.setStringPref("ldap_2.servers.history.filename", "history.mab");
+Services.prefs.setIntPref("ldap_2.servers.pab.dirType", 2);
+Services.prefs.setStringPref("ldap_2.servers.pab.filename", "abook.mab");
+Services.prefs.setIntPref("mail.addr_book.newDirType", 2);
+Services.prefs.setStringPref(
+  "mail.collect_addressbook",
+  "moz-abmdbdirectory://history.mab"
+);
+Services.prefs.setStringPref(
+  "mail.server.default.whiteListAbURI",
+  "moz-abmdbdirectory://abook.mab"
+);
+
 // Ensure the profile directory is set up
 do_get_profile();
 
-// Import the required setup scripts.
-/* import-globals-from ../../../test/resources/abSetup.js */
-load("../../../resources/abSetup.js");
+// Personal Address Book configuration items.
+var kPABData = {
+  URI: "moz-abmdbdirectory://abook.mab",
+  fileName: "abook.mab",
+  dirName: "Personal Address Book",
+  dirType: 2,
+  dirPrefID: "ldap_2.servers.pab",
+  readOnly: false,
+  position: 1,
+};
+
+// Collected Address Book configuration items.
+var kCABData = {
+  URI: "moz-abmdbdirectory://history.mab",
+  fileName: "history.mab",
+  dirName: "Collected Addresses",
+  dirType: 2,
+  dirPrefID: "ldap_2.servers.history",
+  readOnly: false,
+  position: 2,
+};
+
+// Windows (Outlook Express) Address Book deactivation. (Bug 448859)
+Services.prefs.deleteBranch("ldap_2.servers.oe.");
+
+// OSX Address Book deactivation (Bug 955842)
+Services.prefs.deleteBranch("ldap_2.servers.osx.");
+
+// This currently applies to all address books of local type.
+var kNormalPropertiesURI =
+  "chrome://messenger/content/addressbook/abAddressBookNameDialog.xul";
 
 function loadABFile(source, dest) {
   let testAB = do_get_file(`${source}.mab`);
   testAB.copyTo(do_get_profile(), dest);
 }
 
 registerCleanupFunction(function() {
   load("../../../resources/mailShutdown.js");
--- a/mailnews/addrbook/test/unit/head_jsaddrbook.js
+++ b/mailnews/addrbook/test/unit/head_jsaddrbook.js
@@ -4,80 +4,15 @@ var { MailServices } = ChromeUtils.impor
 );
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 // Ensure the profile directory is set up
 do_get_profile();
 
-// What follows is a copy of abSetup.js modified for tests with the
-// JS directory provider. We don't yet make the personal address book
-// and collected addresses book with this provider, so add two new
-// directories and pretend they are the original ones.
-
-MailServices.ab.newAddressBook("pab2", "jsaddrbook://abook.sqlite", 101);
-MailServices.ab.newAddressBook("history2", "jsaddrbook://abook-1.sqlite", 101);
-Services.prefs.setIntPref("ldap_2.servers.history2.position", 2);
-
-// Personal Address Book configuration items.
-var kPABData = {
-  URI: "jsaddrbook://abook.sqlite",
-  fileName: "abook.sqlite",
-  dirName: "pab2",
-  dirType: 101,
-  dirPrefID: "ldap_2.servers.pab2",
-  readOnly: false,
-  position: 1,
-};
-
-// Collected Address Book configuration items.
-var kCABData = {
-  URI: "jsaddrbook://abook-1.sqlite",
-  fileName: "abook-1.sqlite",
-  dirName: "history2",
-  dirType: 101,
-  dirPrefID: "ldap_2.servers.history2",
-  readOnly: false,
-  position: 2,
-};
-
-// Windows (Outlook Express) Address Book deactivation. (Bug 448859)
-Services.prefs.deleteBranch("ldap_2.servers.oe.");
-
-// OSX Address Book deactivation (Bug 955842)
-Services.prefs.deleteBranch("ldap_2.servers.osx.");
-
-// This currently applies to all address books of local type.
-var kNormalPropertiesURI =
-  "chrome://messenger/content/addressbook/abAddressBookNameDialog.xul";
-
-function loadABFile(source, dest) {
-  let sourceFile = do_get_file(`${source}.sql`);
-  let destFile = do_get_profile();
-  destFile.append(kPABData.fileName);
-
-  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
-    Ci.nsIFileInputStream
-  );
-  let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
-    Ci.nsIConverterInputStream
-  );
-  fstream.init(sourceFile, -1, 0, 0);
-  cstream.init(fstream, "UTF-8", 0, 0);
-
-  let data = "";
-  let read = 0;
-  do {
-    let str = {};
-    read = cstream.readString(0xffffffff, str);
-    data += str.value;
-  } while (read != 0);
-  cstream.close();
-
-  let conn = Services.storage.openDatabase(destFile);
-  conn.executeSimpleSQL(data);
-  conn.close();
-}
+// Import the required setup scripts.
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
 
 registerCleanupFunction(function() {
   load("../../../resources/mailShutdown.js");
 });
--- a/mailnews/addrbook/test/unit/test_jsaddrbook.js
+++ b/mailnews/addrbook/test/unit/test_jsaddrbook.js
@@ -1,16 +1,16 @@
 /* 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/. */
 
 "use strict";
 
 var DIR_TYPE = kPABData.dirType;
-var FILE_NAME = DIR_TYPE == 101 ? "abook-2.sqlite" : "abook-1.mab";
+var FILE_NAME = DIR_TYPE == 101 ? "abook-1.sqlite" : "abook-1.mab";
 var SCHEME = DIR_TYPE == 101 ? "jsaddrbook" : "moz-abmdbdirectory";
 
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
 );
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var book, contact, list, listCard;
@@ -83,18 +83,33 @@ var observer = {
           }
         }
       }
     }
   },
 };
 
 add_task(async function setUp() {
-  do_get_profile();
+  let profileDir = do_get_profile();
   observer.setUp();
+
+  let dirs = [...MailServices.ab.directories];
+  equal(dirs.length, 2);
+  equal(dirs[0].fileName, kPABData.fileName);
+  equal(dirs[1].fileName, kCABData.fileName);
+
+  // Check the PAB file was created.
+  let pabFile = profileDir.clone();
+  pabFile.append(kPABData.fileName);
+  ok(pabFile.exists());
+
+  // Check the CAB file was created.
+  let cabFile = profileDir.clone();
+  cabFile.append(kCABData.fileName);
+  ok(cabFile.exists());
 });
 
 add_task(async function createAddressBook() {
   let dirPrefId = MailServices.ab.newAddressBook("new book", "", DIR_TYPE);
   book = MailServices.ab.getDirectoryFromId(dirPrefId);
   observer.checkEvents(["onItemAdded", undefined, book]);
 
   // Check nsIAbItem properties.
@@ -127,17 +142,22 @@ add_task(async function createAddressBoo
     "new book"
   );
   equal(Services.prefs.getIntPref("ldap_2.servers.newbook.dirType"), DIR_TYPE);
   equal(
     Services.prefs.getStringPref("ldap_2.servers.newbook.filename"),
     FILE_NAME
   );
   equal(Services.prefs.getStringPref("ldap_2.servers.newbook.uid"), book.UID);
-  equal([...MailServices.ab.directories].length, DIR_TYPE == 101 ? 5 : 3);
+  equal([...MailServices.ab.directories].length, 3);
+
+  // Check the file was created.
+  let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  dbFile.append(FILE_NAME);
+  ok(dbFile.exists());
 });
 
 add_task(async function editAddressBook() {
   book.dirName = "updated book";
   observer.checkEvents([
     "onItemPropertyChanged",
     book,
     "DirName",
@@ -343,17 +363,17 @@ add_task(async function deleteAddressBoo
   observer.checkEvents(["onItemRemoved", undefined, book]);
   ok(!Services.prefs.prefHasUserValue("ldap_2.servers.newbook.dirType"));
   ok(!Services.prefs.prefHasUserValue("ldap_2.servers.newbook.description"));
   ok(!Services.prefs.prefHasUserValue("ldap_2.servers.newbook.filename"));
   ok(!Services.prefs.prefHasUserValue("ldap_2.servers.newbook.uid"));
   let dbFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
   dbFile.append(FILE_NAME);
   ok(!dbFile.exists());
-  equal([...MailServices.ab.directories].length, DIR_TYPE == 101 ? 4 : 2);
+  equal([...MailServices.ab.directories].length, 2);
   if (DIR_TYPE == 101) {
     throws(
       () => MailServices.ab.getDirectory(`${SCHEME}://${FILE_NAME}`),
       /.*/
     );
   }
 });
 
--- a/mailnews/addrbook/test/unit/test_nsAbManager2.js
+++ b/mailnews/addrbook/test/unit/test_nsAbManager2.js
@@ -118,20 +118,16 @@ function run_test() {
   MailServices.ab.addAddressBookListener(gAblAll, nsIAbListener.all);
 
   for (i = 0; i < numListenerOptions; ++i) {
     gAblSingle[i] = new abL();
     MailServices.ab.addAddressBookListener(gAblSingle[i], 1 << i);
   }
 
   var expectedABs = [kPABData.URI, kCABData.URI];
-  if (kPABData.dirType == 101) {
-    expectedABs.push("moz-abmdbdirectory://abook.mab");
-    expectedABs.push("moz-abmdbdirectory://history.mab");
-  }
 
   // Test - Check initial directories
 
   checkDirs(MailServices.ab.directories, expectedABs);
 
   // Test - Add a directory
 
   var newDirectory1 = addDirectory("testAb1");
--- a/mailnews/base/search/content/searchWidgets.js
+++ b/mailnews/base/search/content/searchWidgets.js
@@ -872,17 +872,17 @@
         // If the old internalOperator was IsntInAB or IsInAB, and the new internalOperator is
         // IsntInAB or IsInAB, noop because the search value was an ab type, and it still is.
         // Otherwise, switch to the ab picker and select the PAB.
         if (
           this.internalOperator != Ci.nsMsgSearchOp.IsntInAB &&
           this.internalOperator != Ci.nsMsgSearchOp.IsInAB
         ) {
           const abs = children[4].querySelector(
-            `[value="moz-abmdbdirectory://abook.mab"]`
+            `[value="moz-abmdbdirectory://abook.mab"], [value="jsaddrbook://abook.sqlite"]`
           );
           if (abs) {
             children[4].selectedItem = abs;
           }
           this.setAttribute("selectedIndex", "4");
         }
       } else if (
         this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
--- a/mailnews/base/src/nsMsgDBView.cpp
+++ b/mailnews/base/src/nsMsgDBView.cpp
@@ -286,19 +286,21 @@ static nsresult GetDisplayNameInAddressB
                                           getter_AddRefs(cardForAddress));
       // The card is found,so stop looping.
       if (NS_SUCCEEDED(rv) && cardForAddress) break;
     }
   }
 
   if (cardForAddress) {
     bool preferDisplayName = true;
-    cardForAddress->GetPropertyAsBool("PreferDisplayName", &preferDisplayName);
-
-    if (preferDisplayName) rv = cardForAddress->GetDisplayName(displayName);
+    rv = cardForAddress->GetPropertyAsBool("PreferDisplayName",
+                                           &preferDisplayName);
+
+    if (NS_FAILED(rv) || preferDisplayName)
+      rv = cardForAddress->GetDisplayName(displayName);
   }
 
   return rv;
 }
 
 /*
  * The unparsedString has following format:
  * "version|displayname"
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/data/remoteContent.sql
@@ -0,0 +1,41 @@
+-- Address book with remote content permissions for use in test_accountMigration.js.
+PRAGMA user_version = 1;
+
+CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
+CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
+CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
+CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
+
+INSERT INTO cards (uid, localId) VALUES
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 1),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 2),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 3);
+
+INSERT INTO properties (card, name, value) VALUES
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PrimaryEmail', 'no@test.invalid'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PhotoType', 'generic'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LowercasePrimaryEmail', 'no@test.invalid'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PopularityIndex', '0'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferMailFormat', '0'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LastModifiedDate', '0'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferDisplayName', '1'),
+  ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'AllowRemoteContent', '0'),
+
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PrimaryEmail', 'yes@test.invalid'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PhotoType', 'generic'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LowercasePrimaryEmail', 'yes@test.invalid'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PopularityIndex', '0'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferMailFormat', '0'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LastModifiedDate', '0'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferDisplayName', '1'),
+  ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'AllowRemoteContent', '0'),
+
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LastModifiedDate', '1397383824'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PrimaryEmail', 'yes@test.invalid'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PhotoType', 'generic'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'SecondEmail', 'yes2@test.invalid'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LowercasePrimaryEmail', 'yes@test.invalid'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PopularityIndex', '0'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferMailFormat', '0'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferDisplayName', '1'),
+  ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'AllowRemoteContent', '1');
--- a/mailnews/base/test/unit/test_accountMigration.js
+++ b/mailnews/base/test/unit/test_accountMigration.js
@@ -25,20 +25,17 @@ function run_test() {
   // Server2 has useSecAuth set to true, auth_login unset
   Services.prefs.setBoolPref("mail.server.server2.useSecAuth", true);
 
   Services.prefs.setCharPref(
     "mail.accountmanager.accounts",
     "account1,account2"
   );
 
-  let testAB = do_get_file("data/remoteContent.mab");
-
-  // Copy the file to the profile directory for a PAB.
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
+  loadABFile("data/remoteContent", kPABData.fileName);
 
   let uriAllowed = Services.io.newURI(
     "chrome://messenger/content/email=yes@test.invalid"
   );
   let uriAllowed2 = Services.io.newURI(
     "chrome://messenger/content/email=yes2@test.invalid"
   );
   let uriDisallowed = Services.io.newURI(
--- a/mailnews/base/test/unit/test_junkWhitelisting.js
+++ b/mailnews/base/test/unit/test_junkWhitelisting.js
@@ -34,21 +34,20 @@ var { MailServices } = ChromeUtils.impor
 var kDomainTest = 0;
 var kDomainExample = 1;
 
 var Files = ["../../../data/bugmail1", "../../../data/bugmail3"];
 
 var hdrs = [];
 
 function run_test() {
-  // Test setup - copy the data file into place
-  var testAB = do_get_file("../../../addrbook/test/unit/data/cardForEmail.mab");
-
-  // Copy the file to the profile directory for a PAB (this is the personal address book)
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
+  loadABFile(
+    "../../../addrbook/test/unit/data/cardForEmail",
+    kPABData.fileName
+  );
 
   do_test_pending();
 
   // kick off copying
   gPOP3Pump.files = Files;
   gPOP3Pump.onDone = continueTest;
   gPOP3Pump.run();
 }
--- a/mailnews/base/test/unit/test_searchAddressInAb.js
+++ b/mailnews/base/test/unit/test_searchAddressInAb.js
@@ -178,21 +178,20 @@ var Files = [
   "../../../data/bugmail7",
   "../../../data/bugmail8",
 ];
 
 function run_test() {
   // Setup local mail accounts.
   localAccountUtils.loadLocalMailAccount();
 
-  // Test setup - copy the data file into place
-  var testAB = do_get_file("../../../addrbook/test/unit/data/cardForEmail.mab");
-
-  // Copy the file to the profile directory for a PAB
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
+  loadABFile(
+    "../../../addrbook/test/unit/data/cardForEmail",
+    kPABData.fileName
+  );
 
   // test that validity table terms are valid
 
   // offline mail table
   testValidityTable(offlineMail, IsInAB, Sender, true);
   testValidityTable(offlineMail, IsInAB, To, true);
   testValidityTable(offlineMail, IsInAB, ToOrCC, true);
   testValidityTable(offlineMail, IsInAB, AllAddresses, true);
new file mode 100644
--- /dev/null
+++ b/mailnews/compose/test/unit/data/listexpansion.sql
@@ -0,0 +1,126 @@
+-- Address book with nested mailing lists for use in test_expandMailingLists.js.
+PRAGMA user_version = 1;
+
+CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
+CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
+CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
+CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
+
+INSERT INTO cards (uid, localId) VALUES
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 1), -- homer
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 2), -- marge
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 3), -- bart
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 4), -- lisa
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 5), -- maggie
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 6), --simpson
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 7), --marge
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', 8), --family
+  ('4808121d-ebad-4564-864d-8f1149aa053b', 9), --kids
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', 10), --parents
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 11), --older-kids
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 12), --bad-kids
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', 13); --bad-younger-kids
+
+INSERT INTO properties (card, name, value) VALUES
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PrimaryEmail', 'homer@example.com'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PhotoType', 'generic'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LowercasePrimaryEmail', 'homer@example.com'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'DisplayName', 'Simpson'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'LastModifiedDate', '1473722922'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PopularityIndex', '0'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferMailFormat', '0'),
+  ('813155c6-924d-4751-95d0-70d8e64f16bc', 'PreferDisplayName', '1'),
+
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'DisplayName', 'Marge'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PrimaryEmail', 'marge@example.com'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PhotoType', 'generic'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LowercasePrimaryEmail', 'marge@example.com'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'LastModifiedDate', '1473723020'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PopularityIndex', '0'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferMailFormat', '0'),
+  ('b2cc8395-d959-45e4-9516-17457adb16fa', 'PreferDisplayName', '1'),
+
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PhotoType', 'generic'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PopularityIndex', '0'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferMailFormat', '0'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PreferDisplayName', '1'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'DisplayName', 'Bart'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'PrimaryEmail', 'bart@foobar.invalid'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercasePrimaryEmail', 'bart@foobar.invalid'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'SecondEmail', 'bart@example.com'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LowercaseSecondEmail', 'bart@example.com'),
+  ('979f194e-49f2-4bbb-b364-598cdc6a7d11', 'LastModifiedDate', '1473716192'),
+
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PrimaryEmail', 'lisa@example.com'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PhotoType', 'generic'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LowercasePrimaryEmail', 'lisa@example.com'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'DisplayName', 'lisa@example.com'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PopularityIndex', '0'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferMailFormat', '0'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'LastModifiedDate', '0'),
+  ('4dd13a79-b70c-4b43-bdba-bacd4e977c1b', 'PreferDisplayName', '1'),
+
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'DisplayName', 'Maggie'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LastModifiedDate', '1473723047'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PrimaryEmail', 'maggie@example.com'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PhotoType', 'generic'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'LowercasePrimaryEmail', 'maggie@example.com'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PopularityIndex', '0'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferMailFormat', '0'),
+  ('c96402d7-1c7b-4242-a35c-b92c8ec9dfa2', 'PreferDisplayName', '1'),
+
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'DisplayName', 'simpson'),
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'PrimaryEmail', 'simpson'),
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'DisplayName', 'marge'),
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'PrimaryEmail', 'marge'),
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', 'DisplayName', 'family'),
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', 'PrimaryEmail', 'family'),
+  ('4808121d-ebad-4564-864d-8f1149aa053b', 'DisplayName', 'kids'),
+  ('4808121d-ebad-4564-864d-8f1149aa053b', 'PrimaryEmail', 'kids'),
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', 'DisplayName', 'parents'),
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', 'PrimaryEmail', 'parents'),
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'PrimaryEmail', 'older-kids'),
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 'DisplayName', 'older-kids'),
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'DisplayName', 'bad-kids'),
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 'PrimaryEmail', 'bad-kids'),
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', 'DisplayName', 'bad-younger-kids'),
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', 'PrimaryEmail', 'bad-younger-kids');
+
+INSERT INTO lists (uid, localId, name, nickName, description) VALUES
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 1, 'simpson', '', ''),
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 2, 'marge', '', 'marges own list'),
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', 3, 'family', '', ''),
+  ('4808121d-ebad-4564-864d-8f1149aa053b', 4, 'kids', '', ''),
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', 5, 'parents', '', ''),
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', 6, 'older-kids', '', ''),
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', 7, 'bad-kids', '', ''),
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', 8, 'bad-younger-kids', '', '');
+
+INSERT INTO list_cards (list, card) VALUES
+  -- simpson
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart
+  ('5ec12f1d-7ee9-403c-a617-48596dacbc18', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa
+  -- marge
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+  ('18204ef9-e4e3-4cd5-9981-604c69bbb9ee', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+  -- family
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents
+  ('ad305609-3535-4d51-8c96-cd82d93aed46', '4808121d-ebad-4564-864d-8f1149aa053b'), -- kids
+  -- parents
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', '813155c6-924d-4751-95d0-70d8e64f16bc'), -- homer
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', 'b2cc8395-d959-45e4-9516-17457adb16fa'), -- marge
+  ('4926ff7a-e929-475a-8aa8-2baac994390c', '4926ff7a-e929-475a-8aa8-2baac994390c'), -- parents
+  -- kids
+  ('4808121d-ebad-4564-864d-8f1149aa053b', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids
+  ('4808121d-ebad-4564-864d-8f1149aa053b', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie
+  -- older-kids
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '4dd13a79-b70c-4b43-bdba-bacd4e977c1b'), -- lisa
+  ('84fa4513-9b60-4379-ade7-1e4b48d67c84', '979f194e-49f2-4bbb-b364-598cdc6a7d11'), -- bart
+  -- bad-kids
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '84fa4513-9b60-4379-ade7-1e4b48d67c84'), -- older-kids
+  ('8e88b9a4-2500-48e0-bcea-b1fa4eab6b72', '34e60324-4fb6-4f10-ab1b-333b07680228'), -- bad-younger-kids
+  -- bad-younger-kids
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', 'c96402d7-1c7b-4242-a35c-b92c8ec9dfa2'), -- maggie
+  ('34e60324-4fb6-4f10-ab1b-333b07680228', '8e88b9a4-2500-48e0-bcea-b1fa4eab6b72'); -- bad-kids
--- a/mailnews/compose/test/unit/test_expandMailingLists.js
+++ b/mailnews/compose/test/unit/test_expandMailingLists.js
@@ -39,21 +39,17 @@ function checkPopulate(aTo, aCheckTo) {
 
   msgCompose.initialize(params);
 
   msgCompose.expandMailingLists();
   equal(fields.to, aCheckTo);
 }
 
 function run_test() {
-  // Test setup - copy the data files into place
-  let testAB = do_get_file("./data/listexpansion.mab");
-
-  // Copy the file to the profile directory for a PAB
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
+  loadABFile("data/listexpansion", kPABData.fileName);
 
   // XXX Getting all directories ensures we create all ABs because mailing
   // lists need help initialising themselves
   MailServices.ab.directories;
 
   // Test expansion of list with no description.
   checkPopulate(
     "simpson <simpson>",
--- a/mailnews/compose/test/unit/test_nsMsgCompose1.js
+++ b/mailnews/compose/test/unit/test_nsMsgCompose1.js
@@ -38,26 +38,18 @@ function checkPopulate(aTo, aCheckTo) {
   Assert.equal(addresses.length, checkEmails.length);
   for (let i = 0; i < addresses.length; i++) {
     Assert.equal(addresses[i].name, checkEmails[i].name);
     Assert.equal(addresses[i].email, checkEmails[i].email);
   }
 }
 
 function run_test() {
-  // Test setup - copy the data files into place
-  var testAB = do_get_file("../../../data/abLists1.mab");
-
-  // Copy the file to the profile directory for a PAB
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
-
-  testAB = do_get_file("../../../data/abLists2.mab");
-
-  // Copy the file to the profile directory for a CAB
-  testAB.copyTo(do_get_profile(), kCABData.fileName);
+  loadABFile("../../../data/abLists1", kPABData.fileName);
+  loadABFile("../../../data/abLists2", kCABData.fileName);
 
   // Test - Check we can initialize with fewest specified
   // parameters and don't fail/crash like we did in bug 411646.
 
   var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
     Ci.nsIMsgCompose
   );
 
--- a/mailnews/compose/test/unit/test_nsMsgCompose3.js
+++ b/mailnews/compose/test/unit/test_nsMsgCompose3.js
@@ -54,21 +54,17 @@ function checkPopulate(aTo, aCheckTo) {
   msgCompose.initialize(params);
 
   Assert.ok(!msgCompose.expandMailingLists());
 
   Assert.equal(fields.to, aCheckTo);
 }
 
 function run_test() {
-  // Test setup - copy the data files into place
-  let testAB = do_get_file("../../../data/tb2hexpopularity.mab");
-
-  // Copy the file to the profile directory for a PAB
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
+  loadABFile("../../../data/tb2hexpopularity", kPABData.fileName);
 
   // Check the popularity index on a couple of cards.
   let AB = MailServices.ab.getDirectory(kPABData.URI);
 
   for (let i = 0; i < TESTS.length; ++i) {
     let card = AB.cardForEmailAddress(TESTS[i].email);
     Assert.ok(!!card);
 
--- a/mailnews/compose/test/unit/test_nsMsgCompose4.js
+++ b/mailnews/compose/test/unit/test_nsMsgCompose4.js
@@ -39,26 +39,18 @@ function checkPopulate(
 
   msgCompose.initialize(params);
 
   msgCompose.expandMailingLists();
   Assert.equal(msgCompose.determineHTMLAction(aConvertible), aSendFormat);
 }
 
 function run_test() {
-  // Test setup - copy the data files into place
-  var testAB = do_get_file("../../../data/abLists1.mab");
-
-  // Copy the file to the profile directory for a PAB
-  testAB.copyTo(do_get_profile(), kPABData.fileName);
-
-  testAB = do_get_file("../../../data/abLists2.mab");
-
-  // Copy the file to the profile directory for a CAB
-  testAB.copyTo(do_get_profile(), kCABData.fileName);
+  loadABFile("../../../data/abLists1", kPABData.fileName);
+  loadABFile("../../../data/abLists2", kCABData.fileName);
 
   // Test - Check we can initialize with fewest specified
   // parameters and don't fail/crash like we did in bug 411646.
 
   var msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
     Ci.nsIMsgCompose
   );
 
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -317,24 +317,24 @@ pref("mapi.blind-send.enabled",         
 
 pref("offline.autoDetect",                  false); // automatically move the user offline or online based on the network connection
 
 pref("ldap_2.autoComplete.useDirectory", false);
 pref("ldap_2.autoComplete.directoryServer", "");
 
 pref("ldap_2.servers.pab.position", 1);
 pref("ldap_2.servers.pab.description", "chrome://messenger/locale/addressbook/addressBook.properties");
-pref("ldap_2.servers.pab.dirType", 2);
-pref("ldap_2.servers.pab.filename", "abook.mab");
+pref("ldap_2.servers.pab.dirType", 101);
+pref("ldap_2.servers.pab.filename", "abook.sqlite");
 pref("ldap_2.servers.pab.isOffline", false);
 
 pref("ldap_2.servers.history.position", 2);
 pref("ldap_2.servers.history.description", "chrome://messenger/locale/addressbook/addressBook.properties");
-pref("ldap_2.servers.history.dirType", 2);
-pref("ldap_2.servers.history.filename", "history.mab");
+pref("ldap_2.servers.history.dirType", 101);
+pref("ldap_2.servers.history.filename", "history.sqlite");
 pref("ldap_2.servers.history.isOffline", false);
 
 // default mapping of addressbook properties to ldap attributes
 pref("ldap_2.servers.default.attrmap.FirstName", "givenName");
 pref("ldap_2.servers.default.attrmap.LastName", "sn,surname");
 pref("ldap_2.servers.default.attrmap.DisplayName", "cn,commonname");
 pref("ldap_2.servers.default.attrmap.NickName", "mozillaNickname,xmozillanickname");
 pref("ldap_2.servers.default.attrmap.PrimaryEmail", "mail");
@@ -446,17 +446,17 @@ pref("mail.identity.default.headers", ""
 // by default, only collect addresses the user sends to (outgoing)
 // incoming is all spam anyways
 #ifdef MOZ_SUITE
 pref("mail.collect_email_address_incoming", false);
 pref("mail.collect_email_address_newsgroup", false);
 #endif
 pref("mail.collect_email_address_outgoing", true);
 // by default, use the Collected Addressbook for collection
-pref("mail.collect_addressbook", "moz-abmdbdirectory://history.mab");
+pref("mail.collect_addressbook", "jsaddrbook://history.sqlite");
 
 pref("mail.default_sendlater_uri", "mailbox://nobody@Local%20Folders/Unsent%20Messages");
 
 pref("mail.smtpservers", "");
 pref("mail.accountmanager.accounts", "");
 
 // Last used account key value
 pref("mail.account.lastKey", 0);
@@ -515,17 +515,17 @@ pref("mail.server.default.use_condstore"
 pref("mail.server.default.use_compress_deflate", true);
 // for spam
 pref("mail.server.default.spamLevel", 100); // 0 off, 100 on.  not doing bool since we might have real levels one day.
 pref("mail.server.default.moveOnSpam", false);
 pref("mail.server.default.moveTargetMode", 0); // 0 == "Junk" on server, 1 == specific folder
 pref("mail.server.default.spamActionTargetAccount", "");
 pref("mail.server.default.spamActionTargetFolder", "");
 pref("mail.server.default.useWhiteList", true);
-pref("mail.server.default.whiteListAbURI", "moz-abmdbdirectory://abook.mab"); // the Personal addressbook.
+pref("mail.server.default.whiteListAbURI", "jsaddrbook://abook.sqlite"); // the Personal addressbook.
 pref("mail.server.default.useServerFilter", false);
 pref("mail.server.default.serverFilterName", "SpamAssassin");
 pref("mail.server.default.serverFilterTrustFlags", 1); // 1 == trust positives, 2 == trust negatives, 3 == trust both
 pref("mail.server.default.purgeSpam", false);
 pref("mail.server.default.purgeSpamInterval", 14); // 14 days
 pref("mail.server.default.check_all_folders_for_new", false);
 // should we inhibit whitelisting of the email addresses for a server's identities?
 pref("mail.server.default.inhibitWhiteListingIdentityUser", true);
--- a/mailnews/test/data/abLists1.sql
+++ b/mailnews/test/data/abLists1.sql
@@ -1,8 +1,9 @@
+-- Address book data for use in various tests.
 PRAGMA user_version = 1;
 
 CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
 CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
 CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
 CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
 
 INSERT INTO cards (uid, localId) VALUES
@@ -53,9 +54,9 @@ INSERT INTO lists (uid, localId, name, n
   ('31c44c28-450f-44d6-ba39-71cae90fac21', 2, 'TestList2', '', ''),
   ('46cf4cbf-5945-43e4-a822-30c2f2969db9', 3, 'TestList3', '', '');
 
 INSERT INTO list_cards (list, card) VALUES
   ('98636844-ed9c-4ac1-98ac-de7989a93615', '0a64d642-7b51-4a84-be67-59b27ff2b528'),
   ('98636844-ed9c-4ac1-98ac-de7989a93615', 'ce1bd5ad-17e7-4a1b-a51e-fbce76556ebd'),
   ('98636844-ed9c-4ac1-98ac-de7989a93615', 'caaadb6c-425d-40e3-8f19-72546f6b01d8'),
   ('31c44c28-450f-44d6-ba39-71cae90fac21', '23acb230-f0d9-4348-a7be-1242cd579631'),
-  ('31c44c28-450f-44d6-ba39-71cae90fac21', '02cf43d5-e5b8-48b4-9546-1bb509cd998f');
+  ('46cf4cbf-5945-43e4-a822-30c2f2969db9', '02cf43d5-e5b8-48b4-9546-1bb509cd998f');
new file mode 100644
--- /dev/null
+++ b/mailnews/test/data/abLists2.sql
@@ -0,0 +1,62 @@
+-- Address book data for use in various tests.
+PRAGMA user_version = 1;
+
+CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
+CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
+CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
+CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
+
+INSERT INTO cards (uid, localId) VALUES
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 1),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 2),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 3),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 4),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 5);
+
+INSERT INTO properties (card, name, value) VALUES
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'PrimaryEmail', 'test1@com.invalid'),
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'LowercasePrimaryEmail', 'test1@com.invalid'),
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'PreferMailFormat', '0'),
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'PopularityIndex', '0'),
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'AllowRemoteContent', '0'),
+  ('420a2534-7e35-45e3-88b1-104e92608faa', 'LastModifiedDate', '0'),
+
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'PrimaryEmail', 'test2@com.invalid'),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'LowercasePrimaryEmail', 'test2@com.invalid'),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'PreferMailFormat', '0'),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'PopularityIndex', '0'),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'AllowRemoteContent', '0'),
+  ('3291e9a7-cbd9-4146-9c4e-e1afe5e25085', 'LastModifiedDate', '0'),
+
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'PrimaryEmail', 'test3@com.invalid'),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'LowercasePrimaryEmail', 'test3@com.invalid'),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'PreferMailFormat', '0'),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'PopularityIndex', '0'),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'AllowRemoteContent', '0'),
+  ('fcc46367-7081-487d-bbd3-f8f8e03e5262', 'LastModifiedDate', '0'),
+
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'PrimaryEmail', 'test4@com.invalid'),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'LowercasePrimaryEmail', 'test4@com.invalid'),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'PreferMailFormat', '1'),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'PopularityIndex', '0'),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'AllowRemoteContent', '0'),
+  ('e62f6ec2-8248-478e-8f6d-e31cdbeda4b8', 'LastModifiedDate', '0'),
+
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'PrimaryEmail', 'test5@com.invalid'),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'LowercasePrimaryEmail', 'test5@com.invalid'),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'PreferMailFormat', '2'),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'PopularityIndex', '0'),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'AllowRemoteContent', '0'),
+  ('4bc4f8c2-66d4-4421-a7b4-4be9d8be8614', 'LastModifiedDate', '0');
+
+INSERT INTO lists (uid, localId, name, nickName, description) VALUES
+  ('df79d3c0-5976-4279-851c-a8814f17ef30', 1, 'ListTest1', '', ''),
+  ('cad38149-925a-4159-8c34-20ac74ae7a17', 2, 'ListTest2', '', ''),
+  ('c069dd7a-408f-4440-9fc0-67643fbe5777', 3, 'ListTest3', '', '');
+
+INSERT INTO list_cards (list, card) VALUES
+  ('df79d3c0-5976-4279-851c-a8814f17ef30', '420a2534-7e35-45e3-88b1-104e92608faa'),
+  ('df79d3c0-5976-4279-851c-a8814f17ef30', '3291e9a7-cbd9-4146-9c4e-e1afe5e25085'),
+  ('df79d3c0-5976-4279-851c-a8814f17ef30', 'fcc46367-7081-487d-bbd3-f8f8e03e5262'),
+  ('cad38149-925a-4159-8c34-20ac74ae7a17', 'e62f6ec2-8248-478e-8f6d-e31cdbeda4b8'),
+  ('c069dd7a-408f-4440-9fc0-67643fbe5777', '4bc4f8c2-66d4-4421-a7b4-4be9d8be8614');
--- a/mailnews/test/data/tb2hexpopularity.sql
+++ b/mailnews/test/data/tb2hexpopularity.sql
@@ -1,8 +1,9 @@
+-- Address book data for use in various tests.
 PRAGMA user_version = 1;
 
 CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
 CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
 CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
 CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
 
 INSERT INTO cards (uid, localId) VALUES
--- a/mailnews/test/resources/abSetup.js
+++ b/mailnews/test/resources/abSetup.js
@@ -14,37 +14,75 @@
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 /**
  * General Configuration Data that applies to the address book.
  */
 
 // Personal Address Book configuration items.
 var kPABData = {
-  URI: "moz-abmdbdirectory://abook.mab",
-  fileName: "abook.mab",
+  URI: "jsaddrbook://abook.sqlite",
+  fileName: "abook.sqlite",
   dirName: "Personal Address Book",
-  dirType: 2,
+  dirType: 101,
   dirPrefID: "ldap_2.servers.pab",
   readOnly: false,
   position: 1,
 };
 
 // Collected Address Book configuration items.
 var kCABData = {
-  URI: "moz-abmdbdirectory://history.mab",
-  fileName: "history.mab",
+  URI: "jsaddrbook://history.sqlite",
+  fileName: "history.sqlite",
   dirName: "Collected Addresses",
-  dirType: 2,
+  dirType: 101,
   dirPrefID: "ldap_2.servers.history",
   readOnly: false,
   position: 2,
 };
 
 // Windows (Outlook Express) Address Book deactivation. (Bug 448859)
 Services.prefs.deleteBranch("ldap_2.servers.oe.");
 
 // OSX Address Book deactivation (Bug 955842)
 Services.prefs.deleteBranch("ldap_2.servers.osx.");
 
 // This currently applies to all address books of local type.
 var kNormalPropertiesURI =
   "chrome://messenger/content/addressbook/abAddressBookNameDialog.xul";
+
+/**
+ * Installs a pre-prepared address book file into the profile directory.
+ * This version is for JS/SQLite address books, if you create a new type,
+ * replace this function to test them.
+ *
+ * @param {String} source  Path to the source data, without extension
+ * @param {String} dest    Final file name in the profile, with extension
+ */
+function loadABFile(source, dest) {
+  let sourceFile = do_get_file(`${source}.sql`);
+  let destFile = do_get_profile();
+  destFile.append(dest);
+
+  info(`Creating ${destFile.path} from ${sourceFile.path}`);
+
+  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+    Ci.nsIFileInputStream
+  );
+  let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
+    Ci.nsIConverterInputStream
+  );
+  fstream.init(sourceFile, -1, 0, 0);
+  cstream.init(fstream, "UTF-8", 0, 0);
+
+  let data = "";
+  let read = 0;
+  do {
+    let str = {};
+    read = cstream.readString(0xffffffff, str);
+    data += str.value;
+  } while (read != 0);
+  cstream.close();
+
+  let conn = Services.storage.openDatabase(destFile);
+  conn.executeSimpleSQL(data);
+  conn.close();
+}