Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 12 Dec 2013 16:00:16 +0100
changeset 160237 800878f01d5dd099186be4b0bf60dee9d82bee98
parent 160236 f5bb944954a5dfa7d56b6ee531e83d2f978cc724 (current diff)
parent 160127 07e7a99841a64c59faf7ba1bd007e791c0c07b83 (diff)
child 160238 7bd725c5371ba209722dcfbf976f70dd3b3a2b16
push id25827
push userkwierso@gmail.com
push dateFri, 13 Dec 2013 03:13:04 +0000
treeherdermozilla-central@1bc33fa19b24 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
mobile/android/base/DoorHanger.java
mobile/android/base/SiteIdentityPopup.java
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "8a192fcf2927a866574996b4895426213e01a325", 
+    "revision": "5bfef5faac50d14e055f642a44ed2df8483fb2fe", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -52,13 +52,13 @@ MOZ_TIME_MANAGER=1
 
 MOZ_B2G_CERTDATA=1
 MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
 
 if test "$OS_TARGET" = "Android"; then
-MOZ_NUWA_PROCESS=0
+MOZ_NUWA_PROCESS=
 fi
 MOZ_FOLD_LIBS=1
 
 MOZ_JSDOWNLOADS=1
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -292,16 +292,18 @@ CssHtmlTree.prototype = {
       }
       // Hiding all properties
       for (let propView of this.propertyViews) {
         propView.refresh();
       }
       return promise.resolve(undefined);
     }
 
+    this.tooltip.hide();
+
     if (aElement === this.viewedElement) {
       return promise.resolve(undefined);
     }
 
     this.viewedElement = aElement;
     this.refreshSourceFilter();
 
     return this.refreshPanel();
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1424,16 +1424,19 @@ CssRuleView.prototype = {
   /**
    * Clear the rule view.
    */
   clear: function CssRuleView_clear()
   {
     this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
+
+    this.previewTooltip.hide();
+    this.colorPicker.hide();
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
   _changed: function CssRuleView_changed()
   {
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -58,8 +58,9 @@ support-files = browser_ruleview_pseudoe
 [browser_bug940500_rule_view_pick_gradient_color.js]
 [browser_ruleview_original_source_link.js]
 support-files =
   sourcemaps.html
   sourcemaps.css
   sourcemaps.css.map
   sourcemaps.scss
 [browser_computedview_original_source_link.js]
+[browser_bug946331_close_tooltip_on_new_selection.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_bug946331_close_tooltip_on_new_selection.js
@@ -0,0 +1,82 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let contentDoc;
+let inspector;
+let ruleView;
+let computedView;
+
+const PAGE_CONTENT = '<div class="one">el 1</div><div class="two">el 2</div>';
+
+function test() {
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
+    contentDoc = content.document;
+    waitForFocus(createDocument, content);
+  }, true);
+
+  content.location = "data:text/html,rule/computed views tooltip hiding test";
+}
+
+function createDocument() {
+  contentDoc.body.innerHTML = PAGE_CONTENT;
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    inspector.sidebar.once("computedview-ready", () => {
+      computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+      startTests();
+    });
+  });
+}
+
+function startTests() {
+  inspector.selection.setNode(contentDoc.querySelector(".one"));
+  inspector.once("inspector-updated", testRuleView);
+}
+
+function endTests() {
+  contentDoc = inspector = ruleView = computedView = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function testRuleView() {
+  info("Testing rule view tooltip closes on new selection");
+
+  // Show the rule view tooltip
+  let tooltip = ruleView.previewTooltip;
+  tooltip.show();
+  tooltip.once("shown", () => {
+    // Select a new node and assert that the tooltip closes
+    tooltip.once("hidden", () => {
+      ok(true, "Rule view tooltip closed after a new node got selected");
+      inspector.once("inspector-updated", testComputedView);
+    });
+    inspector.selection.setNode(contentDoc.querySelector(".two"));
+  });
+}
+
+function testComputedView() {
+  info("Testing computed view tooltip closes on new selection");
+
+  inspector.sidebar.select("computedview");
+  computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
+
+  // Show the computed view tooltip
+  let tooltip = computedView.tooltip;
+  tooltip.show();
+  tooltip.once("shown", () => {
+    // Select a new node and assert that the tooltip closes
+    tooltip.once("hidden", () => {
+      ok(true, "Computed view tooltip closed after a new node got selected");
+      inspector.once("inspector-updated", endTests);
+    });
+    inspector.selection.setNode(contentDoc.querySelector(".one"));
+  });
+}
--- a/browser/metro/base/content/startui/Start.xul
+++ b/browser/metro/base/content/startui/Start.xul
@@ -95,9 +95,10 @@
           <richgriditem/>
           <richgriditem/>
         </richgrid>
 
       </vbox>
 #endif
   </hbox>
   </html:body>
+  <observes element="bcast_windowState" attribute="*"/>
 </html:html>
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -196,16 +196,18 @@ pref("signon.rememberSignons", true);
 // this will automatically enable inline spellchecking (if it is available) for
 // editable elements in HTML
 // 0 = spellcheck nothing
 // 1 = check multi-line controls [default]
 // 2 = check multi/single line controls
 pref("layout.spellcheckDefault", 1);
 
 /* extension manager and xpinstall */
+// Completely disable extensions
+pref("extensions.defaultProviders.enabled", false);
 // Disable all add-on locations other than the profile
 pref("extensions.enabledScopes", 1);
 // Auto-disable any add-ons that are "dropped in" to the profile
 pref("extensions.autoDisableScopes", 1);
 // Disable add-on installation via the web-exposed APIs
 pref("xpinstall.enabled", false);
 pref("xpinstall.whitelist.add", "addons.mozilla.org");
 pref("extensions.autoupdate.enabled", false);
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -192,16 +192,22 @@ documenttab[selected] .documenttab-selec
 }
 
 #startui-page {
   overflow-x: scroll;
   overflow-y: hidden;
   height: 100%;
 }
 
+#startui-page[viewstate="snapped"],
+#startui-page[viewstate="portrait"] {
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+
 #startui-body {
   height: 100%;
   margin: 0;
 }
 
 #start-container {
   height: 100%;
 }
copy from dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
copy to dom/mobilemessage/src/gonk/MobileMessageDB.jsm
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -9,30 +9,25 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 var RIL = {};
 Cu.import("resource://gre/modules/ril_consts.js", RIL);
 
-const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
-  "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
-const RIL_MOBILEMESSAGEDATABASESERVICE_CID =
-  Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}");
 const RIL_GETMESSAGESCURSOR_CID =
   Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
 
 
-const DB_NAME = "sms";
 const DB_VERSION = 20;
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
@@ -76,75 +71,34 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 XPCOMUtils.defineLazyGetter(this, "MMS", function () {
   let MMS = {};
   Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
   return MMS;
 });
 
 /**
- * MobileMessageDatabaseService
+ * MobileMessageDB
  */
-function MobileMessageDatabaseService() {
-  // Prime the directory service's cache to ensure that the ProfD entry exists
-  // by the time IndexedDB queries for it off the main thread. (See bug 743635.)
-  Services.dirsvc.get("ProfD", Ci.nsIFile);
-
-  let that = this;
-  this.newTxn(READ_ONLY, function(error, txn, messageStore){
-    if (error) {
-      return;
-    }
-    // In order to get the highest key value, we open a key cursor in reverse
-    // order and get only the first pointed value.
-    let request = messageStore.openCursor(null, PREV);
-    request.onsuccess = function onsuccess(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        if (DEBUG) {
-          debug("Could not get the last key from mobile message database. " +
-                "Probably empty database");
-        }
-        return;
-      }
-      that.lastMessageId = cursor.key || 0;
-      if (DEBUG) debug("Last assigned message ID was " + that.lastMessageId);
-    };
-    request.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("Could not get the last key from mobile message database " +
-              event.target.errorCode);
-      }
-    };
-  });
-  this.updatePendingTransactionToError();
-}
-MobileMessageDatabaseService.prototype = {
-
-  classID: RIL_MOBILEMESSAGEDATABASESERVICE_CID,
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRilMobileMessageDatabaseService,
-                                         Ci.nsIMobileMessageDatabaseService,
-                                         Ci.nsIObserver]),
+this.MobileMessageDB = function() {};
+MobileMessageDB.prototype = {
+  dbName: null,
+  dbVersion: null,
 
   /**
    * Cache the DB here.
    */
   db: null,
 
   /**
    * Last sms/mms object store key value in the database.
    */
   lastMessageId: 0,
 
   /**
-   * nsIObserver
-   */
-  observe: function observe() {},
-
-  /**
    * Prepare the database. This may include opening the database and upgrading
    * it to the latest schema version.
    *
    * @param callback
    *        Function that takes an error and db argument. It is called when
    *        the database is ready to use or if an error occurs while preparing
    *        the database.
    *
@@ -158,26 +112,26 @@ MobileMessageDatabaseService.prototype =
     }
 
     let self = this;
     function gotDB(db) {
       self.db = db;
       callback(null, db);
     }
 
-    let request = indexedDB.open(DB_NAME, DB_VERSION);
+    let request = indexedDB.open(this.dbName, this.dbVersion);
     request.onsuccess = function (event) {
-      if (DEBUG) debug("Opened database:", DB_NAME, DB_VERSION);
+      if (DEBUG) debug("Opened database:", self.dbName, self.dbVersion);
       gotDB(event.target.result);
     };
     request.onupgradeneeded = function (event) {
       if (DEBUG) {
-        debug("Database needs upgrade:", DB_NAME,
+        debug("Database needs upgrade:", self.dbName,
               event.oldVersion, event.newVersion);
-        debug("Correct new database version:", event.newVersion == DB_VERSION);
+        debug("Correct new database version:", event.newVersion == self.dbVersion);
       }
 
       let db = event.target.result;
 
       let currentVersion = event.oldVersion;
 
       function update(currentVersion) {
         let next = update.bind(self, currentVersion + 1);
@@ -330,25 +284,93 @@ MobileMessageDatabaseService.prototype =
           stores.push(txn.objectStore(storeName));
         }
       }
       callback(null, txn, stores);
     });
   },
 
   /**
+   * Initialize this MobileMessageDB.
+   *
+   * @param aDbName
+   *        A string name for that database.
+   * @param aDbVersion
+   *        The version that mmdb should upgrade to. 0 for the lastest version.
+   * @param aCallback
+   *        A function when either the initialization transaction is completed
+   *        or any error occurs.  Should take only one argument -- null when
+   *        initialized with success or the error object otherwise.
+   */
+  init: function init(aDbName, aDbVersion, aCallback) {
+    this.dbName = aDbName;
+    this.dbVersion = aDbVersion || DB_VERSION;
+
+    let self = this;
+    this.newTxn(READ_ONLY, function(error, txn, messageStore){
+      if (error) {
+        if (aCallback) {
+          aCallback(error);
+        }
+        return;
+      }
+
+      if (aCallback) {
+        txn.oncomplete = function() {
+          aCallback(null);
+        };
+      }
+
+      // In order to get the highest key value, we open a key cursor in reverse
+      // order and get only the first pointed value.
+      let request = messageStore.openCursor(null, PREV);
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          if (DEBUG) {
+            debug("Could not get the last key from mobile message database. " +
+                  "Probably empty database");
+          }
+          return;
+        }
+        self.lastMessageId = cursor.key || 0;
+        if (DEBUG) debug("Last assigned message ID was " + self.lastMessageId);
+      };
+      request.onerror = function onerror(event) {
+        if (DEBUG) {
+          debug("Could not get the last key from mobile message database " +
+                event.target.errorCode);
+        }
+      };
+    });
+  },
+
+  close: function close() {
+    if (!this.db) {
+      return;
+    }
+
+    this.db.close();
+    this.db = null;
+    this.lastMessageId = 0;
+  },
+
+  /**
    * Sometimes user might reboot or remove battery while sending/receiving
    * message. This is function set the status of message records to error.
    */
-  updatePendingTransactionToError: function updatePendingTransactionToError() {
+  updatePendingTransactionToError:
+    function updatePendingTransactionToError(aError) {
+    if (aError) {
+      return;
+    }
+
     this.newTxn(READ_WRITE, function (error, txn, messageStore) {
-      if (DEBUG) {
-        txn.onerror = function onerror(event) {
-          debug("updatePendingTransactionToError fail, event = " + event);
-        };
+      if (error) {
+        return;
       }
 
       let deliveryIndex = messageStore.index("delivery");
 
       // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus:
       // error'.
       let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]);
       let cursorRequestSending = deliveryIndex.openCursor(keyRange);
@@ -2592,32 +2614,32 @@ let FilterSearcherHelper = {
       range = IDBKeyRange.upperBound(endDate.getTime());
     }
     this.filterIndex("timestamp", range, direction, txn, collect);
   },
 
   /**
    * Instanciate a filtering transaction.
    *
-   * @param service
-   *        A MobileMessageDatabaseService. Used to create
+   * @param mmdb
+   *        A MobileMessageDB.
    * @param txn
    *        Ongoing IDBTransaction context object.
    * @param error
    *        Previous error while creating the transaction.
    * @param filter
    *        A SmsFilter object.
    * @param reverse
    *        A boolean value indicating whether we should filter message in
    *        reversed order.
    * @param collect
    *        Result colletor function. It takes three parameters -- txn, message
    *        id, and message timestamp.
    */
-  transact: function transact(service, txn, error, filter, reverse, collect) {
+  transact: function transact(mmdb, txn, error, filter, reverse, collect) {
     if (error) {
       //TODO look at event.target.errorCode, pick appropriate error constant.
       if (DEBUG) debug("IDBRequest error " + error.target.errorCode);
       collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
       return;
     }
 
     let direction = reverse ? PREV : NEXT;
@@ -2697,19 +2719,19 @@ let FilterSearcherHelper = {
     if (filter.numbers) {
       if (DEBUG) debug("filter.numbers " + filter.numbers.join(", "));
 
       if (!single) {
         collect = intersectionCollector.newContext();
       }
 
       let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME);
-      service.findParticipantIdsByAddresses(participantStore, filter.numbers,
-                                            false, true,
-                                            (function (participantIds) {
+      mmdb.findParticipantIdsByAddresses(participantStore, filter.numbers,
+                                         false, true,
+                                         (function (participantIds) {
         if (!participantIds || !participantIds.length) {
           // Oops! No such participant at all.
 
           collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
           return;
         }
 
         if (participantIds.length == 1) {
@@ -3022,42 +3044,42 @@ UnionResultsCollector.prototype = {
   },
 
   newContext: function newContext() {
     this.contexts[1].processing++;
     return this.collect.bind(this, 1);
   }
 };
 
-function GetMessagesCursor(service, callback) {
-  this.service = service;
+function GetMessagesCursor(mmdb, callback) {
+  this.mmdb = mmdb;
   this.callback = callback;
   this.collector = new ResultsCollector();
 
   this.handleContinue(); // Trigger first run.
 }
 GetMessagesCursor.prototype = {
   classID: RIL_GETMESSAGESCURSOR_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
 
-  service: null,
+  mmdb: null,
   callback: null,
   collector: null,
 
   getMessageTxn: function getMessageTxn(messageStore, messageId) {
     if (DEBUG) debug ("Fetching message " + messageId);
 
     let getRequest = messageStore.get(messageId);
     let self = this;
     getRequest.onsuccess = function onsuccess(event) {
       if (DEBUG) {
         debug("notifyNextMessageInListGot - messageId: " + messageId);
       }
       let domMessage =
-        self.service.createDomMessageFromRecord(event.target.result);
+        self.mmdb.createDomMessageFromRecord(event.target.result);
       self.callback.notifyCursorResult(domMessage);
     };
     getRequest.onerror = function onerror(event) {
       if (DEBUG) {
         debug("notifyCursorError - messageId: " + messageId);
       }
       self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
     };
@@ -3079,45 +3101,45 @@ GetMessagesCursor.prototype = {
     if (txn) {
       let messageStore = txn.objectStore(MESSAGE_STORE_NAME);
       this.getMessageTxn(messageStore, messageId);
       return;
     }
 
     // Or, we have to open another transaction ourselves.
     let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, messageStore) {
+    this.mmdb.newTxn(READ_ONLY, function (error, txn, messageStore) {
       if (error) {
         self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       self.getMessageTxn(messageStore, messageId);
     }, [MESSAGE_STORE_NAME]);
   },
 
   // nsICursorContinueCallback
 
   handleContinue: function handleContinue() {
     if (DEBUG) debug("Getting next message in list");
     this.collector.squeeze(this.notify.bind(this));
   }
 };
 
-function GetThreadsCursor(service, callback) {
-  this.service = service;
+function GetThreadsCursor(mmdb, callback) {
+  this.mmdb = mmdb;
   this.callback = callback;
   this.collector = new ResultsCollector();
 
   this.handleContinue(); // Trigger first run.
 }
 GetThreadsCursor.prototype = {
   classID: RIL_GETTHREADSCURSOR_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
 
-  service: null,
+  mmdb: null,
   callback: null,
   collector: null,
 
   getThreadTxn: function getThreadTxn(threadStore, threadId) {
     if (DEBUG) debug ("Fetching thread " + threadId);
 
     let getRequest = threadStore.get(threadId);
     let self = this;
@@ -3160,30 +3182,32 @@ GetThreadsCursor.prototype = {
     if (txn) {
       let threadStore = txn.objectStore(THREAD_STORE_NAME);
       this.getThreadTxn(threadStore, threadId);
       return;
     }
 
     // Or, we have to open another transaction ourselves.
     let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, threadStore) {
+    this.mmdb.newTxn(READ_ONLY, function (error, txn, threadStore) {
       if (error) {
         self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       self.getThreadTxn(threadStore, threadId);
     }, [THREAD_STORE_NAME]);
   },
 
   // nsICursorContinueCallback
 
   handleContinue: function handleContinue() {
     if (DEBUG) debug("Getting next thread in list");
     this.collector.squeeze(this.notify.bind(this));
   }
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileMessageDatabaseService]);
+this.EXPORTED_SYMBOLS = [
+  'MobileMessageDB'
+];
 
 function debug() {
-  dump("MobileMessageDatabaseService: " + Array.slice(arguments).join(" ") + "\n");
+  dump("MobileMessageDB: " + Array.slice(arguments).join(" ") + "\n");
 }
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -3,3187 +3,114 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
-Cu.importGlobalProperties(["indexedDB"]);
 
-var RIL = {};
-Cu.import("resource://gre/modules/ril_consts.js", RIL);
+let MMDB = {};
+Cu.import("resource://gre/modules/MobileMessageDB.jsm", MMDB);
 
 const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
   "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
 const RIL_MOBILEMESSAGEDATABASESERVICE_CID =
   Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}");
-const RIL_GETMESSAGESCURSOR_CID =
-  Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
-const RIL_GETTHREADSCURSOR_CID =
-  Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
-
-const DEBUG = false;
-const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
-
 
 const DB_NAME = "sms";
-const DB_VERSION = 20;
-const MESSAGE_STORE_NAME = "sms";
-const THREAD_STORE_NAME = "thread";
-const PARTICIPANT_STORE_NAME = "participant";
-const MOST_RECENT_STORE_NAME = "most-recent";
-
-const DELIVERY_SENDING = "sending";
-const DELIVERY_SENT = "sent";
-const DELIVERY_RECEIVED = "received";
-const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
-const DELIVERY_ERROR = "error";
-
-const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
-const DELIVERY_STATUS_SUCCESS = "success";
-const DELIVERY_STATUS_PENDING = "pending";
-const DELIVERY_STATUS_ERROR = "error";
-
-const MESSAGE_CLASS_NORMAL = "normal";
-
-const FILTER_TIMESTAMP = "timestamp";
-const FILTER_NUMBERS = "numbers";
-const FILTER_DELIVERY = "delivery";
-const FILTER_READ = "read";
-
-// We canĀ“t create an IDBKeyCursor with a boolean, so we need to use numbers
-// instead.
-const FILTER_READ_UNREAD = 0;
-const FILTER_READ_READ = 1;
-
-const READ_ONLY = "readonly";
-const READ_WRITE = "readwrite";
-const PREV = "prev";
-const NEXT = "next";
-
-const COLLECT_ID_END = 0;
-const COLLECT_ID_ERROR = -1;
-const COLLECT_TIMESTAMP_UNUSED = 0;
-
-XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService",
-                                   "@mozilla.org/mobilemessage/mobilemessageservice;1",
-                                   "nsIMobileMessageService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gMMSService",
-                                   "@mozilla.org/mms/rilmmsservice;1",
-                                   "nsIMmsService");
-
-XPCOMUtils.defineLazyGetter(this, "MMS", function () {
-  let MMS = {};
-  Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
-  return MMS;
-});
 
 /**
  * MobileMessageDatabaseService
  */
 function MobileMessageDatabaseService() {
   // Prime the directory service's cache to ensure that the ProfD entry exists
   // by the time IndexedDB queries for it off the main thread. (See bug 743635.)
   Services.dirsvc.get("ProfD", Ci.nsIFile);
 
-  let that = this;
-  this.newTxn(READ_ONLY, function(error, txn, messageStore){
-    if (error) {
-      return;
-    }
-    // In order to get the highest key value, we open a key cursor in reverse
-    // order and get only the first pointed value.
-    let request = messageStore.openCursor(null, PREV);
-    request.onsuccess = function onsuccess(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        if (DEBUG) {
-          debug("Could not get the last key from mobile message database. " +
-                "Probably empty database");
-        }
-        return;
-      }
-      that.lastMessageId = cursor.key || 0;
-      if (DEBUG) debug("Last assigned message ID was " + that.lastMessageId);
-    };
-    request.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("Could not get the last key from mobile message database " +
-              event.target.errorCode);
-      }
-    };
-  });
-  this.updatePendingTransactionToError();
+  let mmdb = new MMDB.MobileMessageDB();
+  mmdb.init(DB_NAME, 0, mmdb.updatePendingTransactionToError.bind(mmdb));
+  this.mmdb = mmdb;
 }
 MobileMessageDatabaseService.prototype = {
 
   classID: RIL_MOBILEMESSAGEDATABASESERVICE_CID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRilMobileMessageDatabaseService,
                                          Ci.nsIMobileMessageDatabaseService,
                                          Ci.nsIObserver]),
 
   /**
-   * Cache the DB here.
+   * MobileMessageDB instance.
    */
-  db: null,
-
-  /**
-   * Last sms/mms object store key value in the database.
-   */
-  lastMessageId: 0,
+  mmdb: null,
 
   /**
    * nsIObserver
    */
   observe: function observe() {},
 
   /**
-   * Prepare the database. This may include opening the database and upgrading
-   * it to the latest schema version.
-   *
-   * @param callback
-   *        Function that takes an error and db argument. It is called when
-   *        the database is ready to use or if an error occurs while preparing
-   *        the database.
-   *
-   * @return (via callback) a database ready for use.
-   */
-  ensureDB: function ensureDB(callback) {
-    if (this.db) {
-      if (DEBUG) debug("ensureDB: already have a database, returning early.");
-      callback(null, this.db);
-      return;
-    }
-
-    let self = this;
-    function gotDB(db) {
-      self.db = db;
-      callback(null, db);
-    }
-
-    let request = indexedDB.open(DB_NAME, DB_VERSION);
-    request.onsuccess = function (event) {
-      if (DEBUG) debug("Opened database:", DB_NAME, DB_VERSION);
-      gotDB(event.target.result);
-    };
-    request.onupgradeneeded = function (event) {
-      if (DEBUG) {
-        debug("Database needs upgrade:", DB_NAME,
-              event.oldVersion, event.newVersion);
-        debug("Correct new database version:", event.newVersion == DB_VERSION);
-      }
-
-      let db = event.target.result;
-
-      let currentVersion = event.oldVersion;
-
-      function update(currentVersion) {
-        let next = update.bind(self, currentVersion + 1);
-
-        switch (currentVersion) {
-          case 0:
-            if (DEBUG) debug("New database");
-            self.createSchema(db, next);
-            break;
-          case 1:
-            if (DEBUG) debug("Upgrade to version 2. Including `read` index");
-            self.upgradeSchema(event.target.transaction, next);
-            break;
-          case 2:
-            if (DEBUG) debug("Upgrade to version 3. Fix existing entries.");
-            self.upgradeSchema2(event.target.transaction, next);
-            break;
-          case 3:
-            if (DEBUG) debug("Upgrade to version 4. Add quick threads view.");
-            self.upgradeSchema3(db, event.target.transaction, next);
-            break;
-          case 4:
-            if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.");
-            self.upgradeSchema4(event.target.transaction, next);
-            break;
-          case 5:
-            if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.");
-            self.upgradeSchema5(event.target.transaction, next);
-            break;
-          case 6:
-            if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes.");
-            self.upgradeSchema6(event.target.transaction, next);
-            break;
-          case 7:
-            if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores.");
-            self.upgradeSchema7(db, event.target.transaction, next);
-            break;
-          case 8:
-            if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS.");
-            self.upgradeSchema8(event.target.transaction, next);
-            break;
-          case 9:
-            if (DEBUG) debug("Upgrade to version 10. Upgrade type if it's not existing.");
-            self.upgradeSchema9(event.target.transaction, next);
-            break;
-          case 10:
-            if (DEBUG) debug("Upgrade to version 11. Add last message type into threadRecord.");
-            self.upgradeSchema10(event.target.transaction, next);
-            break;
-          case 11:
-            if (DEBUG) debug("Upgrade to version 12. Add envelopeId index for outgoing MMS.");
-            self.upgradeSchema11(event.target.transaction, next);
-            break;
-          case 12:
-            if (DEBUG) debug("Upgrade to version 13. Replaced deliveryStatus by deliveryInfo.");
-            self.upgradeSchema12(event.target.transaction, next);
-            break;
-          case 13:
-            if (DEBUG) debug("Upgrade to version 14. Fix the wrong participants.");
-            self.upgradeSchema13(event.target.transaction, next);
-            break;
-          case 14:
-            if (DEBUG) debug("Upgrade to version 15. Add deliveryTimestamp.");
-            self.upgradeSchema14(event.target.transaction, next);
-            break;
-          case 15:
-            if (DEBUG) debug("Upgrade to version 16. Add ICC ID for each message.");
-            self.upgradeSchema15(event.target.transaction, next);
-            break;
-          case 16:
-            if (DEBUG) debug("Upgrade to version 17. Add isReadReportSent for incoming MMS.");
-            self.upgradeSchema16(event.target.transaction, next);
-            break;
-          case 17:
-            if (DEBUG) debug("Upgrade to version 18. Add last message subject into threadRecord.");
-            self.upgradeSchema17(event.target.transaction, next);
-            break;
-          case 18:
-            if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS.");
-            self.upgradeSchema18(event.target.transaction, next);
-            break;
-          case 19:
-            if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp.");
-            self.upgradeSchema19(event.target.transaction, next);
-            break;
-          case 20:
-            // This will need to be moved for each new version
-            if (DEBUG) debug("Upgrade finished.");
-            break;
-          default:
-            event.target.transaction.abort();
-            callback("Old database version: " + event.oldVersion, null);
-            break;
-        }
-      }
-
-      update(currentVersion);
-    };
-    request.onerror = function (event) {
-      //TODO look at event.target.Code and change error constant accordingly
-      callback("Error opening database!", null);
-    };
-    request.onblocked = function (event) {
-      callback("Opening database request is blocked.", null);
-    };
-  },
-
-  /**
-   * Start a new transaction.
-   *
-   * @param txn_type
-   *        Type of transaction (e.g. READ_WRITE)
-   * @param callback
-   *        Function to call when the transaction is available. It will
-   *        be invoked with the transaction and opened object stores.
-   * @param storeNames
-   *        Names of the stores to open.
-   */
-  newTxn: function newTxn(txn_type, callback, storeNames) {
-    if (!storeNames) {
-      storeNames = [MESSAGE_STORE_NAME];
-    }
-    if (DEBUG) debug("Opening transaction for object stores: " + storeNames);
-    this.ensureDB(function (error, db) {
-      if (error) {
-        if (DEBUG) debug("Could not open database: " + error);
-        callback(error);
-        return;
-      }
-      let txn = db.transaction(storeNames, txn_type);
-      if (DEBUG) debug("Started transaction " + txn + " of type " + txn_type);
-      if (DEBUG) {
-        txn.oncomplete = function oncomplete(event) {
-          debug("Transaction " + txn + " completed.");
-        };
-        txn.onerror = function onerror(event) {
-          //TODO check event.target.errorCode and show an appropiate error
-          //     message according to it.
-          debug("Error occurred during transaction: " + event.target.errorCode);
-        };
-      }
-      let stores;
-      if (storeNames.length == 1) {
-        if (DEBUG) debug("Retrieving object store " + storeNames[0]);
-        stores = txn.objectStore(storeNames[0]);
-      } else {
-        stores = [];
-        for each (let storeName in storeNames) {
-          if (DEBUG) debug("Retrieving object store " + storeName);
-          stores.push(txn.objectStore(storeName));
-        }
-      }
-      callback(null, txn, stores);
-    });
-  },
-
-  /**
-   * Sometimes user might reboot or remove battery while sending/receiving
-   * message. This is function set the status of message records to error.
-   */
-  updatePendingTransactionToError: function updatePendingTransactionToError() {
-    this.newTxn(READ_WRITE, function (error, txn, messageStore) {
-      if (DEBUG) {
-        txn.onerror = function onerror(event) {
-          debug("updatePendingTransactionToError fail, event = " + event);
-        };
-      }
-
-      let deliveryIndex = messageStore.index("delivery");
-
-      // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus:
-      // error'.
-      let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]);
-      let cursorRequestSending = deliveryIndex.openCursor(keyRange);
-      cursorRequestSending.onsuccess = function(event) {
-        let messageCursor = event.target.result;
-        if (!messageCursor) {
-          return;
-        }
-
-        let messageRecord = messageCursor.value;
-
-        // Set delivery to error.
-        messageRecord.delivery = DELIVERY_ERROR;
-        messageRecord.deliveryIndex = [DELIVERY_ERROR, messageRecord.timestamp];
-
-        if (messageRecord.type == "sms") {
-          messageRecord.deliveryStatus = DELIVERY_STATUS_ERROR;
-        } else {
-          // Set delivery status to error.
-          for (let i = 0; i < messageRecord.deliveryInfo.length; i++) {
-            messageRecord.deliveryInfo[i].deliveryStatus = DELIVERY_STATUS_ERROR;
-          }
-        }
-
-        messageCursor.update(messageRecord);
-        messageCursor.continue();
-      };
-
-      // Set all 'delivery: not-downloaded' and 'deliveryStatus: pending'
-      // records to 'delivery: not-downloaded' and 'deliveryStatus: error'.
-      keyRange = IDBKeyRange.bound([DELIVERY_NOT_DOWNLOADED, 0], [DELIVERY_NOT_DOWNLOADED, ""]);
-      let cursorRequestNotDownloaded = deliveryIndex.openCursor(keyRange);
-      cursorRequestNotDownloaded.onsuccess = function(event) {
-        let messageCursor = event.target.result;
-        if (!messageCursor) {
-          return;
-        }
-
-        let messageRecord = messageCursor.value;
-
-        // We have no "not-downloaded" SMS messages.
-        if (messageRecord.type == "sms") {
-          messageCursor.continue();
-          return;
-        }
-
-        // Set delivery status to error.
-        let deliveryInfo = messageRecord.deliveryInfo;
-        if (deliveryInfo.length == 1 &&
-            deliveryInfo[0].deliveryStatus == DELIVERY_STATUS_PENDING) {
-          deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_ERROR;
-        }
-
-        messageCursor.update(messageRecord);
-        messageCursor.continue();
-      };
-    });
-  },
-
-  /**
-   * Create the initial database schema.
-   *
-   * TODO need to worry about number normalization somewhere...
-   * TODO full text search on body???
-   */
-  createSchema: function createSchema(db, next) {
-    // This messageStore holds the main mobile message data.
-    let messageStore = db.createObjectStore(MESSAGE_STORE_NAME, { keyPath: "id" });
-    messageStore.createIndex("timestamp", "timestamp", { unique: false });
-    if (DEBUG) debug("Created object stores and indexes");
-    next();
-  },
-
-  /**
-   * Upgrade to the corresponding database schema version.
-   */
-  upgradeSchema: function upgradeSchema(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.createIndex("read", "read", { unique: false });
-    next();
-  },
-
-  upgradeSchema2: function upgradeSchema2(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      messageRecord.messageClass = MESSAGE_CLASS_NORMAL;
-      messageRecord.deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema3: function upgradeSchema3(db, transaction, next) {
-    // Delete redundant "id" index.
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    if (messageStore.indexNames.contains("id")) {
-      messageStore.deleteIndex("id");
-    }
-
-    /**
-     * This mostRecentStore can be used to quickly construct a thread view of
-     * the mobile message database. Each entry looks like this:
-     *
-     * { senderOrReceiver: <String> (primary key),
-     *   id: <Number>,
-     *   timestamp: <Date>,
-     *   body: <String>,
-     *   unreadCount: <Number> }
-     *
-     */
-    let mostRecentStore = db.createObjectStore(MOST_RECENT_STORE_NAME,
-                                               { keyPath: "senderOrReceiver" });
-    mostRecentStore.createIndex("timestamp", "timestamp");
-    next();
-  },
-
-  upgradeSchema4: function upgradeSchema4(transaction, next) {
-    let threads = {};
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        for (let thread in threads) {
-          mostRecentStore.put(threads[thread]);
-        }
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      let contact = messageRecord.sender || messageRecord.receiver;
-
-      if (contact in threads) {
-        let thread = threads[contact];
-        if (!messageRecord.read) {
-          thread.unreadCount++;
-        }
-        if (messageRecord.timestamp > thread.timestamp) {
-          thread.id = messageRecord.id;
-          thread.body = messageRecord.body;
-          thread.timestamp = messageRecord.timestamp;
-        }
-      } else {
-        threads[contact] = {
-          senderOrReceiver: contact,
-          id: messageRecord.id,
-          timestamp: messageRecord.timestamp,
-          body: messageRecord.body,
-          unreadCount: messageRecord.read ? 0 : 1
-        };
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema5: function upgradeSchema5(transaction, next) {
-    // Don't perform any upgrade. See Bug 819560.
-    next();
-  },
-
-  upgradeSchema6: function upgradeSchema6(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "delivery" index.
-    if (messageStore.indexNames.contains("delivery")) {
-      messageStore.deleteIndex("delivery");
-    }
-    // Delete "sender" index.
-    if (messageStore.indexNames.contains("sender")) {
-      messageStore.deleteIndex("sender");
-    }
-    // Delete "receiver" index.
-    if (messageStore.indexNames.contains("receiver")) {
-      messageStore.deleteIndex("receiver");
-    }
-    // Delete "read" index.
-    if (messageStore.indexNames.contains("read")) {
-      messageStore.deleteIndex("read");
-    }
-
-    // Create new "delivery", "number" and "read" indexes.
-    messageStore.createIndex("delivery", "deliveryIndex");
-    messageStore.createIndex("number", "numberIndex", { multiEntry: true });
-    messageStore.createIndex("read", "readIndex");
-
-    // Populate new "deliverIndex", "numberIndex" and "readIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      let timestamp = messageRecord.timestamp;
-      messageRecord.deliveryIndex = [messageRecord.delivery, timestamp];
-      messageRecord.numberIndex = [
-        [messageRecord.sender, timestamp],
-        [messageRecord.receiver, timestamp]
-      ];
-      messageRecord.readIndex = [messageRecord.read, timestamp];
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add participant/thread stores.
-   *
-   * The message store now saves original phone numbers/addresses input from
-   * content to message records. No normalization is made.
-   *
-   * For filtering messages by phone numbers, it first looks up corresponding
-   * participant IDs from participant table and fetch message records with
-   * matching keys defined in per record "participantIds" field.
-   *
-   * For message threading, messages with the same participant ID array are put
-   * in the same thread. So updating "unreadCount", "lastMessageId" and
-   * "lastTimestamp" are through the "threadId" carried by per message record.
-   * Fetching threads list is now simply walking through the thread sotre. The
-   * "mostRecentStore" is dropped.
-   */
-  upgradeSchema7: function upgradeSchema7(db, transaction, next) {
-    /**
-     * This "participant" object store keeps mappings of multiple phone numbers
-     * of the same recipient to an integer participant id. Each entry looks
-     * like:
-     *
-     * { id: <Number> (primary key),
-     *   addresses: <Array of strings> }
-     */
-    let participantStore = db.createObjectStore(PARTICIPANT_STORE_NAME,
-                                                { keyPath: "id",
-                                                  autoIncrement: true });
-    participantStore.createIndex("addresses", "addresses", { multiEntry: true });
-
-    /**
-     * This "threads" object store keeps mappings from an integer thread id to
-     * ids of the participants of that message thread. Each entry looks like:
-     *
-     * { id: <Number> (primary key),
-     *   participantIds: <Array of participant IDs>,
-     *   participantAddresses: <Array of the first addresses of the participants>,
-     *   lastMessageId: <Number>,
-     *   lastTimestamp: <Date>,
-     *   subject: <String>,
-     *   unreadCount: <Number> }
-     *
-     */
-    let threadStore = db.createObjectStore(THREAD_STORE_NAME,
-                                           { keyPath: "id",
-                                             autoIncrement: true });
-    threadStore.createIndex("participantIds", "participantIds");
-    threadStore.createIndex("lastTimestamp", "lastTimestamp");
-
-    /**
-     * Replace "numberIndex" with "participantIdsIndex" and create an additional
-     * "threadId". "numberIndex" will be removed later.
-     */
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.createIndex("threadId", "threadIdIndex");
-    messageStore.createIndex("participantIds", "participantIdsIndex",
-                             { multiEntry: true });
-
-    // Now populate participantStore & threadStore.
-    let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
-    let self = this;
-    let mostRecentRequest = mostRecentStore.openCursor();
-    mostRecentRequest.onsuccess = function(event) {
-      let mostRecentCursor = event.target.result;
-      if (!mostRecentCursor) {
-        db.deleteObjectStore(MOST_RECENT_STORE_NAME);
-
-        // No longer need the "number" index in messageStore, use
-        // "participantIds" index instead.
-        messageStore.deleteIndex("number");
-        next();
-        return;
-      }
-
-      let mostRecentRecord = mostRecentCursor.value;
-
-      // Each entry in mostRecentStore is supposed to be a unique thread, so we
-      // retrieve the records out and insert its "senderOrReceiver" column as a
-      // new record in participantStore.
-      let number = mostRecentRecord.senderOrReceiver;
-      self.findParticipantRecordByAddress(participantStore, number, true,
-                                          function (participantRecord) {
-        // Also create a new record in threadStore.
-        let threadRecord = {
-          participantIds: [participantRecord.id],
-          participantAddresses: [number],
-          lastMessageId: mostRecentRecord.id,
-          lastTimestamp: mostRecentRecord.timestamp,
-          subject: mostRecentRecord.body,
-          unreadCount: mostRecentRecord.unreadCount,
-        };
-        let addThreadRequest = threadStore.add(threadRecord);
-        addThreadRequest.onsuccess = function (event) {
-          threadRecord.id = event.target.result;
-
-          let numberRange = IDBKeyRange.bound([number, 0], [number, ""]);
-          let messageRequest = messageStore.index("number")
-                                           .openCursor(numberRange, NEXT);
-          messageRequest.onsuccess = function (event) {
-            let messageCursor = event.target.result;
-            if (!messageCursor) {
-              // No more message records, check next most recent record.
-              mostRecentCursor.continue();
-              return;
-            }
-
-            let messageRecord = messageCursor.value;
-            // Check whether the message really belongs to this thread.
-            let matchSenderOrReceiver = false;
-            if (messageRecord.delivery == DELIVERY_RECEIVED) {
-              if (messageRecord.sender == number) {
-                matchSenderOrReceiver = true;
-              }
-            } else if (messageRecord.receiver == number) {
-              matchSenderOrReceiver = true;
-            }
-            if (!matchSenderOrReceiver) {
-              // Check next message record.
-              messageCursor.continue();
-              return;
-            }
-
-            messageRecord.threadId = threadRecord.id;
-            messageRecord.threadIdIndex = [threadRecord.id,
-                                           messageRecord.timestamp];
-            messageRecord.participantIdsIndex = [
-              [participantRecord.id, messageRecord.timestamp]
-            ];
-            messageCursor.update(messageRecord);
-            // Check next message record.
-            messageCursor.continue();
-          };
-          messageRequest.onerror = function () {
-            // Error in fetching message records, check next most recent record.
-            mostRecentCursor.continue();
-          };
-        };
-        addThreadRequest.onerror = function () {
-          // Error in fetching message records, check next most recent record.
-          mostRecentCursor.continue();
-        };
-      });
-    };
-  },
-
-  /**
-   * Add transactionId index for MMS.
-   */
-  upgradeSchema8: function upgradeSchema8(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "transactionId" index.
-    if (messageStore.indexNames.contains("transactionId")) {
-      messageStore.deleteIndex("transactionId");
-    }
-
-    // Create new "transactionId" indexes.
-    messageStore.createIndex("transactionId", "transactionIdIndex", { unique: true });
-
-    // Populate new "transactionIdIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if ("mms" == messageRecord.type &&
-          (DELIVERY_NOT_DOWNLOADED == messageRecord.delivery ||
-           DELIVERY_RECEIVED == messageRecord.delivery)) {
-        messageRecord.transactionIdIndex =
-          messageRecord.headers["x-mms-transaction-id"];
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema9: function upgradeSchema9(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Update type attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == undefined) {
-        messageRecord.type = "sms";
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema10: function upgradeSchema10(transaction, next) {
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-
-    // Add 'lastMessageType' to each thread record.
-    threadStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let threadRecord = cursor.value;
-      let lastMessageId = threadRecord.lastMessageId;
-      let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-      let request = messageStore.mozGetAll(lastMessageId);
-
-      request.onsuccess = function onsuccess() {
-        let messageRecord = request.result[0];
-        if (!messageRecord) {
-          if (DEBUG) debug("Message ID " + lastMessageId + " not found");
-          return;
-        }
-        if (messageRecord.id != lastMessageId) {
-          if (DEBUG) {
-            debug("Requested message ID (" + lastMessageId + ") is different from" +
-                  " the one we got");
-          }
-          return;
-        }
-        threadRecord.lastMessageType = messageRecord.type;
-        cursor.update(threadRecord);
-        cursor.continue();
-      };
-
-      request.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        cursor.continue();
-      };
-    };
-  },
-
-  /**
-   * Add envelopeId index for MMS.
-   */
-  upgradeSchema11: function upgradeSchema11(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Delete "envelopeId" index.
-    if (messageStore.indexNames.contains("envelopeId")) {
-      messageStore.deleteIndex("envelopeId");
-    }
-
-    // Create new "envelopeId" indexes.
-    messageStore.createIndex("envelopeId", "envelopeIdIndex", { unique: true });
-
-    // Populate new "envelopeIdIndex" attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms" &&
-          messageRecord.delivery == DELIVERY_SENT) {
-        messageRecord.envelopeIdIndex = messageRecord.headers["message-id"];
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Replace deliveryStatus by deliveryInfo.
-   */
-  upgradeSchema12: function upgradeSchema12(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms") {
-        messageRecord.deliveryInfo = [];
-
-        if (messageRecord.deliveryStatus.length == 1 &&
-            (messageRecord.delivery == DELIVERY_NOT_DOWNLOADED ||
-             messageRecord.delivery == DELIVERY_RECEIVED)) {
-          messageRecord.deliveryInfo.push({
-            receiver: null,
-            deliveryStatus: messageRecord.deliveryStatus[0] });
-        } else {
-          for (let i = 0; i < messageRecord.deliveryStatus.length; i++) {
-            messageRecord.deliveryInfo.push({
-              receiver: messageRecord.receivers[i],
-              deliveryStatus: messageRecord.deliveryStatus[i] });
-          }
-        }
-        delete messageRecord.deliveryStatus;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Fix the wrong participants.
-   */
-  upgradeSchema13: function upgradeSchema13(transaction, next) {
-    let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME);
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    let self = this;
-
-    let isInvalid = function (participantRecord) {
-      let entries = [];
-      for (let addr of participantRecord.addresses) {
-        entries.push({
-          normalized: addr,
-          parsed: PhoneNumberUtils.parseWithMCC(addr, null)
-        })
-      }
-      for (let ix = 0 ; ix < entries.length - 1; ix++) {
-        let entry1 = entries[ix];
-        for (let iy = ix + 1 ; iy < entries.length; iy ++) {
-          let entry2 = entries[iy];
-          if (!self.matchPhoneNumbers(entry1.normalized, entry1.parsed,
-                                      entry2.normalized, entry2.parsed)) {
-            return true;
-          }
-        }
-      }
-      return false;
-    };
-
-    let invalidParticipantIds = [];
-    participantStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (cursor) {
-        let participantRecord = cursor.value;
-        // Check if this participant record is valid
-        if (isInvalid(participantRecord)) {
-          invalidParticipantIds.push(participantRecord.id);
-          cursor.delete();
-        }
-        cursor.continue();
-        return;
-      }
-
-      // Participant store cursor iteration done.
-      if (!invalidParticipantIds.length) {
-        next();
-        return;
-      }
-
-      // Find affected thread.
-      let wrongThreads = [];
-      threadStore.openCursor().onsuccess = function(event) {
-        let threadCursor = event.target.result;
-        if (threadCursor) {
-          let threadRecord = threadCursor.value;
-          let participantIds = threadRecord.participantIds;
-          let foundInvalid = false;
-          for (let invalidParticipantId of invalidParticipantIds) {
-            if (participantIds.indexOf(invalidParticipantId) != -1) {
-              foundInvalid = true;
-              break;
-            }
-          }
-          if (foundInvalid) {
-            wrongThreads.push(threadRecord.id);
-            threadCursor.delete();
-          }
-          threadCursor.continue();
-          return;
-        }
-
-        if (!wrongThreads.length) {
-          next();
-          return;
-        }
-        // Use recursive function to avoid we add participant twice.
-        (function createUpdateThreadAndParticipant(ix) {
-          let threadId = wrongThreads[ix];
-          let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]);
-          messageStore.index("threadId").openCursor(range).onsuccess = function(event) {
-            let messageCursor = event.target.result;
-            if (!messageCursor) {
-              ix++;
-              if (ix === wrongThreads.length) {
-                next();
-                return;
-              }
-              createUpdateThreadAndParticipant(ix);
-              return;
-            }
-
-            let messageRecord = messageCursor.value;
-            let timestamp = messageRecord.timestamp;
-            let threadParticipants = [];
-            // Recaculate the thread participants of received message.
-            if (messageRecord.delivery === DELIVERY_RECEIVED ||
-                messageRecord.delivery === DELIVERY_NOT_DOWNLOADED) {
-              threadParticipants.push(messageRecord.sender);
-              if (messageRecord.type == "mms") {
-                this.fillReceivedMmsThreadParticipants(messageRecord, threadParticipants);
-              }
-            }
-            // Recaculate the thread participants of sent messages and error
-            // messages. In error sms messages, we don't have error received sms.
-            // In received MMS, we don't update the error to deliver field but
-            // deliverStatus. So we only consider sent message in DELIVERY_ERROR.
-            else if (messageRecord.delivery === DELIVERY_SENT ||
-                messageRecord.delivery === DELIVERY_ERROR) {
-              if (messageRecord.type == "sms") {
-                threadParticipants = [messageRecord.receiver];
-              } else if (messageRecord.type == "mms") {
-                threadParticipants = messageRecord.receivers;
-              }
-            }
-            self.findThreadRecordByParticipants(threadStore, participantStore,
-                                                threadParticipants, true,
-                                                function (threadRecord,
-                                                          participantIds) {
-              if (!participantIds) {
-                debug("participantIds is empty!");
-                return;
-              }
-
-              let timestamp = messageRecord.timestamp;
-              // Setup participantIdsIndex.
-              messageRecord.participantIdsIndex = [];
-              for each (let id in participantIds) {
-                messageRecord.participantIdsIndex.push([id, timestamp]);
-              }
-              if (threadRecord) {
-                let needsUpdate = false;
-
-                if (threadRecord.lastTimestamp <= timestamp) {
-                  threadRecord.lastTimestamp = timestamp;
-                  threadRecord.subject = messageRecord.body;
-                  threadRecord.lastMessageId = messageRecord.id;
-                  threadRecord.lastMessageType = messageRecord.type;
-                  needsUpdate = true;
-                }
-
-                if (!messageRecord.read) {
-                  threadRecord.unreadCount++;
-                  needsUpdate = true;
-                }
-
-                if (needsUpdate) {
-                  threadStore.put(threadRecord);
-                }
-                messageRecord.threadId = threadRecord.id;
-                messageRecord.threadIdIndex = [threadRecord.id, timestamp];
-                messageCursor.update(messageRecord);
-                messageCursor.continue();
-                return;
-              }
-
-              let threadRecord = {
-                participantIds: participantIds,
-                participantAddresses: threadParticipants,
-                lastMessageId: messageRecord.id,
-                lastTimestamp: timestamp,
-                subject: messageRecord.body,
-                unreadCount: messageRecord.read ? 0 : 1,
-                lastMessageType: messageRecord.type
-              };
-              threadStore.add(threadRecord).onsuccess = function (event) {
-                let threadId = event.target.result;
-                // Setup threadId & threadIdIndex.
-                messageRecord.threadId = threadId;
-                messageRecord.threadIdIndex = [threadId, timestamp];
-                messageCursor.update(messageRecord);
-                messageCursor.continue();
-              };
-            });
-          };
-        })(0);
-      };
-    };
-  },
-
-  /**
-   * Add deliveryTimestamp.
-   */
-  upgradeSchema14: function upgradeSchema14(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        messageRecord.deliveryTimestamp = 0;
-      } else if (messageRecord.type == "mms") {
-        let deliveryInfo = messageRecord.deliveryInfo;
-        for (let i = 0; i < deliveryInfo.length; i++) {
-          deliveryInfo[i].deliveryTimestamp = 0;
-        }
-      }
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add ICC ID.
-   */
-  upgradeSchema15: function upgradeSchema15(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      messageRecord.iccId = null;
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add isReadReportSent for incoming MMS.
-   */
-  upgradeSchema16: function upgradeSchema16(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Update type attributes.
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "mms") {
-        messageRecord.isReadReportSent = false;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  upgradeSchema17: function upgradeSchema17(transaction, next) {
-    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    // Add 'lastMessageSubject' to each thread record.
-    threadStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let threadRecord = cursor.value;
-      // We have defined 'threadRecord.subject' in upgradeSchema7(), but it
-      // actually means 'threadRecord.body'.  Swap the two values first.
-      threadRecord.body = threadRecord.subject;
-      delete threadRecord.subject;
-
-      // Only MMS supports subject so assign null for non-MMS one.
-      if (threadRecord.lastMessageType != "mms") {
-        threadRecord.lastMessageSubject = null;
-        cursor.update(threadRecord);
-
-        cursor.continue();
-        return;
-      }
-
-      messageStore.get(threadRecord.lastMessageId).onsuccess = function(event) {
-        let messageRecord = event.target.result;
-        let subject = messageRecord.headers.subject;
-        threadRecord.lastMessageSubject = subject || null;
-        cursor.update(threadRecord);
-
-        cursor.continue();
-      };
-    };
-  },
-
-  /**
-   * Add pid for incoming SMS.
-   */
-  upgradeSchema18: function upgradeSchema18(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        messageRecord.pid = RIL.PDU_PID_DEFAULT;
-        cursor.update(messageRecord);
-      }
-      cursor.continue();
-    };
-  },
-
-  /**
-   * Add readStatus and readTimestamp.
-   */
-  upgradeSchema19: function upgradeSchema19(transaction, next) {
-    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
-    messageStore.openCursor().onsuccess = function(event) {
-      let cursor = event.target.result;
-      if (!cursor) {
-        next();
-        return;
-      }
-
-      let messageRecord = cursor.value;
-      if (messageRecord.type == "sms") {
-        cursor.continue();
-        return;
-      }
-
-      // We can always retrieve transaction id from
-      // |messageRecord.headers["x-mms-transaction-id"]|.
-      if (messageRecord.hasOwnProperty("transactionId")) {
-        delete messageRecord.transactionId;
-      }
-
-      // xpconnect gives "undefined" for an unassigned argument of an interface
-      // method.
-      if (messageRecord.envelopeIdIndex === "undefined") {
-        delete messageRecord.envelopeIdIndex;
-      }
-
-      // Convert some header fields that were originally decoded as BooleanValue
-      // to numeric enums.
-      for (let field of ["x-mms-cancel-status",
-                         "x-mms-sender-visibility",
-                         "x-mms-read-status"]) {
-        let value = messageRecord.headers[field];
-        if (value !== undefined) {
-          messageRecord.headers[field] = value ? 128 : 129;
-        }
-      }
-
-      // For all sent and received MMS messages, we have to add their
-      // |readStatus| and |readTimestamp| attributes in |deliveryInfo| array.
-      let readReportRequested =
-        messageRecord.headers["x-mms-read-report"] || false;
-      for (let element of messageRecord.deliveryInfo) {
-        element.readStatus = readReportRequested
-                           ? MMS.DOM_READ_STATUS_PENDING
-                           : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
-        element.readTimestamp = 0;
-      }
-
-      cursor.update(messageRecord);
-      cursor.continue();
-    };
-  },
-
-  matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1,
-                                                            addr2, parsedAddr2) {
-    if ((parsedAddr1.internationalNumber &&
-         parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
-        (parsedAddr1.nationalNumber &&
-         parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) {
-      return true;
-    }
-
-    if (parsedAddr1.countryName != parsedAddr2.countryName) {
-      return false;
-    }
-
-    let ssPref = "dom.phonenumber.substringmatching." + parsedAddr1.countryName;
-    if (Services.prefs.getPrefType(ssPref) != Ci.nsIPrefBranch.PREF_INT) {
-      return false;
-    }
-
-    let val = Services.prefs.getIntPref(ssPref);
-    return addr1.length > val &&
-           addr2.length > val &&
-           addr1.slice(-val) === addr2.slice(-val);
-  },
-
-  matchPhoneNumbers: function matchPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2) {
-    if (parsedAddr1 && parsedAddr2) {
-      return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-    }
-
-    if (parsedAddr1) {
-      parsedAddr2 = PhoneNumberUtils.parseWithCountryName(addr2, parsedAddr1.countryName);
-      if (parsedAddr2) {
-        return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-      }
-
-      return false;
-    }
-
-    if (parsedAddr2) {
-      parsedAddr1 = PhoneNumberUtils.parseWithCountryName(addr1, parsedAddr2.countryName);
-      if (parsedAddr1) {
-        return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2);
-      }
-    }
-
-    return false;
-  },
-
-  createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) {
-    if (DEBUG) {
-      debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord));
-    }
-    if (aMessageRecord.type == "sms") {
-      return gMobileMessageService.createSmsMessage(aMessageRecord.id,
-                                                    aMessageRecord.threadId,
-                                                    aMessageRecord.iccId,
-                                                    aMessageRecord.delivery,
-                                                    aMessageRecord.deliveryStatus,
-                                                    aMessageRecord.sender,
-                                                    aMessageRecord.receiver,
-                                                    aMessageRecord.body,
-                                                    aMessageRecord.messageClass,
-                                                    aMessageRecord.timestamp,
-                                                    aMessageRecord.deliveryTimestamp,
-                                                    aMessageRecord.read);
-    } else if (aMessageRecord.type == "mms") {
-      let headers = aMessageRecord["headers"];
-      if (DEBUG) {
-        debug("MMS: headers: " + JSON.stringify(headers));
-      }
-
-      let subject = headers["subject"];
-      if (subject == undefined) {
-        subject = "";
-      }
-
-      let smil = "";
-      let attachments = [];
-      let parts = aMessageRecord.parts;
-      if (parts) {
-        for (let i = 0; i < parts.length; i++) {
-          let part = parts[i];
-          if (DEBUG) {
-            debug("MMS: part[" + i + "]: " + JSON.stringify(part));
-          }
-          // Sometimes the part is incomplete because the device reboots when
-          // downloading MMS. Don't need to expose this part to the content.
-          if (!part) {
-            continue;
-          }
-
-          let partHeaders = part["headers"];
-          let partContent = part["content"];
-          // Don't need to make the SMIL part if it's present.
-          if (partHeaders["content-type"]["media"] == "application/smil") {
-            smil = partContent;
-            continue;
-          }
-          attachments.push({
-            "id": partHeaders["content-id"],
-            "location": partHeaders["content-location"],
-            "content": partContent
-          });
-        }
-      }
-      let expiryDate = 0;
-      if (headers["x-mms-expiry"] != undefined) {
-        expiryDate = aMessageRecord.timestamp + headers["x-mms-expiry"] * 1000;
-      }
-      let readReportRequested = headers["x-mms-read-report"] || false;
-      return gMobileMessageService.createMmsMessage(aMessageRecord.id,
-                                                    aMessageRecord.threadId,
-                                                    aMessageRecord.iccId,
-                                                    aMessageRecord.delivery,
-                                                    aMessageRecord.deliveryInfo,
-                                                    aMessageRecord.sender,
-                                                    aMessageRecord.receivers,
-                                                    aMessageRecord.timestamp,
-                                                    aMessageRecord.read,
-                                                    subject,
-                                                    smil,
-                                                    attachments,
-                                                    expiryDate,
-                                                    readReportRequested);
-    }
-  },
-
-  findParticipantRecordByAddress: function findParticipantRecordByAddress(
-      aParticipantStore, aAddress, aCreate, aCallback) {
-    if (DEBUG) {
-      debug("findParticipantRecordByAddress("
-            + JSON.stringify(aAddress) + ", " + aCreate + ")");
-    }
-
-    // Two types of input number to match here, international(+886987654321),
-    // and local(0987654321) types. The "nationalNumber" parsed from
-    // phonenumberutils will be "987654321" in this case.
-
-    // Normalize address before searching for participant record.
-    let normalizedAddress = PhoneNumberUtils.normalize(aAddress, false);
-    let allPossibleAddresses = [normalizedAddress];
-    let parsedAddress = PhoneNumberUtils.parse(normalizedAddress);
-    if (parsedAddress && parsedAddress.internationalNumber &&
-        allPossibleAddresses.indexOf(parsedAddress.internationalNumber) < 0) {
-      // We only stores international numbers into participant store because
-      // the parsed national number doesn't contain country info and may
-      // duplicate in different country.
-      allPossibleAddresses.push(parsedAddress.internationalNumber);
-    }
-    if (DEBUG) {
-      debug("findParticipantRecordByAddress: allPossibleAddresses = " +
-            JSON.stringify(allPossibleAddresses));
-    }
-
-    // Make a copy here because we may need allPossibleAddresses again.
-    let needles = allPossibleAddresses.slice(0);
-    let request = aParticipantStore.index("addresses").get(needles.pop());
-    request.onsuccess = (function onsuccess(event) {
-      let participantRecord = event.target.result;
-      // 1) First try matching through "addresses" index of participant store.
-      //    If we're lucky, return the fetched participant record.
-      if (participantRecord) {
-        if (DEBUG) {
-          debug("findParticipantRecordByAddress: got "
-                + JSON.stringify(participantRecord));
-        }
-        aCallback(participantRecord);
-        return;
-      }
-
-      // Try next possible address again.
-      if (needles.length) {
-        let request = aParticipantStore.index("addresses").get(needles.pop());
-        request.onsuccess = onsuccess.bind(this);
-        return;
-      }
-
-      // 2) Traverse throught all participants and check all alias addresses.
-      aParticipantStore.openCursor().onsuccess = (function (event) {
-        let cursor = event.target.result;
-        if (!cursor) {
-          // Have traversed whole object store but still in vain.
-          if (!aCreate) {
-            aCallback(null);
-            return;
-          }
-
-          let participantRecord = { addresses: [normalizedAddress] };
-          let addRequest = aParticipantStore.add(participantRecord);
-          addRequest.onsuccess = function (event) {
-            participantRecord.id = event.target.result;
-            if (DEBUG) {
-              debug("findParticipantRecordByAddress: created "
-                    + JSON.stringify(participantRecord));
-            }
-            aCallback(participantRecord);
-          };
-          return;
-        }
-
-        let participantRecord = cursor.value;
-        for (let storedAddress of participantRecord.addresses) {
-          let parsedStoredAddress = PhoneNumberUtils.parseWithMCC(storedAddress, null);
-          let match = this.matchPhoneNumbers(normalizedAddress, parsedAddress,
-                                             storedAddress, parsedStoredAddress);
-          if (!match) {
-            // 3) Else we fail to match current stored participant record.
-            continue;
-          }
-          // Match!
-          if (aCreate) {
-            // In a READ-WRITE transaction, append one more possible address for
-            // this participant record.
-            participantRecord.addresses =
-              participantRecord.addresses.concat(allPossibleAddresses);
-            cursor.update(participantRecord);
-          }
-
-          if (DEBUG) {
-            debug("findParticipantRecordByAddress: match "
-                  + JSON.stringify(cursor.value));
-          }
-          aCallback(participantRecord);
-          return;
-        }
-
-        // Check next participant record if available.
-        cursor.continue();
-      }).bind(this);
-    }).bind(this);
-  },
-
-  findParticipantIdsByAddresses: function findParticipantIdsByAddresses(
-      aParticipantStore, aAddresses, aCreate, aSkipNonexistent, aCallback) {
-    if (DEBUG) {
-      debug("findParticipantIdsByAddresses("
-            + JSON.stringify(aAddresses) + ", "
-            + aCreate + ", " + aSkipNonexistent + ")");
-    }
-
-    if (!aAddresses || !aAddresses.length) {
-      if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
-      aCallback(null);
-      return;
-    }
-
-    let self = this;
-    (function findParticipantId(index, result) {
-      if (index >= aAddresses.length) {
-        // Sort numerically.
-        result.sort(function (a, b) {
-          return a - b;
-        });
-        if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result);
-        aCallback(result);
-        return;
-      }
-
-      self.findParticipantRecordByAddress(aParticipantStore,
-                                          aAddresses[index++], aCreate,
-                                          function (participantRecord) {
-        if (!participantRecord) {
-          if (!aSkipNonexistent) {
-            if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
-            aCallback(null);
-            return;
-          }
-        } else if (result.indexOf(participantRecord.id) < 0) {
-          result.push(participantRecord.id);
-        }
-        findParticipantId(index, result);
-      });
-    }) (0, []);
-  },
-
-  findThreadRecordByParticipants: function findThreadRecordByParticipants(
-      aThreadStore, aParticipantStore, aAddresses,
-      aCreateParticipants, aCallback) {
-    if (DEBUG) {
-      debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses)
-            + ", " + aCreateParticipants + ")");
-    }
-    this.findParticipantIdsByAddresses(aParticipantStore, aAddresses,
-                                       aCreateParticipants, false,
-                                       function (participantIds) {
-      if (!participantIds) {
-        if (DEBUG) debug("findThreadRecordByParticipants: returning null");
-        aCallback(null, null);
-        return;
-      }
-      // Find record from thread store.
-      let request = aThreadStore.index("participantIds").get(participantIds);
-      request.onsuccess = function (event) {
-        let threadRecord = event.target.result;
-        if (DEBUG) {
-          debug("findThreadRecordByParticipants: return "
-                + JSON.stringify(threadRecord));
-        }
-        aCallback(threadRecord, participantIds);
-      };
-    });
-  },
-
-  newTxnWithCallback: function newTxnWithCallback(aCallback, aFunc, aStoreNames) {
-    let self = this;
-    this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) {
-      let notifyResult = function(aRv, aMessageRecord) {
-        if (!aCallback) {
-          return;
-        }
-        let domMessage =
-          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
-        aCallback.notify(aRv, domMessage);
-      };
-
-      if (aError) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-        return;
-      }
-
-      let capture = {};
-      aTransaction.oncomplete = function(event) {
-        notifyResult(Cr.NS_OK, capture.messageRecord);
-      };
-      aTransaction.onabort = function(event) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-      };
-
-      aFunc(capture, aStores);
-    }, aStoreNames);
-  },
-
-  saveRecord: function saveRecord(aMessageRecord, aAddresses, aCallback) {
-    if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
-
-    let self = this;
-    this.newTxn(READ_WRITE, function(error, txn, stores) {
-      let notifyResult = function(aRv, aMessageRecord) {
-        if (!aCallback) {
-          return;
-        }
-        let domMessage =
-          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
-        aCallback.notify(aRv, domMessage);
-      };
-
-      if (error) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-        return;
-      }
-
-      txn.oncomplete = function oncomplete(event) {
-        if (aMessageRecord.id > self.lastMessageId) {
-          self.lastMessageId = aMessageRecord.id;
-        }
-        notifyResult(Cr.NS_OK, aMessageRecord);
-      };
-      txn.onabort = function onabort(event) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE, null);
-      };
-
-      let messageStore = stores[0];
-      let participantStore = stores[1];
-      let threadStore = stores[2];
-      self.replaceShortMessageOnSave(txn, messageStore, participantStore,
-                                     threadStore, aMessageRecord, aAddresses);
-    }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
-  },
-
-  replaceShortMessageOnSave:
-    function replaceShortMessageOnSave(aTransaction, aMessageStore,
-                                       aParticipantStore, aThreadStore,
-                                       aMessageRecord, aAddresses) {
-    let isReplaceTypePid = (aMessageRecord.pid) &&
-                           ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
-                             aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
-                            aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE);
-
-    if (aMessageRecord.type != "sms" ||
-        aMessageRecord.delivery != DELIVERY_RECEIVED ||
-        !isReplaceTypePid) {
-      this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                          aThreadStore, aMessageRecord, aAddresses);
-      return;
-    }
-
-    // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
-    //
-    //   ... the MS shall check the originating address and replace any
-    //   existing stored message having the same Protocol Identifier code
-    //   and originating address with the new short message and other
-    //   parameter values. If there is no message to be replaced, the MS
-    //   shall store the message in the normal way. ... it is recommended
-    //   that the SC address should not be checked by the MS."
-    let self = this;
-    this.findParticipantRecordByAddress(aParticipantStore,
-                                        aMessageRecord.sender, false,
-                                        function(participantRecord) {
-      if (!participantRecord) {
-        self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
-        return;
-      }
-
-      let participantId = participantRecord.id;
-      let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
-      let request = aMessageStore.index("participantIds").openCursor(range);
-      request.onsuccess = function onsuccess(event) {
-        let cursor = event.target.result;
-        if (!cursor) {
-          self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                              aThreadStore, aMessageRecord, aAddresses);
-          return;
-        }
-
-        // A message record with same participantId found.
-        // Verify matching criteria.
-        let foundMessageRecord = cursor.value;
-        if (foundMessageRecord.type != "sms" ||
-            foundMessageRecord.sender != aMessageRecord.sender ||
-            foundMessageRecord.pid != aMessageRecord.pid) {
-          cursor.continue();
-          return;
-        }
-
-        // Match! Now replace that found message record with current one.
-        aMessageRecord.id = foundMessageRecord.id;
-        self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
-      };
-    });
-  },
-
-  realSaveRecord: function realSaveRecord(aTransaction, aMessageStore,
-                                          aParticipantStore, aThreadStore,
-                                          aMessageRecord, aAddresses) {
-    let self = this;
-    this.findThreadRecordByParticipants(aThreadStore, aParticipantStore,
-                                        aAddresses, true,
-                                        function(threadRecord, participantIds) {
-      if (!participantIds) {
-        aTransaction.abort();
-        return;
-      }
-
-      let isOverriding = (aMessageRecord.id !== undefined);
-      if (!isOverriding) {
-        // |self.lastMessageId| is only updated in |txn.oncomplete|.
-        aMessageRecord.id = self.lastMessageId + 1;
-      }
-
-      let timestamp = aMessageRecord.timestamp;
-      let insertMessageRecord = function(threadId) {
-        // Setup threadId & threadIdIndex.
-        aMessageRecord.threadId = threadId;
-        aMessageRecord.threadIdIndex = [threadId, timestamp];
-        // Setup participantIdsIndex.
-        aMessageRecord.participantIdsIndex = [];
-        for each (let id in participantIds) {
-          aMessageRecord.participantIdsIndex.push([id, timestamp]);
-        }
-
-        if (!isOverriding) {
-          // Really add to message store.
-          aMessageStore.put(aMessageRecord);
-          return;
-        }
-
-        // If we're going to override an old message, we need to update the
-        // info of the original thread containing the overridden message.
-        // To get the original thread ID and read status of the overridden
-        // message record, we need to retrieve it before overriding it.
-        aMessageStore.get(aMessageRecord.id).onsuccess = function(event) {
-          let oldMessageRecord = event.target.result;
-          aMessageStore.put(aMessageRecord);
-          if (oldMessageRecord) {
-            self.updateThreadByMessageChange(aMessageStore,
-                                             aThreadStore,
-                                             oldMessageRecord.threadId,
-                                             aMessageRecord.id,
-                                             oldMessageRecord.read);
-          }
-        };
-      };
-
-      if (threadRecord) {
-        let needsUpdate = false;
-
-        if (threadRecord.lastTimestamp <= timestamp) {
-          let lastMessageSubject;
-          if (aMessageRecord.type == "mms") {
-            lastMessageSubject = aMessageRecord.headers.subject;
-          }
-          threadRecord.lastMessageSubject = lastMessageSubject || null;
-          threadRecord.lastTimestamp = timestamp;
-          threadRecord.body = aMessageRecord.body;
-          threadRecord.lastMessageId = aMessageRecord.id;
-          threadRecord.lastMessageType = aMessageRecord.type;
-          needsUpdate = true;
-        }
-
-        if (!aMessageRecord.read) {
-          threadRecord.unreadCount++;
-          needsUpdate = true;
-        }
-
-        if (needsUpdate) {
-          aThreadStore.put(threadRecord);
-        }
-
-        insertMessageRecord(threadRecord.id);
-        return;
-      }
-
-      let lastMessageSubject;
-      if (aMessageRecord.type == "mms") {
-        lastMessageSubject = aMessageRecord.headers.subject;
-      }
-
-      threadRecord = {
-        participantIds: participantIds,
-        participantAddresses: aAddresses,
-        lastMessageId: aMessageRecord.id,
-        lastTimestamp: timestamp,
-        lastMessageSubject: lastMessageSubject || null,
-        body: aMessageRecord.body,
-        unreadCount: aMessageRecord.read ? 0 : 1,
-        lastMessageType: aMessageRecord.type,
-      };
-      aThreadStore.add(threadRecord).onsuccess = function(event) {
-        let threadId = event.target.result;
-        insertMessageRecord(threadId);
-      };
-    });
-  },
-
-  forEachMatchedMmsDeliveryInfo:
-    function forEachMatchedMmsDeliveryInfo(aDeliveryInfo, aNeedle, aCallback) {
-
-    let typedAddress = {
-      type: MMS.Address.resolveType(aNeedle),
-      address: aNeedle
-    };
-    let normalizedAddress, parsedAddress;
-    if (typedAddress.type === "PLMN") {
-      normalizedAddress = PhoneNumberUtils.normalize(aNeedle, false);
-      parsedAddress = PhoneNumberUtils.parse(normalizedAddress);
-    }
-
-    for (let element of aDeliveryInfo) {
-      let typedStoredAddress = {
-        type: MMS.Address.resolveType(element.receiver),
-        address: element.receiver
-      };
-      if (typedAddress.type !== typedStoredAddress.type) {
-        // Not even my type.  Skip.
-        continue;
-      }
-
-      if (typedAddress.address == typedStoredAddress.address) {
-        // Have a direct match.
-        aCallback(element);
-        continue;
-      }
-
-      if (typedAddress.type !== "PLMN") {
-        // Address type other than "PLMN" must have direct match.  Or, skip.
-        continue;
-      }
-
-      // Both are of "PLMN" type.
-      let normalizedStoredAddress =
-        PhoneNumberUtils.normalize(element.receiver, false);
-      let parsedStoredAddress =
-        PhoneNumberUtils.parseWithMCC(normalizedStoredAddress, null);
-      if (this.matchPhoneNumbers(normalizedAddress, parsedAddress,
-                                 normalizedStoredAddress, parsedStoredAddress)) {
-        aCallback(element);
-      }
-    }
-  },
-
-  updateMessageDeliveryById: function updateMessageDeliveryById(
-      id, type, receiver, delivery, deliveryStatus, envelopeId, callback) {
-    if (DEBUG) {
-      debug("Setting message's delivery by " + type + " = "+ id
-            + " receiver: " + receiver
-            + " delivery: " + delivery
-            + " deliveryStatus: " + deliveryStatus
-            + " envelopeId: " + envelopeId);
-    }
-
-    let self = this;
-    this.newTxnWithCallback(callback, function(aCapture, aMessageStore) {
-      let getRequest;
-      if (type === "messageId") {
-        getRequest = aMessageStore.get(id);
-      } else if (type === "envelopeId") {
-        getRequest = aMessageStore.index("envelopeId").get(id);
-      }
-
-      getRequest.onsuccess = function onsuccess(event) {
-        let messageRecord = event.target.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("type = " + id + " is not found");
-          throw Cr.NS_ERROR_FAILURE;
-        }
-
-        let isRecordUpdated = false;
-
-        // Update |messageRecord.delivery| if needed.
-        if (delivery && messageRecord.delivery != delivery) {
-          messageRecord.delivery = delivery;
-          messageRecord.deliveryIndex = [delivery, messageRecord.timestamp];
-          isRecordUpdated = true;
-        }
-
-        // Attempt to update |deliveryStatus| and |deliveryTimestamp| of:
-        // - the |messageRecord| for SMS.
-        // - the element(s) in |messageRecord.deliveryInfo| for MMS.
-        if (deliveryStatus) {
-          // A callback for updating the deliveyStatus/deliveryTimestamp of
-          // each target.
-          let updateFunc = function(aTarget) {
-            if (aTarget.deliveryStatus == deliveryStatus) {
-              return;
-            }
-
-            aTarget.deliveryStatus = deliveryStatus;
-
-            // Update |deliveryTimestamp| if it's successfully delivered.
-            if (deliveryStatus == DELIVERY_STATUS_SUCCESS) {
-              aTarget.deliveryTimestamp = Date.now();
-            }
-
-            isRecordUpdated = true;
-          };
-
-          if (messageRecord.type == "sms") {
-            updateFunc(messageRecord);
-          } else if (messageRecord.type == "mms") {
-            if (!receiver) {
-              // If the receiver is specified, we only need to update the
-              // element(s) in deliveryInfo that match the same receiver.
-              messageRecord.deliveryInfo.forEach(updateFunc);
-            } else {
-              self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo,
-                                                 receiver, updateFunc);
-            }
-          }
-        }
-
-        // Update |messageRecord.envelopeIdIndex| if needed.
-        if (envelopeId) {
-          if (messageRecord.envelopeIdIndex != envelopeId) {
-            messageRecord.envelopeIdIndex = envelopeId;
-            isRecordUpdated = true;
-          }
-        }
-
-        aCapture.messageRecord = messageRecord;
-        if (!isRecordUpdated) {
-          if (DEBUG) {
-            debug("The values of delivery, deliveryStatus and envelopeId " +
-                  "don't need to be updated.");
-          }
-          return;
-        }
-
-        if (DEBUG) {
-          debug("The delivery, deliveryStatus or envelopeId are updated.");
-        }
-        aMessageStore.put(messageRecord);
-      };
-    });
-  },
-
-  fillReceivedMmsThreadParticipants: function fillReceivedMmsThreadParticipants(aMessage, threadParticipants) {
-    let receivers = aMessage.receivers;
-    // If we don't want to disable the MMS grouping for receiving, we need to
-    // add the receivers (excluding the user's own number) to the participants
-    // for creating the thread. Some cases might be investigated as below:
-    //
-    // 1. receivers.length == 0
-    //    This usually happens when receiving an MMS notification indication
-    //    which doesn't carry any receivers.
-    // 2. receivers.length == 1
-    //    If the receivers contain single phone number, we don't need to
-    //    add it into participants because we know that number is our own.
-    // 3. receivers.length >= 2
-    //    If the receivers contain multiple phone numbers, we need to add all
-    //    of them but not the user's own number into participants.
-    if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) {
-      return;
-    }
-    let isSuccess = false;
-    let slicedReceivers = receivers.slice();
-    if (aMessage.msisdn) {
-      let found = slicedReceivers.indexOf(aMessage.msisdn);
-      if (found !== -1) {
-        isSuccess = true;
-        slicedReceivers.splice(found, 1);
-      }
-    }
-
-    if (!isSuccess) {
-      // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's
-      // own phone number), so we cannot correcly exclude the user's own
-      // number from the receivers, thus wrongly building the thread index.
-      if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
-    }
-
-    threadParticipants = threadParticipants.concat(slicedReceivers);
-  },
-
-  updateThreadByMessageChange: function updateThreadByMessageChange(messageStore,
-                                                                    threadStore,
-                                                                    threadId,
-                                                                    messageId,
-                                                                    messageRead) {
-    threadStore.get(threadId).onsuccess = function(event) {
-      // This must exist.
-      let threadRecord = event.target.result;
-      if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord));
-
-      if (!messageRead) {
-        threadRecord.unreadCount--;
-      }
-
-      if (threadRecord.lastMessageId == messageId) {
-        // Check most recent sender/receiver.
-        let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]);
-        let request = messageStore.index("threadId")
-                                  .openCursor(range, PREV);
-        request.onsuccess = function(event) {
-          let cursor = event.target.result;
-          if (!cursor) {
-            if (DEBUG) {
-              debug("Deleting mru entry for thread id " + threadId);
-            }
-            threadStore.delete(threadId);
-            return;
-          }
-
-          let nextMsg = cursor.value;
-          let lastMessageSubject;
-          if (nextMsg.type == "mms") {
-            lastMessageSubject = nextMsg.headers.subject;
-          }
-          threadRecord.lastMessageSubject = lastMessageSubject || null;
-          threadRecord.lastMessageId = nextMsg.id;
-          threadRecord.lastTimestamp = nextMsg.timestamp;
-          threadRecord.body = nextMsg.body;
-          threadRecord.lastMessageType = nextMsg.type;
-          if (DEBUG) {
-            debug("Updating mru entry: " +
-                  JSON.stringify(threadRecord));
-          }
-          threadStore.put(threadRecord);
-        };
-      } else if (!messageRead) {
-        // Shortcut, just update the unread count.
-        if (DEBUG) {
-          debug("Updating unread count for thread id " + threadId + ": " +
-                (threadRecord.unreadCount + 1) + " -> " +
-                threadRecord.unreadCount);
-        }
-        threadStore.put(threadRecord);
-      }
-    };
-  },
-
-  /**
    * nsIRilMobileMessageDatabaseService API
    */
 
-  saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) {
-    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
-        (aMessage.type == "sms" && (aMessage.messageClass == undefined ||
-                                    aMessage.sender == undefined)) ||
-        (aMessage.type == "mms" && (aMessage.delivery == undefined ||
-                                    aMessage.deliveryStatus == undefined ||
-                                    !Array.isArray(aMessage.receivers))) ||
-        aMessage.timestamp == undefined) {
-      if (aCallback) {
-        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-      }
-      return;
-    }
-
-    let threadParticipants;
-    if (aMessage.type == "mms") {
-      if (aMessage.headers.from) {
-        aMessage.sender = aMessage.headers.from.address;
-      } else {
-        aMessage.sender = "anonymous";
-      }
-
-      threadParticipants = [aMessage.sender];
-      this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants);
-    } else { // SMS
-      threadParticipants = [aMessage.sender];
-    }
-
-    let timestamp = aMessage.timestamp;
-
-    // Adding needed indexes and extra attributes for internal use.
-    // threadIdIndex & participantIdsIndex are filled in saveRecord().
-    aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
-    aMessage.read = FILTER_READ_UNREAD;
-
-    if (aMessage.type == "mms") {
-      aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"];
-      aMessage.isReadReportSent = false;
-
-      // As a receiver, we don't need to care about the delivery status of
-      // others, so we put a single element with self's phone number in the
-      // |deliveryInfo| array.
-      aMessage.deliveryInfo = [{
-        receiver: aMessage.phoneNumber,
-        deliveryStatus: aMessage.deliveryStatus,
-        deliveryTimestamp: 0,
-        readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE,
-        readTimestamp: 0,
-      }];
-
-      delete aMessage.deliveryStatus;
-    }
-
-    if (aMessage.type == "sms") {
-      aMessage.delivery = DELIVERY_RECEIVED;
-      aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
-      aMessage.deliveryTimestamp = 0;
-
-      if (aMessage.pid == undefined) {
-        aMessage.pid = RIL.PDU_PID_DEFAULT;
-      }
-    }
-    aMessage.deliveryIndex = [aMessage.delivery, timestamp];
-
-    this.saveRecord(aMessage, threadParticipants, aCallback);
+  saveReceivedMessage: function(aMessage, aCallback) {
+    this.mmdb.saveReceivedMessage(aMessage, aCallback);
   },
 
-  saveSendingMessage: function saveSendingMessage(aMessage, aCallback) {
-    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
-        (aMessage.type == "sms" && aMessage.receiver == undefined) ||
-        (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) ||
-        aMessage.deliveryStatusRequested == undefined ||
-        aMessage.timestamp == undefined) {
-      if (aCallback) {
-        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-      }
-      return;
-    }
+  saveSendingMessage: function(aMessage, aCallback) {
+    this.mmdb.saveSendingMessage(aMessage, aCallback);
+  },
 
-    // Set |aMessage.deliveryStatus|. Note that for MMS record
-    // it must be an array of strings; For SMS, it's a string.
-    let deliveryStatus = aMessage.deliveryStatusRequested
-                       ? DELIVERY_STATUS_PENDING
-                       : DELIVERY_STATUS_NOT_APPLICABLE;
-    if (aMessage.type == "sms") {
-      aMessage.deliveryStatus = deliveryStatus;
-      // If |deliveryTimestamp| is not specified, use 0 as default.
-      if (aMessage.deliveryTimestamp == undefined) {
-        aMessage.deliveryTimestamp = 0;
-      }
-    } else if (aMessage.type == "mms") {
-      let receivers = aMessage.receivers
-      if (!Array.isArray(receivers)) {
-        if (DEBUG) {
-          debug("Need receivers for MMS. Fail to save the sending message.");
-        }
-        if (aCallback) {
-          aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-        }
-        return;
-      }
-      let readStatus = aMessage.headers["x-mms-read-report"]
-                     ? MMS.DOM_READ_STATUS_PENDING
-                     : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
-      aMessage.deliveryInfo = [];
-      for (let i = 0; i < receivers.length; i++) {
-        aMessage.deliveryInfo.push({
-          receiver: receivers[i],
-          deliveryStatus: deliveryStatus,
-          deliveryTimestamp: 0,
-          readStatus: readStatus,
-          readTimestamp: 0,
-        });
-      }
-    }
-
-    let timestamp = aMessage.timestamp;
-
-    // Adding needed indexes and extra attributes for internal use.
-    // threadIdIndex & participantIdsIndex are filled in saveRecord().
-    aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp];
-    aMessage.readIndex = [FILTER_READ_READ, timestamp];
-    aMessage.delivery = DELIVERY_SENDING;
-    aMessage.messageClass = MESSAGE_CLASS_NORMAL;
-    aMessage.read = FILTER_READ_READ;
-
-    let addresses;
-    if (aMessage.type == "sms") {
-      addresses = [aMessage.receiver];
-    } else if (aMessage.type == "mms") {
-      addresses = aMessage.receivers;
-    }
-    this.saveRecord(aMessage, addresses, aCallback);
+  setMessageDeliveryByMessageId: function(aMessageId, aReceiver, aDelivery,
+                                          aDeliveryStatus, aEnvelopeId,
+                                          aCallback) {
+    this.mmdb.updateMessageDeliveryById(aMessageId, "messageId", aReceiver,
+                                        aDelivery, aDeliveryStatus,
+                                        aEnvelopeId, aCallback);
   },
 
-  setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId(
-      messageId, receiver, delivery, deliveryStatus, envelopeId, callback) {
-    this.updateMessageDeliveryById(messageId, "messageId",
-                                   receiver, delivery, deliveryStatus,
-                                   envelopeId, callback);
-
-  },
-
-  setMessageDeliveryStatusByEnvelopeId:
-    function setMessageDeliveryStatusByEnvelopeId(aEnvelopeId, aReceiver,
-                                                  aDeliveryStatus, aCallback) {
-    this.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, null,
-                                   aDeliveryStatus, null, aCallback);
-  },
-
-  setMessageReadStatusByEnvelopeId:
-    function setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver,
-                                              aReadStatus, aCallback) {
-    if (DEBUG) {
-      debug("Setting message's read status by envelopeId = " + aEnvelopeId +
-            ", receiver: " + aReceiver + ", readStatus: " + aReadStatus);
-    }
-
-    let self = this;
-    this.newTxnWithCallback(aCallback, function(aCapture, aMessageStore) {
-      let getRequest = aMessageStore.index("envelopeId").get(aEnvelopeId);
-      getRequest.onsuccess = function onsuccess(event) {
-        let messageRecord = event.target.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("envelopeId '" + aEnvelopeId + "' not found");
-          throw Cr.NS_ERROR_FAILURE;
-        }
-
-        aCapture.messageRecord = messageRecord;
-
-        let isRecordUpdated = false;
-        self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo,
-                                           aReceiver, function(aEntry) {
-          if (aEntry.readStatus == aReadStatus) {
-            return;
-          }
-
-          aEntry.readStatus = aReadStatus;
-          if (aReadStatus == MMS.DOM_READ_STATUS_SUCCESS) {
-            aEntry.readTimestamp = Date.now();
-          } else {
-            aEntry.readTimestamp = 0;
-          }
-          isRecordUpdated = true;
-        });
-
-        if (!isRecordUpdated) {
-          if (DEBUG) {
-            debug("The values of readStatus don't need to be updated.");
-          }
-          return;
-        }
-
-        if (DEBUG) {
-          debug("The readStatus is updated.");
-        }
-        aMessageStore.put(messageRecord);
-      };
-    });
+  setMessageDeliveryStatusByEnvelopeId: function(aEnvelopeId, aReceiver,
+                                                 aDeliveryStatus, aCallback) {
+    this.mmdb.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver,
+                                        null, aDeliveryStatus, null, aCallback);
   },
 
-  getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) {
-    if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId);
-    let self = this;
-    this.newTxn(READ_ONLY, function (error, txn, messageStore) {
-      if (error) {
-        if (DEBUG) debug(error);
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-        return;
-      }
-      let request = messageStore.index("transactionId").get(aTransactionId);
-
-      txn.oncomplete = function oncomplete(event) {
-        if (DEBUG) debug("Transaction " + txn + " completed.");
-        let messageRecord = request.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("Transaction ID " + aTransactionId + " not found");
-          aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null);
-          return;
-        }
-        // In this case, we don't need a dom message. Just pass null to the
-        // third argument.
-        aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR,
-                         messageRecord, null);
-      };
-
-      txn.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-      };
-    });
+  setMessageReadStatusByEnvelopeId: function(aEnvelopeId, aReceiver,
+                                             aReadStatus, aCallback) {
+    this.mmdb.setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver,
+                                               aReadStatus, aCallback);
   },
 
-  getMessageRecordById: function getMessageRecordById(aMessageId, aCallback) {
-    if (DEBUG) debug("Retrieving message with ID " + aMessageId);
-    let self = this;
-    this.newTxn(READ_ONLY, function (error, txn, messageStore) {
-      if (error) {
-        if (DEBUG) debug(error);
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-        return;
-      }
-      let request = messageStore.mozGetAll(aMessageId);
+  getMessageRecordByTransactionId: function(aTransactionId, aCallback) {
+    this.mmdb.getMessageRecordByTransactionId(aTransactionId, aCallback);
+  },
 
-      txn.oncomplete = function oncomplete() {
-        if (DEBUG) debug("Transaction " + txn + " completed.");
-        if (request.result.length > 1) {
-          if (DEBUG) debug("Got too many results for id " + aMessageId);
-          aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null);
-          return;
-        }
-        let messageRecord = request.result[0];
-        if (!messageRecord) {
-          if (DEBUG) debug("Message ID " + aMessageId + " not found");
-          aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null);
-          return;
-        }
-        if (messageRecord.id != aMessageId) {
-          if (DEBUG) {
-            debug("Requested message ID (" + aMessageId + ") is " +
-                  "different from the one we got");
-          }
-          aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null);
-          return;
-        }
-        let domMessage = self.createDomMessageFromRecord(messageRecord);
-        aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR,
-                         messageRecord, domMessage);
-      };
-
-      txn.onerror = function onerror(event) {
-        if (DEBUG) {
-          if (event.target) {
-            debug("Caught error on transaction", event.target.errorCode);
-          }
-        }
-        aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null);
-      };
-    });
+  getMessageRecordById: function(aMessageId, aCallback) {
+    this.mmdb.getMessageRecordById(aMessageId, aCallback);
   },
 
   /**
    * nsIMobileMessageDatabaseService API
    */
 
-  getMessage: function getMessage(aMessageId, aRequest) {
-    if (DEBUG) debug("Retrieving message with ID " + aMessageId);
-    let notifyCallback = {
-      notify: function notify(aRv, aMessageRecord, aDomMessage) {
-        if (Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR == aRv) {
-          aRequest.notifyMessageGot(aDomMessage);
-          return;
-        }
-        aRequest.notifyGetMessageFailed(aRv, null);
-      }
-    };
-    this.getMessageRecordById(aMessageId, notifyCallback);
-  },
-
-  deleteMessage: function deleteMessage(messageIds, length, aRequest) {
-    if (DEBUG) debug("deleteMessage: message ids " + JSON.stringify(messageIds));
-    let deleted = [];
-    let self = this;
-    this.newTxn(READ_WRITE, function (error, txn, stores) {
-      if (error) {
-        if (DEBUG) debug("deleteMessage: failed to open transaction");
-        aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-        return;
-      }
-      txn.onerror = function onerror(event) {
-        if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
-        //TODO look at event.target.errorCode, pick appropriate error constant
-        aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-      };
-
-      const messageStore = stores[0];
-      const threadStore = stores[1];
-
-      txn.oncomplete = function oncomplete(event) {
-        if (DEBUG) debug("Transaction " + txn + " completed.");
-        aRequest.notifyMessageDeleted(deleted, length);
-      };
-
-      for (let i = 0; i < length; i++) {
-        let messageId = messageIds[i];
-        deleted[i] = false;
-        messageStore.get(messageId).onsuccess = function(messageIndex, event) {
-          let messageRecord = event.target.result;
-          let messageId = messageIds[messageIndex];
-          if (messageRecord) {
-            if (DEBUG) debug("Deleting message id " + messageId);
-
-            // First actually delete the message.
-            messageStore.delete(messageId).onsuccess = function(event) {
-              if (DEBUG) debug("Message id " + messageId + " deleted");
-              deleted[messageIndex] = true;
-
-              // Then update unread count and most recent message.
-              self.updateThreadByMessageChange(messageStore,
-                                               threadStore,
-                                               messageRecord.threadId,
-                                               messageId,
-                                               messageRecord.read);
-
-              Services.obs.notifyObservers(null,
-                                           "mobile-message-deleted",
-                                           JSON.stringify({ id: messageId }));
-            };
-          } else if (DEBUG) {
-            debug("Message id " + messageId + " does not exist");
-          }
-        }.bind(null, i);
-      }
-    }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]);
+  getMessage: function(aMessageId, aRequest) {
+    this.mmdb.getMessage(aMessageId, aRequest);
   },
 
-  createMessageCursor: function createMessageCursor(filter, reverse, callback) {
-    if (DEBUG) {
-      debug("Creating a message cursor. Filters:" +
-            " startDate: " + filter.startDate +
-            " endDate: " + filter.endDate +
-            " delivery: " + filter.delivery +
-            " numbers: " + filter.numbers +
-            " read: " + filter.read +
-            " threadId: " + filter.threadId +
-            " reverse: " + reverse);
-    }
-
-    let cursor = new GetMessagesCursor(this, callback);
-
-    let self = this;
-    self.newTxn(READ_ONLY, function (error, txn, stores) {
-      let collector = cursor.collector;
-      let collect = collector.collect.bind(collector);
-      FilterSearcherHelper.transact(self, txn, error, filter, reverse, collect);
-    }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]);
-
-    return cursor;
-  },
-
-  markMessageRead: function markMessageRead(messageId, value, aSendReadReport, aRequest) {
-    if (DEBUG) debug("Setting message " + messageId + " read to " + value);
-    this.newTxn(READ_WRITE, function (error, txn, stores) {
-      if (error) {
-        if (DEBUG) debug(error);
-        aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-        return;
-      }
-
-      txn.onerror = function onerror(event) {
-        if (DEBUG) debug("Caught error on transaction ", event.target.errorCode);
-        aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-      };
-
-      let messageStore = stores[0];
-      let threadStore = stores[1];
-      messageStore.get(messageId).onsuccess = function onsuccess(event) {
-        let messageRecord = event.target.result;
-        if (!messageRecord) {
-          if (DEBUG) debug("Message ID " + messageId + " not found");
-          aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR);
-          return;
-        }
-
-        if (messageRecord.id != messageId) {
-          if (DEBUG) {
-            debug("Retrieve message ID (" + messageId + ") is " +
-                  "different from the one we got");
-          }
-          aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR);
-          return;
-        }
-
-        // If the value to be set is the same as the current message `read`
-        // value, we just notify successfully.
-        if (messageRecord.read == value) {
-          if (DEBUG) debug("The value of messageRecord.read is already " + value);
-          aRequest.notifyMessageMarkedRead(messageRecord.read);
-          return;
-        }
-
-        messageRecord.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD;
-        messageRecord.readIndex = [messageRecord.read, messageRecord.timestamp];
-        let readReportMessageId, readReportTo;
-        if (aSendReadReport &&
-            messageRecord.type == "mms" &&
-            messageRecord.delivery == DELIVERY_RECEIVED &&
-            messageRecord.read == FILTER_READ_READ &&
-            !messageRecord.isReadReportSent) {
-          messageRecord.isReadReportSent = true;
-
-          let from = messageRecord.headers["from"];
-          readReportTo = from && from.address;
-          readReportMessageId = messageRecord.headers["message-id"];
-        }
-
-        if (DEBUG) debug("Message.read set to: " + value);
-        messageStore.put(messageRecord).onsuccess = function onsuccess(event) {
-          if (DEBUG) {
-            debug("Update successfully completed. Message: " +
-                  JSON.stringify(event.target.result));
-          }
-
-          // Now update the unread count.
-          let threadId = messageRecord.threadId;
-
-          threadStore.get(threadId).onsuccess = function(event) {
-            let threadRecord = event.target.result;
-            threadRecord.unreadCount += value ? -1 : 1;
-            if (DEBUG) {
-              debug("Updating unreadCount for thread id " + threadId + ": " +
-                    (value ?
-                     threadRecord.unreadCount + 1 :
-                     threadRecord.unreadCount - 1) +
-                     " -> " + threadRecord.unreadCount);
-            }
-            threadStore.put(threadRecord).onsuccess = function(event) {
-              if(readReportMessageId && readReportTo) {
-                gMMSService.sendReadReport(readReportMessageId,
-                                           readReportTo,
-                                           messageRecord.iccId);
-              }
-              aRequest.notifyMessageMarkedRead(messageRecord.read);
-            };
-          };
-        };
-      };
-    }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]);
+  deleteMessage: function(aMessageIds, aLength, aRequest) {
+    this.mmdb.deleteMessage(aMessageIds, aLength, aRequest);
   },
 
-  createThreadCursor: function createThreadCursor(callback) {
-    if (DEBUG) debug("Getting thread list");
-
-    let cursor = new GetThreadsCursor(this, callback);
-    this.newTxn(READ_ONLY, function (error, txn, threadStore) {
-      let collector = cursor.collector;
-      if (error) {
-        if (DEBUG) debug(error);
-        collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
-        return;
-      }
-      txn.onerror = function onerror(event) {
-        if (DEBUG) debug("Caught error on transaction ", event.target.errorCode);
-        collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
-      };
-      let request = threadStore.index("lastTimestamp").openKeyCursor();
-      request.onsuccess = function(event) {
-        let cursor = event.target.result;
-        if (cursor) {
-          if (collector.collect(txn, cursor.primaryKey, cursor.key)) {
-            cursor.continue();
-          }
-        } else {
-          collector.collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-        }
-      };
-    }, [THREAD_STORE_NAME]);
-
-    return cursor;
-  }
-};
-
-let FilterSearcherHelper = {
-
-  /**
-   * @param index
-   *        The name of a message store index to filter on.
-   * @param range
-   *        A IDBKeyRange.
-   * @param direction
-   *        NEXT or PREV.
-   * @param txn
-   *        Ongoing IDBTransaction context object.
-   * @param collect
-   *        Result colletor function. It takes three parameters -- txn, message
-   *        id, and message timestamp.
-   */
-  filterIndex: function filterIndex(index, range, direction, txn, collect) {
-    let messageStore = txn.objectStore(MESSAGE_STORE_NAME);
-    let request = messageStore.index(index).openKeyCursor(range, direction);
-    request.onsuccess = function onsuccess(event) {
-      let cursor = event.target.result;
-      // Once the cursor has retrieved all keys that matches its key range,
-      // the filter search is done.
-      if (cursor) {
-        let timestamp = Array.isArray(cursor.key) ? cursor.key[1] : cursor.key;
-        if (collect(txn, cursor.primaryKey, timestamp)) {
-          cursor.continue();
-        }
-      } else {
-        collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-      }
-    };
-    request.onerror = function onerror(event) {
-      if (DEBUG && event) debug("IDBRequest error " + event.target.errorCode);
-      collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
-    };
-  },
-
-  /**
-   * Explicitly fiter message on the timestamp index.
-   *
-   * @param startDate
-   *        Timestamp of the starting date.
-   * @param endDate
-   *        Timestamp of the ending date.
-   * @param direction
-   *        NEXT or PREV.
-   * @param txn
-   *        Ongoing IDBTransaction context object.
-   * @param collect
-   *        Result colletor function. It takes three parameters -- txn, message
-   *        id, and message timestamp.
-   */
-  filterTimestamp: function filterTimestamp(startDate, endDate, direction, txn,
-                                            collect) {
-    let range = null;
-    if (startDate != null && endDate != null) {
-      range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime());
-    } else if (startDate != null) {
-      range = IDBKeyRange.lowerBound(startDate.getTime());
-    } else if (endDate != null) {
-      range = IDBKeyRange.upperBound(endDate.getTime());
-    }
-    this.filterIndex("timestamp", range, direction, txn, collect);
+  createMessageCursor: function(aFilter, aReverse, aCallback) {
+    return this.mmdb.createMessageCursor(aFilter, aReverse, aCallback);
   },
 
-  /**
-   * Instanciate a filtering transaction.
-   *
-   * @param service
-   *        A MobileMessageDatabaseService. Used to create
-   * @param txn
-   *        Ongoing IDBTransaction context object.
-   * @param error
-   *        Previous error while creating the transaction.
-   * @param filter
-   *        A SmsFilter object.
-   * @param reverse
-   *        A boolean value indicating whether we should filter message in
-   *        reversed order.
-   * @param collect
-   *        Result colletor function. It takes three parameters -- txn, message
-   *        id, and message timestamp.
-   */
-  transact: function transact(service, txn, error, filter, reverse, collect) {
-    if (error) {
-      //TODO look at event.target.errorCode, pick appropriate error constant.
-      if (DEBUG) debug("IDBRequest error " + error.target.errorCode);
-      collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED);
-      return;
-    }
-
-    let direction = reverse ? PREV : NEXT;
-
-    // We support filtering by date range only (see `else` block below) or by
-    // number/delivery status/read status with an optional date range.
-    if (filter.delivery == null &&
-        filter.numbers == null &&
-        filter.read == null &&
-        filter.threadId == null) {
-      // Filtering by date range only.
-      if (DEBUG) {
-        debug("filter.timestamp " + filter.startDate + ", " + filter.endDate);
-      }
-
-      this.filterTimestamp(filter.startDate, filter.endDate, direction, txn,
-                           collect);
-      return;
-    }
-
-    // Numeric 0 is smaller than any time stamp, and empty string is larger
-    // than all numeric values.
-    let startDate = 0, endDate = "";
-    if (filter.startDate != null) {
-      startDate = filter.startDate.getTime();
-    }
-    if (filter.endDate != null) {
-      endDate = filter.endDate.getTime();
-    }
-
-    let single, intersectionCollector;
-    {
-      let num = 0;
-      if (filter.delivery) num++;
-      if (filter.numbers) num++;
-      if (filter.read != undefined) num++;
-      if (filter.threadId != undefined) num++;
-      single = (num == 1);
-    }
-
-    if (!single) {
-      intersectionCollector = new IntersectionResultsCollector(collect, reverse);
-    }
+  markMessageRead: function(aMessageId, aValue, aSendReadReport, aRequest) {
+    this.mmdb.markMessageRead(aMessageId, aValue, aSendReadReport, aRequest);
+  },
 
-    // Retrieve the keys from the 'delivery' index that matches the value of
-    // filter.delivery.
-    if (filter.delivery) {
-      if (DEBUG) debug("filter.delivery " + filter.delivery);
-      let delivery = filter.delivery;
-      let range = IDBKeyRange.bound([delivery, startDate], [delivery, endDate]);
-      this.filterIndex("delivery", range, direction, txn,
-                       single ? collect : intersectionCollector.newContext());
-    }
-
-    // Retrieve the keys from the 'read' index that matches the value of
-    // filter.read.
-    if (filter.read != undefined) {
-      if (DEBUG) debug("filter.read " + filter.read);
-      let read = filter.read ? FILTER_READ_READ : FILTER_READ_UNREAD;
-      let range = IDBKeyRange.bound([read, startDate], [read, endDate]);
-      this.filterIndex("read", range, direction, txn,
-                       single ? collect : intersectionCollector.newContext());
-    }
-
-    // Retrieve the keys from the 'threadId' index that matches the value of
-    // filter.threadId.
-    if (filter.threadId != undefined) {
-      if (DEBUG) debug("filter.threadId " + filter.threadId);
-      let threadId = filter.threadId;
-      let range = IDBKeyRange.bound([threadId, startDate], [threadId, endDate]);
-      this.filterIndex("threadId", range, direction, txn,
-                       single ? collect : intersectionCollector.newContext());
-    }
-
-    // Retrieve the keys from the 'sender' and 'receiver' indexes that
-    // match the values of filter.numbers
-    if (filter.numbers) {
-      if (DEBUG) debug("filter.numbers " + filter.numbers.join(", "));
-
-      if (!single) {
-        collect = intersectionCollector.newContext();
-      }
-
-      let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME);
-      service.findParticipantIdsByAddresses(participantStore, filter.numbers,
-                                            false, true,
-                                            (function (participantIds) {
-        if (!participantIds || !participantIds.length) {
-          // Oops! No such participant at all.
-
-          collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-          return;
-        }
-
-        if (participantIds.length == 1) {
-          let id = participantIds[0];
-          let range = IDBKeyRange.bound([id, startDate], [id, endDate]);
-          this.filterIndex("participantIds", range, direction, txn, collect);
-          return;
-        }
-
-        let unionCollector = new UnionResultsCollector(collect);
-
-        this.filterTimestamp(filter.startDate, filter.endDate, direction, txn,
-                             unionCollector.newTimestampContext());
-
-        for (let i = 0; i < participantIds.length; i++) {
-          let id = participantIds[i];
-          let range = IDBKeyRange.bound([id, startDate], [id, endDate]);
-          this.filterIndex("participantIds", range, direction, txn,
-                           unionCollector.newContext());
-        }
-      }).bind(this));
-    }
+  createThreadCursor: function(aCallback) {
+    return this.mmdb.createThreadCursor(aCallback);
   }
 };
 
-function ResultsCollector() {
-  this.results = [];
-  this.done = false;
-}
-ResultsCollector.prototype = {
-  results: null,
-  requestWaiting: null,
-  done: null,
-
-  /**
-   * Queue up passed id, reply if necessary.
-   *
-   * @param txn
-   *        Ongoing IDBTransaction context object.
-   * @param id
-   *        COLLECT_ID_END(0) for no more results, COLLECT_ID_ERROR(-1) for
-   *        errors and valid otherwise.
-   * @param timestamp
-   *        We assume this function is always called in timestamp order. So
-   *        this parameter is actually unused.
-   *
-   * @return true if expects more. false otherwise.
-   */
-  collect: function collect(txn, id, timestamp) {
-    if (this.done) {
-      return false;
-    }
-
-    if (DEBUG) {
-      debug("collect: message ID = " + id);
-    }
-    if (id) {
-      // Queue up any id but '0' and replies later accordingly.
-      this.results.push(id);
-    }
-    if (id <= 0) {
-      // No more processing on '0' or negative values passed.
-      this.done = true;
-    }
-
-    if (!this.requestWaiting) {
-      if (DEBUG) debug("Cursor.continue() not called yet");
-      return !this.done;
-    }
-
-    // We assume there is only one request waiting throughout the message list
-    // retrieving process. So we don't bother continuing to process further
-    // waiting requests here. This assumption comes from DOMCursor::Continue()
-    // implementation.
-    let callback = this.requestWaiting;
-    this.requestWaiting = null;
-
-    this.drip(txn, callback);
-
-    return !this.done;
-  },
-
-  /**
-   * Callback right away with the first queued result entry if the filtering is
-   * done. Or queue up the request and callback when a new entry is available.
-   *
-   * @param callback
-   *        A callback function that accepts a numeric id.
-   */
-  squeeze: function squeeze(callback) {
-    if (this.requestWaiting) {
-      throw new Error("Already waiting for another request!");
-    }
-
-    if (!this.done) {
-      // Database transaction ongoing, let it reply for us so that we won't get
-      // blocked by the existing transaction.
-      this.requestWaiting = callback;
-      return;
-    }
-
-    this.drip(null, callback);
-  },
-
-  /**
-   * @param txn
-   *        Ongoing IDBTransaction context object or null.
-   * @param callback
-   *        A callback function that accepts a numeric id.
-   */
-  drip: function drip(txn, callback) {
-    if (!this.results.length) {
-      if (DEBUG) debug("No messages matching the filter criteria");
-      callback(txn, COLLECT_ID_END);
-      return;
-    }
-
-    if (this.results[0] < 0) {
-      // An previous error found. Keep the answer in results so that we can
-      // reply INTERNAL_ERROR for further requests.
-      if (DEBUG) debug("An previous error found");
-      callback(txn, COLLECT_ID_ERROR);
-      return;
-    }
-
-    let firstMessageId = this.results.shift();
-    callback(txn, firstMessageId);
-  }
-};
-
-function IntersectionResultsCollector(collect, reverse) {
-  this.cascadedCollect = collect;
-  this.reverse = reverse;
-  this.contexts = [];
-}
-IntersectionResultsCollector.prototype = {
-  cascadedCollect: null,
-  reverse: false,
-  contexts: null,
-
-  /**
-   * Queue up {id, timestamp} pairs, find out intersections and report to
-   * |cascadedCollect|. Return true if it is still possible to have another match.
-   */
-  collect: function collect(contextIndex, txn, id, timestamp) {
-    if (DEBUG) {
-      debug("IntersectionResultsCollector: "
-            + contextIndex + ", " + id + ", " + timestamp);
-    }
-
-    let contexts = this.contexts;
-    let context = contexts[contextIndex];
-
-    if (id < 0) {
-      // Act as no more matched records.
-      id = 0;
-    }
-    if (!id) {
-      context.done = true;
-
-      if (!context.results.length) {
-        // Already empty, can't have further intersection results.
-        return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-      }
-
-      for (let i = 0; i < contexts.length; i++) {
-        if (!contexts[i].done) {
-          // Don't call |this.cascadedCollect| because |context.results| might not
-          // be empty, so other contexts might still have a chance here.
-          return false;
-        }
-      }
-
-      // It was the last processing context and is no more processing.
-      return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-    }
-
-    // Search id in other existing results. If no other results has it,
-    // and A) the last timestamp is smaller-equal to current timestamp,
-    // we wait for further results; either B) record timestamp is larger
-    // then current timestamp or C) no more processing for a filter, then we
-    // drop this id because there can't be a match anymore.
-    for (let i = 0; i < contexts.length; i++) {
-      if (i == contextIndex) {
-        continue;
-      }
-
-      let ctx = contexts[i];
-      let results = ctx.results;
-      let found = false;
-      for (let j = 0; j < results.length; j++) {
-        let result = results[j];
-        if (result.id == id) {
-          found = true;
-          break;
-        }
-        if ((!this.reverse && (result.timestamp > timestamp)) ||
-            (this.reverse && (result.timestamp < timestamp))) {
-          // B) Cannot find a match anymore. Drop.
-          return true;
-        }
-      }
-
-      if (!found) {
-        if (ctx.done) {
-          // C) Cannot find a match anymore. Drop.
-          if (results.length) {
-            let lastResult = results[results.length - 1];
-            if ((!this.reverse && (lastResult.timestamp >= timestamp)) ||
-                (this.reverse && (lastResult.timestamp <= timestamp))) {
-              // Still have a chance to get another match. Return true.
-              return true;
-            }
-          }
-
-          // Impossible to find another match because all results in ctx have
-          // timestamps smaller than timestamp.
-          context.done = true;
-          return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-        }
-
-        // A) Pending.
-        context.results.push({
-          id: id,
-          timestamp: timestamp
-        });
-        return true;
-      }
-    }
-
-    // Now id is found in all other results. Report it.
-    return this.cascadedCollect(txn, id, timestamp);
-  },
-
-  newContext: function newContext() {
-    let contextIndex = this.contexts.length;
-    this.contexts.push({
-      results: [],
-      done: false
-    });
-    return this.collect.bind(this, contextIndex);
-  }
-};
-
-function UnionResultsCollector(collect) {
-  this.cascadedCollect = collect;
-  this.contexts = [{
-    // Timestamp.
-    processing: 1,
-    results: []
-  }, {
-    processing: 0,
-    results: []
-  }];
-}
-UnionResultsCollector.prototype = {
-  cascadedCollect: null,
-  contexts: null,
-
-  collect: function collect(contextIndex, txn, id, timestamp) {
-    if (DEBUG) {
-      debug("UnionResultsCollector: "
-            + contextIndex + ", " + id + ", " + timestamp);
-    }
-
-    let contexts = this.contexts;
-    let context = contexts[contextIndex];
-
-    if (id < 0) {
-      // Act as no more matched records.
-      id = 0;
-    }
-    if (id) {
-      if (!contextIndex) {
-        // Timestamp.
-        context.results.push({
-          id: id,
-          timestamp: timestamp
-        });
-      } else {
-        context.results.push(id);
-      }
-      return true;
-    }
-
-    context.processing -= 1;
-    if (contexts[0].processing || contexts[1].processing) {
-      // At least one queue is still processing, but we got here because
-      // current cursor gives 0 as id meaning no more messages are
-      // available. Return false here to stop further cursor.continue() calls.
-      return false;
-    }
-
-    let tres = contexts[0].results;
-    let qres = contexts[1].results;
-    tres = tres.filter(function (element) {
-      return qres.indexOf(element.id) != -1;
-    });
-
-    for (let i = 0; i < tres.length; i++) {
-      this.cascadedCollect(txn, tres[i].id, tres[i].timestamp);
-    }
-    this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
-
-    return false;
-  },
-
-  newTimestampContext: function newTimestampContext() {
-    return this.collect.bind(this, 0);
-  },
-
-  newContext: function newContext() {
-    this.contexts[1].processing++;
-    return this.collect.bind(this, 1);
-  }
-};
-
-function GetMessagesCursor(service, callback) {
-  this.service = service;
-  this.callback = callback;
-  this.collector = new ResultsCollector();
-
-  this.handleContinue(); // Trigger first run.
-}
-GetMessagesCursor.prototype = {
-  classID: RIL_GETMESSAGESCURSOR_CID,
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
-
-  service: null,
-  callback: null,
-  collector: null,
-
-  getMessageTxn: function getMessageTxn(messageStore, messageId) {
-    if (DEBUG) debug ("Fetching message " + messageId);
-
-    let getRequest = messageStore.get(messageId);
-    let self = this;
-    getRequest.onsuccess = function onsuccess(event) {
-      if (DEBUG) {
-        debug("notifyNextMessageInListGot - messageId: " + messageId);
-      }
-      let domMessage =
-        self.service.createDomMessageFromRecord(event.target.result);
-      self.callback.notifyCursorResult(domMessage);
-    };
-    getRequest.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("notifyCursorError - messageId: " + messageId);
-      }
-      self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-    };
-  },
-
-  notify: function notify(txn, messageId) {
-    if (!messageId) {
-      this.callback.notifyCursorDone();
-      return;
-    }
-
-    if (messageId < 0) {
-      this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-      return;
-    }
-
-    // When filter transaction is not yet completed, we're called with current
-    // ongoing transaction object.
-    if (txn) {
-      let messageStore = txn.objectStore(MESSAGE_STORE_NAME);
-      this.getMessageTxn(messageStore, messageId);
-      return;
-    }
-
-    // Or, we have to open another transaction ourselves.
-    let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, messageStore) {
-      if (error) {
-        self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-        return;
-      }
-      self.getMessageTxn(messageStore, messageId);
-    }, [MESSAGE_STORE_NAME]);
-  },
-
-  // nsICursorContinueCallback
-
-  handleContinue: function handleContinue() {
-    if (DEBUG) debug("Getting next message in list");
-    this.collector.squeeze(this.notify.bind(this));
-  }
-};
-
-function GetThreadsCursor(service, callback) {
-  this.service = service;
-  this.callback = callback;
-  this.collector = new ResultsCollector();
-
-  this.handleContinue(); // Trigger first run.
-}
-GetThreadsCursor.prototype = {
-  classID: RIL_GETTHREADSCURSOR_CID,
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]),
-
-  service: null,
-  callback: null,
-  collector: null,
-
-  getThreadTxn: function getThreadTxn(threadStore, threadId) {
-    if (DEBUG) debug ("Fetching thread " + threadId);
-
-    let getRequest = threadStore.get(threadId);
-    let self = this;
-    getRequest.onsuccess = function onsuccess(event) {
-      let threadRecord = event.target.result;
-      if (DEBUG) {
-        debug("notifyCursorResult: " + JSON.stringify(threadRecord));
-      }
-      let thread =
-        gMobileMessageService.createThread(threadRecord.id,
-                                           threadRecord.participantAddresses,
-                                           threadRecord.lastTimestamp,
-                                           threadRecord.lastMessageSubject || "",
-                                           threadRecord.body,
-                                           threadRecord.unreadCount,
-                                           threadRecord.lastMessageType);
-      self.callback.notifyCursorResult(thread);
-    };
-    getRequest.onerror = function onerror(event) {
-      if (DEBUG) {
-        debug("notifyCursorError - threadId: " + threadId);
-      }
-      self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-    };
-  },
-
-  notify: function notify(txn, threadId) {
-    if (!threadId) {
-      this.callback.notifyCursorDone();
-      return;
-    }
-
-    if (threadId < 0) {
-      this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-      return;
-    }
-
-    // When filter transaction is not yet completed, we're called with current
-    // ongoing transaction object.
-    if (txn) {
-      let threadStore = txn.objectStore(THREAD_STORE_NAME);
-      this.getThreadTxn(threadStore, threadId);
-      return;
-    }
-
-    // Or, we have to open another transaction ourselves.
-    let self = this;
-    this.service.newTxn(READ_ONLY, function (error, txn, threadStore) {
-      if (error) {
-        self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
-        return;
-      }
-      self.getThreadTxn(threadStore, threadId);
-    }, [THREAD_STORE_NAME]);
-  },
-
-  // nsICursorContinueCallback
-
-  handleContinue: function handleContinue() {
-    if (DEBUG) debug("Getting next thread in list");
-    this.collector.squeeze(this.notify.bind(this));
-  }
-}
-
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileMessageDatabaseService]);
-
-function debug() {
-  dump("MobileMessageDatabaseService: " + Array.slice(arguments).join(" ") + "\n");
-}
--- a/dom/mobilemessage/src/moz.build
+++ b/dom/mobilemessage/src/moz.build
@@ -17,16 +17,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
     SOURCES += [
         'android/MobileMessageDatabaseService.cpp',
         'android/SmsService.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
     EXTRA_JS_MODULES = [
         'gonk/mms_consts.js',
         'gonk/MmsPduHelper.jsm',
+        'gonk/MobileMessageDB.jsm',
         'gonk/wap_consts.js',
         'gonk/WspPduHelper.jsm',
     ]
     EXTRA_COMPONENTS += [
         'gonk/MmsService.js',
         'gonk/MmsService.manifest',
         'gonk/MobileMessageDatabaseService.js',
         'gonk/MobileMessageDatabaseService.manifest',
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -3,17 +3,17 @@
 
 const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
 
 let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
 
 /* Push required permissions and test if |navigator.mozMobileMessage| exists.
  * Resolve if it does, reject otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   manager -- an reference to navigator.mozMobileMessage.
  *
  * Reject params: (none)
  *
  * @return A deferred promise.
  */
 let manager;
 function ensureMobileMessage() {
@@ -42,17 +42,17 @@ function ensureMobileMessage() {
   });
 
   return deferred.promise;
 }
 
 /* Send a SMS message to a single receiver.  Resolve if it succeeds, reject
  * otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   message -- the sent SmsMessage.
  *
  * Reject params:
  *   error -- a DOMError.
  *
  * @param aReceiver the address of the receiver.
  * @param aText the text body of the message.
  *
@@ -70,17 +70,17 @@ function sendSmsWithSuccess(aReceiver, a
   };
 
   return deferred.promise;
 }
 
 /* Send a MMS message with specified parameters.  Resolve if it fails, reject
  * otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   message -- the failed MmsMessage
  *
  * Reject params: (none)
  *
  * @param aMmsParameters a MmsParameters instance.
  *
  * @return A deferred promise.
  */
@@ -97,17 +97,17 @@ function sendMmsWithFailure(aMmsParamete
     deferred.reject();
   };
 
   return deferred.promise;
 }
 
 /* Retrieve messages from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   messages -- an array of {Sms,Mms}Message instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @param aFilter an optional MozSmsFilter instance.
  * @param aReverse a boolean value indicating whether the order of the messages
  *                 should be reversed.
@@ -133,31 +133,31 @@ function getMessages(aFilter, aReverse) 
   };
   cursor.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
 /* Retrieve all messages from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   messages -- an array of {Sms,Mms}Message instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @return A deferred promise.
  */
 function getAllMessages() {
   return getMessages(null, false);
 }
 
 /* Retrieve all threads from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   threads -- an array of MozMobileMessageThread instances.
  *
  * Reject params:
  *   event -- a DOMEvent
  *
  * @return A deferred promise.
  */
 function getAllThreads() {
@@ -176,17 +176,17 @@ function getAllThreads() {
   };
   cursor.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
 /* Retrieve a single specified thread from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   thread -- a MozMobileMessageThread instance.
  *
  * Reject params:
  *   event -- a DOMEvent if an error occurs in the retrieving process, or
  *            undefined if there's no such thread.
  *
  * @aThreadId a numeric value identifying the target thread.
  *
@@ -201,17 +201,17 @@ function getThreadById(aThreadId) {
         }
       }
       throw undefined;
     });
 }
 
 /* Delete messages specified from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   result -- an array of boolean values indicating whether delesion was
  *             actually performed on the message record with corresponding id.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
  * @aMessageId an array of numeric values identifying the target messages.
  *
@@ -231,17 +231,17 @@ function deleteMessagesById(aMessageIds)
   };
   request.onerror = deferred.reject.bind(deferred);
 
   return deferred.promise;
 }
 
 /* Delete messages specified from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   result -- an array of boolean values indicating whether delesion was
  *             actually performed on the message record with corresponding id.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
  * @aMessages an array of {Sms,Mms}Message instances.
  *
@@ -249,17 +249,17 @@ function deleteMessagesById(aMessageIds)
  */
 function deleteMessages(aMessages) {
   let ids = messagesToIds(aMessages);
   return deleteMessagesById(ids);
 }
 
 /* Delete all messages from database.
  *
- * Forfill params:
+ * Fulfill params:
  *   ids -- an array of numeric values identifying those deleted
  *          {Sms,Mms}Messages.
  *
  * Reject params:
  *   event -- a DOMEvent.
  *
  * @return A deferred promise.
  */
@@ -270,17 +270,17 @@ function deleteAllMessages() {
 let pendingEmulatorCmdCount = 0;
 
 /* Send emulator command with safe guard.
  *
  * We should only call |finish()| after all emulator command transactions
  * end, so here comes with the pending counter.  Resolve when the emulator
  * gives positive response, and reject otherwise.
  *
- * Forfill params:
+ * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
 function runEmulatorCmdSafe(aCommand) {
@@ -298,75 +298,155 @@ function runEmulatorCmdSafe(aCommand) {
     }
   });
 
   return deferred.promise;
 }
 
 /* Send simple text SMS to emulator.
  *
- * Forfill params:
+ * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
 function sendTextSmsToEmulator(aFrom, aText) {
   let command = "sms send " + aFrom + " " + aText;
   return runEmulatorCmdSafe(command);
 }
 
 /* Send raw SMS TPDU to emulator.
  *
- * Forfill params:
+ * Fulfill params:
  *   result -- an array of emulator response lines.
  *
  * Reject params:
  *   result -- an array of emulator response lines.
  *
  * @return A deferred promise.
  */
 function sendRawSmsToEmulator(aPdu) {
   let command = "sms pdu " + aPdu;
   return runEmulatorCmdSafe(command);
 }
 
+/* Name space for MobileMessageDB.jsm.  Only initialized after first call to
+ * newMobileMessageDB.
+ */
+let MMDB;
+
+// Create a new MobileMessageDB instance.
+function newMobileMessageDB() {
+  if (!MMDB) {
+    MMDB = Cu.import("resource://gre/modules/MobileMessageDB.jsm", {});
+    is(typeof MMDB.MobileMessageDB, "function", "MMDB.MobileMessageDB");
+  }
+
+  let mmdb = new MMDB.MobileMessageDB();
+  ok(mmdb, "MobileMessageDB instance");
+  return mmdb;
+}
+
+/* Initialize a MobileMessageDB.  Resolve if initialized with success, reject
+ * otherwise.
+ *
+ * Fulfill params: a MobileMessageDB instance.
+ * Reject params: a MobileMessageDB instance.
+ *
+ * @param aMmdb
+ *        A MobileMessageDB instance.
+ * @param aDbName
+ *        A string name for that database.
+ * @param aDbVersion
+ *        The version that MobileMessageDB should upgrade to. 0 for the lastest
+ *        version.
+ *
+ * @return A deferred promise.
+ */
+function initMobileMessageDB(aMmdb, aDbName, aDbVersion) {
+  let deferred = Promise.defer();
+
+  aMmdb.init(aDbName, aDbVersion, function(aError) {
+    if (aError) {
+      deferred.reject(aMmdb);
+    } else {
+      deferred.resolve(aMmdb);
+    }
+  });
+
+  return deferred.promise;
+}
+
+/* Close a MobileMessageDB.
+ *
+ * @return The passed MobileMessageDB instance.
+ */
+function closeMobileMessageDB(aMmdb) {
+  aMmdb.close();
+  return aMmdb;
+}
+
 /* Create a new array of id attribute of input messages.
  *
  * @param aMessages an array of {Sms,Mms}Message instances.
  *
  * @return an array of numeric values.
  */
 function messagesToIds(aMessages) {
   let ids = [];
   for (let message of aMessages) {
     ids.push(message.id);
   }
   return ids;
 }
 
+// A reference to a nsIUUIDGenerator service.
+let uuidGenerator;
+
+/* Generate a new UUID.
+ *
+ * @return A UUID string.
+ */
+function newUUID() {
+  if (!uuidGenerator) {
+    uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+                    .getService(Ci.nsIUUIDGenerator);
+    ok(uuidGenerator, "uuidGenerator");
+  }
+
+  return uuidGenerator.generateUUID().toString();
+}
+
 /* Flush permission settings and call |finish()|.
  */
 function cleanUp() {
   waitFor(function() {
     SpecialPowers.flushPermissions(function() {
       // Use ok here so that we have at least one test run.
       ok(true, "permissions flushed");
 
       finish();
     });
   }, function() {
     return pendingEmulatorCmdCount === 0;
   });
 }
 
+function startTestBase(aTestCaseMain) {
+  Promise.resolve()
+         .then(aTestCaseMain)
+         .then(cleanUp, function() {
+           ok(false, 'promise rejects during test.');
+           cleanUp();
+         });
+}
+
 function startTestCommon(aTestCaseMain) {
-  ensureMobileMessage()
-    .then(deleteAllMessages)
-    .then(aTestCaseMain)
-    .then(deleteAllMessages)
-    .then(cleanUp, function() {
-      ok(false, 'promise rejects during test.');
-      cleanUp();
-    });
+  startTestBase(function() {
+    return ensureMobileMessage()
+      .then(deleteAllMessages)
+      .then(aTestCaseMain)
+      .then(deleteAllMessages);
+  });
 }
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -34,10 +34,11 @@ qemu = true
 [test_getsegmentinfofortext.js]
 [test_phone_number_normalization.js]
 [test_invalid_address.js]
 [test_mmsmessage_attachments.js]
 [test_getthreads.js]
 [test_smsc_address.js]
 [test_dsds_default_service_id.js]
 [test_thread_subject.js]
+[test_mmdb_new.js]
 [test_mmdb_setmessagedeliverybyid_sms.js]
 [test_replace_short_message_type.js]
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_new.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+startTestBase(function testCaseMain() {
+  log("Test init MobileMessageDB");
+
+  let mmdb = newMobileMessageDB();
+  let dbName = "test_mmdb_new";
+  let dbVersion = 0;
+  let check = function() {
+    is(mmdb.dbName, dbName, "dbName");
+    if (!dbVersion) {
+      ok(mmdb.dbVersion, "dbVersion");
+      dbVersion = mmdb.dbVersion;
+    } else {
+      is(mmdb.dbVersion, dbVersion, "dbVersion");
+    }
+  };
+
+  return initMobileMessageDB(mmdb, dbName, dbVersion)
+    .then(check)
+    .then(closeMobileMessageDB.bind(null, mmdb))
+    .then(check)
+    .then(function() {
+      log("Test re-init and close.");
+      return initMobileMessageDB(mmdb, dbName, dbVersion);
+    })
+    .then(check)
+    .then(closeMobileMessageDB.bind(null, mmdb))
+    .then(check);
+});
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -182,25 +182,16 @@ abstract public class BrowserApp extends
     private Integer mTargetTabForEditingMode = null;
 
     // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
     // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
     // both the web content and the HomePager will be hidden. This flag is used to prevent the
     // race by determining if the web content should be hidden at the animation's end.
     private boolean mHideWebContentOnAnimationEnd = false;
 
-    private SiteIdentityPopup mSiteIdentityPopup;
-
-    public SiteIdentityPopup getSiteIdentityPopup() {
-        if (mSiteIdentityPopup == null)
-            mSiteIdentityPopup = new SiteIdentityPopup(this);
-
-        return mSiteIdentityPopup;
-    }
-
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         if (tab == null) {
             // Only RESTORED is allowed a null tab: it's the only event that
             // isn't tied to a specific tab.
             if (msg != Tabs.TabEvents.RESTORED) {
                 throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
             }
@@ -213,19 +204,16 @@ abstract public class BrowserApp extends
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     maybeCancelFaviconLoad(tab);
                 }
                 // fall through
             case SELECTED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     updateHomePagerForTab(tab);
 
-                    if (mSiteIdentityPopup != null)
-                        mSiteIdentityPopup.dismiss();
-
                     final TabsPanel.Panel panel = tab.isPrivate()
                                                 ? TabsPanel.Panel.PRIVATE_TABS
                                                 : TabsPanel.Panel.NORMAL_TABS;
                     // Delay calling showTabs so that it does not modify the mTabsChangedListeners
                     // array while we are still iterating through the array.
                     ThreadUtils.postToUiThread(new Runnable() {
                         @Override
                         public void run() {
@@ -612,18 +600,17 @@ abstract public class BrowserApp extends
             super.onBackPressed();
             return;
         }
 
         if (dismissEditingMode()) {
             return;
         }
 
-        if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
-            mSiteIdentityPopup.dismiss();
+        if (mBrowserToolbar.onBackPressed()) {
             return;
         }
 
         if (mActionMode != null) {
             endActionModeCompat();
             return;
         }
 
@@ -1042,19 +1029,17 @@ abstract public class BrowserApp extends
         });
     }
 
     @Override
     public void refreshChrome() {
         invalidateOptionsMenu();
         updateSideBarState();
         mTabsPanel.refresh();
-        if (mSiteIdentityPopup != null) {
-            mSiteIdentityPopup.dismiss();
-        }
+        mBrowserToolbar.refresh();
     }
 
     @Override
     public boolean hasTabsSideBar() {
         return (mTabsPanel != null && mTabsPanel.isSideBar());
     }
 
     private void updateSideBarState() {
--- a/mobile/android/base/DoorHangerPopup.java
+++ b/mobile/android/base/DoorHangerPopup.java
@@ -2,16 +2,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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.widget.ArrowPopup;
+import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.prompts.PromptInput;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.os.Build;
 import android.util.Log;
@@ -29,18 +30,18 @@ public class DoorHangerPopup extends Arr
 
     // Stores a set of all active DoorHanger notifications. A DoorHanger is
     // uniquely identified by its tabId and value.
     private HashSet<DoorHanger> mDoorHangers;
 
     // Whether or not the doorhanger popup is disabled.
     private boolean mDisabled;
 
-    DoorHangerPopup(GeckoApp activity, View anchor) {
-        super(activity, anchor);
+    DoorHangerPopup(GeckoApp activity) {
+        super(activity);
 
         mDoorHangers = new HashSet<DoorHanger>();
 
         registerEventListener("Doorhanger:Add");
         registerEventListener("Doorhanger:Remove");
         Tabs.registerOnTabsChangedListener(this);
     }
 
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1369,17 +1369,17 @@ public abstract class GeckoApp
         final String hint = getResources().getString(R.string.url_bar_default_text);
         urlBar.setHint(hint);
 
         // Allow onConfigurationChanged to take care of the rest.
         onConfigurationChanged(getResources().getConfiguration());
     }
 
     protected void initializeChrome() {
-        mDoorHangerPopup = new DoorHangerPopup(this, null);
+        mDoorHangerPopup = new DoorHangerPopup(this);
         mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
 
         if (mCameraView == null) {
             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                 mCameraView = new SurfaceView(this);
                 ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
             } else {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/SiteIdentity.java
@@ -0,0 +1,110 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import org.json.JSONObject;
+
+import android.text.TextUtils;
+
+public class SiteIdentity {
+    private SecurityMode mSecurityMode;
+    private String mHost;
+    private String mOwner;
+    private String mSupplemental;
+    private String mVerifier;
+    private String mEncrypted;
+
+    // The order of the items here correspond to image
+    // levels in site_security_level.xml
+    public enum SecurityMode {
+        UNKNOWN("unknown"),
+        VERIFIED("verified"),
+        IDENTIFIED("identified"),
+        MIXED_CONTENT_BLOCKED("mixed_content_blocked"),
+        MIXED_CONTENT_LOADED("mixed_content_loaded");
+
+        private final String mId;
+
+        private SecurityMode(String id) {
+            mId = id;
+        }
+
+        public static SecurityMode fromString(String id) {
+            if (id == null) {
+                throw new IllegalArgumentException("Can't convert null String to SiteIdentity");
+            }
+
+            for (SecurityMode mode : SecurityMode.values()) {
+                if (TextUtils.equals(mode.mId, id.toLowerCase())) {
+                    return mode;
+                }
+            }
+
+            throw new IllegalArgumentException("Could not convert String id to SiteIdentity");
+        }
+
+        @Override
+        public String toString() {
+            return mId;
+        }
+    }
+
+    public SiteIdentity() {
+        reset(SecurityMode.UNKNOWN);
+    }
+
+    private void reset(SecurityMode securityMode) {
+        mSecurityMode = securityMode;
+        mHost = null;
+        mOwner = null;
+        mSupplemental = null;
+        mVerifier = null;
+        mEncrypted = null;
+    }
+
+    void update(JSONObject identityData) {
+        try {
+            mSecurityMode = SecurityMode.fromString(identityData.getString("mode"));
+        } catch (Exception e) {
+            reset(SecurityMode.UNKNOWN);
+            return;
+        }
+
+        try {
+            mHost = identityData.getString("host");
+            mOwner = identityData.getString("owner");
+            mSupplemental = identityData.optString("supplemental", null);
+            mVerifier = identityData.getString("verifier");
+            mEncrypted = identityData.getString("encrypted");
+        } catch (Exception e) {
+            reset(mSecurityMode);
+        }
+    }
+
+    public SecurityMode getSecurityMode() {
+        return mSecurityMode;
+    }
+
+    public String getHost() {
+        return mHost;
+    }
+
+    public String getOwner() {
+        return mOwner;
+    }
+
+    public String getSupplemental() {
+        return mSupplemental;
+    }
+
+    public String getVerifier() {
+        return mVerifier;
+    }
+
+    public String getEncrypted() {
+        return mEncrypted;
+    }
+}
\ No newline at end of file
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -1,15 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.home.HomePager;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -40,17 +41,17 @@ public class Tab {
     private String mBaseDomain;
     private String mUserSearch;
     private String mTitle;
     private Bitmap mFavicon;
     private String mFaviconUrl;
     private int mFaviconSize;
     private boolean mHasFeeds;
     private boolean mHasOpenSearch;
-    private JSONObject mIdentityData;
+    private SiteIdentity mSiteIdentity;
     private boolean mReaderEnabled;
     private BitmapDrawable mThumbnail;
     private int mHistoryIndex;
     private int mHistorySize;
     private int mParentId;
     private HomePager.Page mAboutHomePage;
     private boolean mExternal;
     private boolean mBookmark;
@@ -96,17 +97,17 @@ public class Tab {
         mParentId = parentId;
         mAboutHomePage = HomePager.Page.TOP_SITES;
         mTitle = title == null ? "" : title;
         mFavicon = null;
         mFaviconUrl = null;
         mFaviconSize = 0;
         mHasFeeds = false;
         mHasOpenSearch = false;
-        mIdentityData = null;
+        mSiteIdentity = new SiteIdentity();
         mReaderEnabled = false;
         mEnteringReaderMode = false;
         mThumbnail = null;
         mHistoryIndex = -1;
         mHistorySize = 0;
         mBookmark = false;
         mReadingListItem = false;
         mFaviconLoadId = 0;
@@ -241,27 +242,22 @@ public class Tab {
     public boolean hasFeeds() {
         return mHasFeeds;
     }
 
     public boolean hasOpenSearch() {
         return mHasOpenSearch;
     }
 
-    public String getSecurityMode() {
-        try {
-            return mIdentityData.getString("mode");
-        } catch (Exception e) {
-            // If mIdentityData is null, or we get a JSONException
-            return SiteIdentityPopup.UNKNOWN;
-        }
+    public SecurityMode getSecurityMode() {
+        return mSiteIdentity.getSecurityMode();
     }
 
-    public JSONObject getIdentityData() {
-        return mIdentityData;
+    public SiteIdentity getSiteIdentity() {
+        return mSiteIdentity;
     }
 
     public boolean getReaderEnabled() {
         return mReaderEnabled;
     }
 
     public boolean isBookmark() {
         return mBookmark;
@@ -410,17 +406,17 @@ public class Tab {
         mHasFeeds = hasFeeds;
     }
 
     public void setHasOpenSearch(boolean hasOpenSearch) {
         mHasOpenSearch = hasOpenSearch;
     }
 
     public void updateIdentityData(JSONObject identityData) {
-        mIdentityData = identityData;
+        mSiteIdentity.update(identityData);
     }
 
     public void setReaderEnabled(boolean readerEnabled) {
         mReaderEnabled = readerEnabled;
         Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.MENU_UPDATED);
     }
 
     void updateBookmark() {
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -115,29 +115,17 @@ public class LayerView extends FrameLayo
             mOverscroll = new OverscrollEdgeEffect(this);
         } else {
             mOverscroll = null;
         }
         Tabs.registerOnTabsChangedListener(this);
     }
 
     public LayerView(Context context) {
-        super(context);
-
-        mGLController = GLController.getInstance(this);
-        mPaintState = PAINT_START;
-        mBackgroundColor = Color.WHITE;
-
-        mTouchInterceptors = new ArrayList<TouchEventInterceptor>();
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            mOverscroll = new OverscrollEdgeEffect(this);
-        } else {
-            mOverscroll = null;
-        }
-        Tabs.registerOnTabsChangedListener(this);
+        this(context, null);
     }
 
     public void initializeView(EventDispatcher eventDispatcher) {
         mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher);
         if (mOverscroll != null) {
             mLayerClient.setOverscrollHandler(mOverscroll);
         }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -115,17 +115,16 @@ gbjar.sources += [
     'db/DBUtils.java',
     'db/FormHistoryProvider.java',
     'db/LocalBrowserDB.java',
     'db/PasswordsProvider.java',
     'db/PerProfileContentProvider.java',
     'db/PerProfileDatabases.java',
     'db/TabsProvider.java',
     'Distribution.java',
-    'DoorHanger.java',
     'DoorHangerPopup.java',
     'EditBookmarkDialog.java',
     'favicons/cache/FaviconCache.java',
     'favicons/cache/FaviconCacheElement.java',
     'favicons/cache/FaviconsForURL.java',
     'favicons/Favicons.java',
     'favicons/LoadFaviconTask.java',
     'favicons/OnFaviconLoadedListener.java',
@@ -274,17 +273,17 @@ gbjar.sources += [
     'ReaderModeUtils.java',
     'ReferrerReceiver.java',
     'RemoteTabs.java',
     'Restarter.java',
     'ScrollAnimator.java',
     'ServiceNotificationClient.java',
     'SessionParser.java',
     'SharedPreferencesHelper.java',
-    'SiteIdentityPopup.java',
+    'SiteIdentity.java',
     'SmsManager.java',
     'sqlite/ByteBufferInputStream.java',
     'sqlite/MatrixBlobCursor.java',
     'sqlite/SQLiteBridge.java',
     'sqlite/SQLiteBridgeException.java',
     'SurfaceBits.java',
     'Tab.java',
     'Tabs.java',
@@ -297,16 +296,17 @@ gbjar.sources += [
     'ThumbnailHelper.java',
     'toolbar/AutocompleteHandler.java',
     'toolbar/BackButton.java',
     'toolbar/BrowserToolbar.java',
     'toolbar/CanvasDelegate.java',
     'toolbar/ForwardButton.java',
     'toolbar/PageActionLayout.java',
     'toolbar/ShapedButton.java',
+    'toolbar/SiteIdentityPopup.java',
     'toolbar/TabCounter.java',
     'toolbar/ToolbarEditLayout.java',
     'toolbar/ToolbarEditText.java',
     'TouchEventInterceptor.java',
     'updater/UpdateService.java',
     'updater/UpdateServiceHelper.java',
     'VideoPlayer.java',
     'WebAppAllocator.java',
@@ -316,16 +316,17 @@ gbjar.sources += [
     'widget/AnimatedHeightLayout.java',
     'widget/ArrowPopup.java',
     'widget/BasicColorPicker.java',
     'widget/ButtonToast.java',
     'widget/CheckableLinearLayout.java',
     'widget/ClickableWhenDisabledEditText.java',
     'widget/DateTimePicker.java',
     'widget/Divider.java',
+    'widget/DoorHanger.java',
     'widget/FaviconView.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
     'widget/IconTabWidget.java',
     'widget/TabRow.java',
     'widget/ThumbnailView.java',
     'widget/TwoWayView.java',
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -7,17 +7,18 @@ package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.SiteIdentityPopup;
+import org.mozilla.gecko.SiteIdentity;
+import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.MenuPopup;
 import org.mozilla.gecko.PrefsHelper;
@@ -137,16 +138,18 @@ public class BrowserToolbar extends Geck
 
     private OnActivateListener mActivateListener;
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
     private OnStartEditingListener mStartEditingListener;
     private OnStopEditingListener mStopEditingListener;
 
+    private SiteIdentityPopup mSiteIdentityPopup;
+
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
     private boolean mShowSiteSecurity;
     private boolean mSpinnerVisible;
 
     private boolean mIsEditing;
     private boolean mAnimatingEntry;
@@ -291,17 +294,19 @@ public class BrowserToolbar extends Geck
                 mFavicon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             }
             mFavicon.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         }
         mFaviconSize = Math.round(res.getDimension(R.dimen.browser_toolbar_favicon_size));
 
         mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
         mSiteSecurityVisible = (mSiteSecurity.getVisibility() == View.VISIBLE);
-        mActivity.getSiteIdentityPopup().setAnchor(mSiteSecurity);
+
+        mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
+        mSiteIdentityPopup.setAnchor(mSiteSecurity);
 
         mProgressSpinner = AnimationUtils.loadAnimation(mActivity, R.anim.progress_spinner);
 
         mStop = (ImageButton) findViewById(R.id.stop);
         mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
 
         mMenu = (GeckoImageButton) findViewById(R.id.menu);
         mMenuIcon = (GeckoImageView) findViewById(R.id.menu_icon);
@@ -420,24 +425,26 @@ public class BrowserToolbar extends Geck
         });
 
         Button.OnClickListener faviconListener = new Button.OnClickListener() {
             @Override
             public void onClick(View view) {
                 if (mSiteSecurity.getVisibility() != View.VISIBLE)
                     return;
 
-                JSONObject identityData = Tabs.getInstance().getSelectedTab().getIdentityData();
-                if (identityData == null) {
+                final Tab tab = Tabs.getInstance().getSelectedTab();
+
+                final SiteIdentity siteIdentity = tab.getSiteIdentity();
+                if (siteIdentity.getSecurityMode() == SecurityMode.UNKNOWN) {
                     Log.e(LOGTAG, "Selected tab has no identity data");
                     return;
                 }
-                SiteIdentityPopup siteIdentityPopup = mActivity.getSiteIdentityPopup();
-                siteIdentityPopup.updateIdentity(identityData);
-                siteIdentityPopup.show();
+
+                mSiteIdentityPopup.updateIdentity(siteIdentity);
+                mSiteIdentityPopup.show();
             }
         };
 
         mFavicon.setOnClickListener(faviconListener);
         mSiteSecurity.setOnClickListener(faviconListener);
 
         mStop.setOnClickListener(new Button.OnClickListener() {
             @Override
@@ -477,16 +484,24 @@ public class BrowserToolbar extends Geck
                 @Override
                 public void onClick(View view) {
                     mActivity.openOptionsMenu();
                 }
             });
         }
     }
 
+    public void refresh() {
+        dismissSiteIdentityPopup();
+    }
+
+    public boolean onBackPressed() {
+        return dismissSiteIdentityPopup();
+    }
+
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return false;
         }
 
         // Galaxy Note sends key events for the stylus that are outside of the
         // valid keyCode range (see bug 758427)
         if (keyCode > KeyEvent.getMaxKeyCode()) {
@@ -553,16 +568,17 @@ public class BrowserToolbar extends Geck
         switch (msg) {
             case ADDED:
             case CLOSED:
                 updateTabCount(tabs.getDisplayCount());
                 break;
             case RESTORED:
                 // TabCount fixup after OOM
             case SELECTED:
+                dismissSiteIdentityPopup();
                 updateTabCount(tabs.getDisplayCount());
                 mSwitchingTabs = true;
                 // Fall through.
         }
 
         if (tabs.isSelectedTab(tab)) {
             switch (msg) {
                 case TITLE:
@@ -589,17 +605,17 @@ public class BrowserToolbar extends Geck
 
                 case SELECTED:
                 case LOAD_ERROR:
                     updateTitle();
                     // Fall through.
                 case LOCATION_CHANGE:
                     // A successful location change will cause Tab to notify
                     // us of a title change, so we don't update the title here.
-                    refresh();
+                    refreshState();
                     break;
 
                 case CLOSED:
                 case ADDED:
                     updateBackButton(tab);
                     updateForwardButton(tab);
                     break;
 
@@ -664,16 +680,25 @@ public class BrowserToolbar extends Geck
 
     @Override
     public void onAnimationEnd(Animation animation) {
         if (animation.equals(mTitleSlideRight)) {
             mSiteSecurity.startAnimation(mLockFadeIn);
         }
     }
 
+    private boolean dismissSiteIdentityPopup() {
+        if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
+            mSiteIdentityPopup.dismiss();
+            return true;
+        }
+
+        return false;
+    }
+
     private int getUrlBarEntryTranslation() {
         return getWidth() - mUrlBarEntry.getRight();
     }
 
     private int getUrlBarCurveTranslation() {
         return getWidth() - mTabs.getLeft();
     }
 
@@ -952,21 +977,20 @@ public class BrowserToolbar extends Geck
 
         if (image != null) {
             image = Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, false);
             mFavicon.setImageBitmap(image);
         } else {
             mFavicon.setImageDrawable(null);
         }
     }
-
-    private void setSecurityMode(String mode) {
-        int imageLevel = SiteIdentityPopup.getSecurityImageLevel(mode);
-        mSiteSecurity.setImageLevel(imageLevel);
-        mShowSiteSecurity = (imageLevel != SiteIdentityPopup.LEVEL_UKNOWN);
+    
+    private void setSecurityMode(SecurityMode mode) {
+        mSiteSecurity.setImageLevel(mode.ordinal());
+        mShowSiteSecurity = (mode != SecurityMode.UNKNOWN);
 
         setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
     }
 
     public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) {
         if (!tabsAreShown) {
             PropertyAnimator buttonsAnimator =
                     new PropertyAnimator(animator.getDuration(), sButtonsInterpolator);
@@ -1529,17 +1553,17 @@ public class BrowserToolbar extends Geck
     public void show() {
         setVisibility(View.VISIBLE);
     }
 
     public void hide() {
         setVisibility(View.GONE);
     }
 
-    private void refresh() {
+    private void refreshState() {
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
             setFavicon(tab.getFavicon());
             setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
             setSecurityMode(tab.getSecurityMode());
             setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
             updateBackButton(tab);
             updateForwardButton(tab);
rename from mobile/android/base/SiteIdentityPopup.java
rename to mobile/android/base/toolbar/SiteIdentityPopup.java
--- a/mobile/android/base/SiteIdentityPopup.java
+++ b/mobile/android/base/toolbar/SiteIdentityPopup.java
@@ -1,15 +1,23 @@
 /* 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/. */
 
-package org.mozilla.gecko;
+package org.mozilla.gecko.toolbar;
 
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.SiteIdentity;
+import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.widget.ArrowPopup;
+import org.mozilla.gecko.widget.DoorHanger;
+import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.res.Resources;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -17,66 +25,39 @@ import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 /**
  * SiteIdentityPopup is a singleton class that displays site identity data in
  * an arrow panel popup hanging from the lock icon in the browser toolbar.
  */
-public class SiteIdentityPopup extends ArrowPopup
-                               implements DoorHanger.OnButtonClickListener {
+public class SiteIdentityPopup extends ArrowPopup {
     private static final String LOGTAG = "GeckoSiteIdentityPopup";
 
-    public static final String UNKNOWN = "unknown";
-    public static final String VERIFIED = "verified";
-    public static final String IDENTIFIED = "identified";
-    public static final String MIXED_CONTENT_BLOCKED = "mixed_content_blocked";
-    public static final String MIXED_CONTENT_LOADED = "mixed_content_loaded";
-
-    // Security states corresponding to image levels in site_security_level.xml
-    public static final int LEVEL_UKNOWN = 0;
-    public static final int LEVEL_IDENTIFIED = 1;
-    public static final int LEVEL_VERIFIED = 2;
-    public static final int LEVEL_MIXED_CONTENT_BLOCKED = 3;
-    public static final int LEVEL_MIXED_CONTENT_LOADED = 4;
-
     // FIXME: Update this URL for mobile. See bug 885923.
     private static final String MIXED_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/how-does-content-isnt-secure-affect-my-safety";
 
     private Resources mResources;
 
     private LinearLayout mIdentity;
     private TextView mHost;
     private TextView mOwner;
     private TextView mVerifier;
 
     private DoorHanger mMixedContentNotification;
 
-    SiteIdentityPopup(BrowserApp aActivity) {
-        super(aActivity, null);
-
-        mResources = aActivity.getResources();
-    }
+    private final OnButtonClickListener mButtonClickListener;
 
-    public static int getSecurityImageLevel(String mode) {
-        if (IDENTIFIED.equals(mode)) {
-            return LEVEL_IDENTIFIED;
-        }
-        if (VERIFIED.equals(mode)) {
-            return LEVEL_VERIFIED;
-        }
-        if (MIXED_CONTENT_BLOCKED.equals(mode)) {
-            return LEVEL_MIXED_CONTENT_BLOCKED;
-        }
-        if (MIXED_CONTENT_LOADED.equals(mode)) {
-            return LEVEL_MIXED_CONTENT_LOADED;
-        }
-        return LEVEL_UKNOWN;
+    SiteIdentityPopup(BrowserApp activity) {
+        super(activity);
+
+        mResources = activity.getResources();
+        mButtonClickListener = new PopupButtonListener();
     }
 
     @Override
     protected void init() {
         super.init();
 
         // Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
         // which may reshow the popup (see bug 785156)
@@ -86,60 +67,44 @@ public class SiteIdentityPopup extends A
         mIdentity = (LinearLayout) inflater.inflate(R.layout.site_identity, null);
         mContent.addView(mIdentity);
 
         mHost = (TextView) mIdentity.findViewById(R.id.host);
         mOwner = (TextView) mIdentity.findViewById(R.id.owner);
         mVerifier = (TextView) mIdentity.findViewById(R.id.verifier);
     }
 
-    private void setIdentity(JSONObject identityData) {
-        try {
-            String host = identityData.getString("host");
-            mHost.setText(host);
+    private void setIdentity(SiteIdentity siteIdentity) {
+        if (siteIdentity.getSecurityMode() == SecurityMode.MIXED_CONTENT_LOADED) {
+            // Hide the identity data if there isn't valid site identity data.
+            // Set some top padding on the popup content to create a of light blue
+            // between the popup arrow and the mixed content notification.
+            mContent.setPadding(0, (int) mResources.getDimension(R.dimen.identity_padding_top), 0, 0);
+            mIdentity.setVisibility(View.GONE);
+        } else {
+            mHost.setText(siteIdentity.getHost());
 
-            String owner = identityData.getString("owner");
+            String owner = siteIdentity.getOwner();
 
             // Supplemental data is optional.
-            String supplemental = identityData.optString("supplemental");
+            final String supplemental = siteIdentity.getSupplemental();
             if (!TextUtils.isEmpty(supplemental)) {
                 owner += "\n" + supplemental;
             }
             mOwner.setText(owner);
 
-            String verifier = identityData.getString("verifier");
-            String encrypted = identityData.getString("encrypted");
+            final String verifier = siteIdentity.getVerifier();
+            final String encrypted = siteIdentity.getEncrypted();
             mVerifier.setText(verifier + "\n" + encrypted);
 
             mContent.setPadding(0, 0, 0, 0);
             mIdentity.setVisibility(View.VISIBLE);
-
-        } catch (JSONException e) {
-            // Hide the identity data if there isn't valid site identity data.
-            // Set some top padding on the popup content to create a of light blue
-            // between the popup arrow and the mixed content notification.
-            mContent.setPadding(0, (int) mResources.getDimension(R.dimen.identity_padding_top), 0, 0);
-            mIdentity.setVisibility(View.GONE);
         }
     }
 
-    @Override
-    public void onButtonClick(DoorHanger dh, String tag) {
-        try {
-            JSONObject data = new JSONObject();
-            data.put("allowMixedContent", tag.equals("disable"));
-            GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
-            GeckoAppShell.sendEventToGecko(e);
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Exception creating message to enable/disable mixed content blocking", e);
-        }
-
-        dismiss();
-    }
-
     private void addMixedContentNotification(boolean blocked) {
         // Remove any exixting mixed content notification.
         removeMixedContentNotification();
         mMixedContentNotification = new DoorHanger(mActivity, DoorHanger.Theme.DARK);
 
         String message;
         if (blocked) {
             message = mActivity.getString(R.string.blocked_mixed_content_message_top) + "\n\n" +
@@ -147,58 +112,71 @@ public class SiteIdentityPopup extends A
         } else {
             message = mActivity.getString(R.string.loaded_mixed_content_message);
         }
         mMixedContentNotification.setMessage(message);
         mMixedContentNotification.addLink(mActivity.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
 
         if (blocked) {
             mMixedContentNotification.setIcon(R.drawable.shield_doorhanger);
-            mMixedContentNotification.addButton(mActivity.getString(R.string.disable_protection), "disable", this);
-            mMixedContentNotification.addButton(mActivity.getString(R.string.keep_blocking), "keepBlocking", this);
+            mMixedContentNotification.addButton(mActivity.getString(R.string.disable_protection),
+                                                "disable", mButtonClickListener);
+            mMixedContentNotification.addButton(mActivity.getString(R.string.keep_blocking),
+                                                "keepBlocking", mButtonClickListener);
         } else {
             mMixedContentNotification.setIcon(R.drawable.warning_doorhanger);
-            mMixedContentNotification.addButton(mActivity.getString(R.string.enable_protection), "enable", this);
+            mMixedContentNotification.addButton(mActivity.getString(R.string.enable_protection),
+                                                "enable", mButtonClickListener);
         }
 
         mContent.addView(mMixedContentNotification);
     }
 
     private void removeMixedContentNotification() {
         if (mMixedContentNotification != null) {
             mContent.removeView(mMixedContentNotification);
             mMixedContentNotification = null;
         }
     }
 
     /*
      * @param identityData A JSONObject that holds the current tab's identity data.
      */
-    public void updateIdentity(JSONObject identityData) {
-        String mode;
-        try {
-            mode = identityData.getString("mode");
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "Exception trying to get identity mode", e);
-            return;
-        }
-
-        if (UNKNOWN.equals(mode)) {
+    void updateIdentity(SiteIdentity siteIdentity) {
+        final SecurityMode mode = siteIdentity.getSecurityMode();
+        if (mode == SecurityMode.UNKNOWN) {
             Log.e(LOGTAG, "Can't show site identity popup in non-identified state");
             return;
         }
 
         if (!mInflated)
             init();
 
-        setIdentity(identityData);
+        setIdentity(siteIdentity);
 
-        if (MIXED_CONTENT_BLOCKED.equals(mode) || MIXED_CONTENT_LOADED.equals(mode)) {
-            addMixedContentNotification(MIXED_CONTENT_BLOCKED.equals(mode));
+        if (mode == SecurityMode.MIXED_CONTENT_LOADED ||
+            mode == SecurityMode.MIXED_CONTENT_BLOCKED) {
+            addMixedContentNotification(mode == SecurityMode.MIXED_CONTENT_BLOCKED);
         }
     }
 
     @Override
     public void dismiss() {
         super.dismiss();
         removeMixedContentNotification();
     }
+
+    private class PopupButtonListener implements OnButtonClickListener {
+        @Override
+        public void onButtonClick(DoorHanger dh, String tag) {
+            try {
+                JSONObject data = new JSONObject();
+                data.put("allowMixedContent", tag.equals("disable"));
+                GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
+                GeckoAppShell.sendEventToGecko(e);
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Exception creating message to enable/disable mixed content blocking", e);
+            }
+
+            dismiss();
+        }
+    }
 }
--- a/mobile/android/base/widget/ArrowPopup.java
+++ b/mobile/android/base/widget/ArrowPopup.java
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.widget;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.HardwareUtils;
 
+import android.content.res.Resources;
 import android.graphics.drawable.BitmapDrawable;
 import android.os.Build;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -27,31 +28,36 @@ public class ArrowPopup extends PopupWin
     private ImageView mArrow;
 
     private int mArrowWidth;
     private int mYOffset;
 
     protected LinearLayout mContent;
     protected boolean mInflated;
 
-    public ArrowPopup(GeckoApp aActivity, View aAnchor) {
-        super(aActivity);
-        mActivity = aActivity;
-        mAnchor = aAnchor;
+    public ArrowPopup(GeckoApp aActivity) {
+        this(aActivity, null);
+    }
+
+    public ArrowPopup(GeckoApp activity, View anchor) {
+        super(activity);
+        mActivity = activity;
+        mAnchor = anchor;
 
         mInflated = false;
 
-        mArrowWidth = aActivity.getResources().getDimensionPixelSize(R.dimen.menu_popup_arrow_width);
-        mYOffset = aActivity.getResources().getDimensionPixelSize(R.dimen.menu_popup_offset);
+        final Resources res = activity.getResources();
+        mArrowWidth = res.getDimensionPixelSize(R.dimen.menu_popup_arrow_width);
+        mYOffset = res.getDimensionPixelSize(R.dimen.menu_popup_offset);
 
         setAnimationStyle(R.style.PopupAnimation);
     }
 
-    public void setAnchor(View aAnchor) {
-        mAnchor = aAnchor;
+    public void setAnchor(View anchor) {
+        mAnchor = anchor;
     }
 
     protected void init() {
         setBackgroundDrawable(new BitmapDrawable());
         setOutsideTouchable(true);
 
         setWindowLayoutMode(HardwareUtils.isTablet() ? ViewGroup.LayoutParams.WRAP_CONTENT : ViewGroup.LayoutParams.FILL_PARENT,
             ViewGroup.LayoutParams.WRAP_CONTENT);
rename from mobile/android/base/DoorHanger.java
rename to mobile/android/base/widget/DoorHanger.java
--- a/mobile/android/base/DoorHanger.java
+++ b/mobile/android/base/widget/DoorHanger.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
-package org.mozilla.gecko;
+package org.mozilla.gecko.widget;
 
-import org.mozilla.gecko.widget.Divider;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.prompts.PromptInput;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -31,17 +32,17 @@ import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.Spinner;
 import android.widget.SpinnerAdapter;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.List;
 
-class DoorHanger extends LinearLayout {
+public class DoorHanger extends LinearLayout {
     private static final String LOGTAG = "GeckoDoorHanger";
 
     private static int sInputPadding = -1;
     private static int sSpinnerTextColor = -1;
     private static int sSpinnerTextSize = -1;
 
     private static LayoutParams sButtonParams;
     static {
@@ -68,30 +69,30 @@ class DoorHanger extends LinearLayout {
 
     private int mPersistence = 0;
     private boolean mPersistWhileVisible = false;
     private long mTimeout = 0;
 
     // Color used for dividers above and between buttons.
     private int mDividerColor;
 
-    static enum Theme {
+    public static enum Theme {
         LIGHT,
         DARK
     }
 
-    interface OnButtonClickListener {
+    public interface OnButtonClickListener {
         public void onButtonClick(DoorHanger dh, String tag);
     }
 
-    DoorHanger(Context context, Theme theme) {
+    public DoorHanger(Context context, Theme theme) {
         this(context, 0, null, theme);
     }
 
-    DoorHanger(Context context, int tabId, String value) {
+    public DoorHanger(Context context, int tabId, String value) {
         this(context, tabId, value, Theme.LIGHT);
     }
 
     private DoorHanger(Context context, int tabId, String value, Theme theme) {
         super(context);
 
         mTabId = tabId;
         mValue = value;
@@ -128,50 +129,50 @@ class DoorHanger extends LinearLayout {
             mDividerColor = mResources.getColor(R.color.doorhanger_divider_dark);
 
             // Set a dark background, and use a smaller text size for dark-themed DoorHangers.
             setBackgroundColor(mResources.getColor(R.color.doorhanger_background_dark));
             mTextView.setTextSize(mResources.getDimension(R.dimen.doorhanger_textsize_small));
         }
     }
 
-    int getTabId() {
+    public int getTabId() {
         return mTabId;
     }
 
-    String getValue() {
+    public String getValue() {
         return mValue;
     }
 
-    List<PromptInput> getInputs() {
+    public List<PromptInput> getInputs() {
         return mInputs;
     }
 
-    CheckBox getCheckBox() {
+    public CheckBox getCheckBox() {
         return mCheckBox;
     }
 
-    void showDivider() {
+    public void showDivider() {
         mDivider.setVisibility(View.VISIBLE);
     }
 
-    void hideDivider() {
+    public void hideDivider() {
         mDivider.setVisibility(View.GONE);
     }
 
-    void setMessage(String message) {
+    public void setMessage(String message) {
         mTextView.setText(message);
     }
 
-    void setIcon(int resId) {
+    public void setIcon(int resId) {
         mIcon.setImageResource(resId);
         mIcon.setVisibility(View.VISIBLE);
     }
 
-    void addLink(String label, String url, String delimiter) {
+    public void addLink(String label, String url, String delimiter) {
         String title = mTextView.getText().toString();
         SpannableString titleWithLink = new SpannableString(title + delimiter + label);
         URLSpan linkSpan = new URLSpan(url) {
             @Override
             public void onClick(View view) {
                 Tabs.getInstance().loadUrlInTab(getURL());
             }
         };
@@ -180,17 +181,17 @@ class DoorHanger extends LinearLayout {
         ForegroundColorSpan colorSpan = new ForegroundColorSpan(mTextView.getCurrentTextColor());
         titleWithLink.setSpan(colorSpan, 0, title.length(), 0);
 
         titleWithLink.setSpan(linkSpan, title.length() + 1, titleWithLink.length(), 0);
         mTextView.setText(titleWithLink);
         mTextView.setMovementMethod(LinkMovementMethod.getInstance());
     }
 
-    void addButton(final String text, final String tag, final OnButtonClickListener listener) {
+    public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
         final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
         button.setText(text);
         button.setTag(tag);
 
         button.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 listener.onButtonClick(DoorHanger.this, tag);
@@ -210,17 +211,17 @@ class DoorHanger extends LinearLayout {
             divider.setOrientation(Divider.Orientation.VERTICAL);
             divider.setBackgroundColor(mDividerColor);
             mChoicesLayout.addView(divider);
         }
 
         mChoicesLayout.addView(button, sButtonParams);
     }
 
-    void setOptions(final JSONObject options) {
+    public void setOptions(final JSONObject options) {
         final int persistence = options.optInt("persistence");
         if (persistence > 0) {
             mPersistence = persistence;
         }
 
         mPersistWhileVisible = options.optBoolean("persistWhileVisible");
 
         final long timeout = options.optLong("timeout");
@@ -318,17 +319,17 @@ class DoorHanger extends LinearLayout {
 
 
     /*
      * Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
      *
      * @param isShowing Whether or not this doorhanger is currently visible to the user.
      *                 (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden)
      */
-    boolean shouldRemove(boolean isShowing) {
+    public boolean shouldRemove(boolean isShowing) {
         if (mPersistWhileVisible && isShowing) {
             // We still want to decrement mPersistence, even if the popup is showing
             if (mPersistence != 0)
                 mPersistence--;
             return false;
         }
 
         // If persistence is set to -1, the doorhanger will never be
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -52,16 +52,18 @@ user_pref("font.size.inflation.minTwips"
 
 // Only load extensions from the application and user profile
 // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
 user_pref("extensions.enabledScopes", 5);
 // Disable metadata caching for installed add-ons by default
 user_pref("extensions.getAddons.cache.enabled", false);
 // Disable intalling any distribution add-ons
 user_pref("extensions.installDistroAddons", false);
+// XPI extensions are required for test harnesses to load
+user_pref("extensions.defaultProviders.enabled", true);
 
 user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
 user_pref("geo.wifi.testing", true);
 user_pref("geo.wifi.logging.enabled", true);
 
 user_pref("camino.warn_when_closing", false); // Camino-only, harmless to others
 
 // Make url-classifier updates so rare that they won't affect tests
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -18,16 +18,17 @@ if ("@mozilla.org/xre/app-info;1" in Cc)
   if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
     // Refuse to run in child processes.
     throw new Error("You cannot use the AddonManager in child processes!");
   }
 }
 
 
 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
+const PREF_DEFAULT_PROVIDERS_ENABLED  = "extensions.defaultProviders.enabled";
 const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
 const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
 const PREF_EM_LAST_PLATFORM_VERSION   = "extensions.lastPlatformVersion";
 const PREF_EM_AUTOUPDATE_DEFAULT      = "extensions.update.autoUpdateDefault";
 const PREF_EM_STRICT_COMPATIBILITY    = "extensions.strictCompatibility";
 const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
 const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
 const PREF_APP_UPDATE_ENABLED         = "app.update.enabled";
@@ -538,25 +539,32 @@ var AddonManagerInternal = {
     } catch (e) {}
     Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
 
     try {
       gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
     } catch (e) {}
     Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false);
 
+    let defaultProvidersEnabled = true;
+    try {
+      defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
+    } catch (e) {}
+
     // Ensure all default providers have had a chance to register themselves
-    DEFAULT_PROVIDERS.forEach(function(url) {
-      try {
-        Components.utils.import(url, {});
-      }
-      catch (e) {
-        ERROR("Exception loading default provider \"" + url + "\"", e);
-      }
-    });
+    if (defaultProvidersEnabled) {
+      DEFAULT_PROVIDERS.forEach(function(url) {
+        try {
+          Components.utils.import(url, {});
+        }
+        catch (e) {
+          ERROR("Exception loading default provider \"" + url + "\"", e);
+        }
+      });
+    }
 
     // Load any providers registered in the category manager
     let catman = Cc["@mozilla.org/categorymanager;1"].
                  getService(Ci.nsICategoryManager);
     let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
     while (entries.hasMoreElements()) {
       let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
       let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_default_providers_pref.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the extensions.defaultProviders.enabled pref which turns
+// off the default XPIProvider and LightweightThemeManager.
+
+function run_test() {
+  Services.prefs.setBoolPref("extensions.defaultProviders.enabled", false);
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  startupManager();
+  do_check_false(AddonManager.isInstallEnabled("application/x-xpinstall"));
+  Services.prefs.clearUserPref("extensions.defaultProviders.enabled");
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -143,16 +143,17 @@ fail-if = os == "android"
 [test_cacheflush.js]
 [test_checkcompatibility.js]
 [test_childprocess.js]
 [test_ChromeManifestParser.js]
 [test_compatoverrides.js]
 [test_corrupt.js]
 [test_corrupt_strictcompat.js]
 [test_corruptfile.js]
+[test_default_providers_pref.js]
 [test_dictionary.js]
 [test_langpack.js]
 [test_disable.js]
 [test_distribution.js]
 [test_dss.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_duplicateplugins.js]