Bug 702559 - First implementation of mozIStorageAsyncConnection;r=mak
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 27 Jun 2013 09:00:59 -0400
changeset 148968 90143e24ad66e9c30b843ea3d6547e3d06aa55ce
parent 148967 aeae60c65cc7669c139bf42cdaab8f2a7d936362
child 148969 e3aab5e3d15e2f6e4e6e5ea2651d122ceff5cbec
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs702559
milestone25.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
Bug 702559 - First implementation of mozIStorageAsyncConnection;r=mak * * * Bug 702559 - Implementation of transaction helper compatible with mozIStorageAsyncConnection;r=mak
extensions/cookie/nsPermissionManager.cpp
netwerk/cookie/nsCookieService.cpp
storage/public/moz.build
storage/public/mozIStorageAsyncConnection.idl
storage/public/mozIStorageCompletionCallback.idl
storage/public/mozIStorageConnection.idl
storage/public/mozIStorageService.idl
storage/public/mozStorageHelper.h
storage/public/storage.h
storage/src/mozStorageAsyncStatementExecution.cpp
storage/src/mozStorageAsyncStatementExecution.h
storage/src/mozStorageConnection.cpp
storage/src/mozStorageConnection.h
storage/src/mozStoragePrivateHelpers.cpp
storage/src/mozStorageService.cpp
storage/test/storage_test_harness.h
toolkit/components/places/Database.cpp
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -237,17 +237,17 @@ NS_IMPL_ISUPPORTS1(CloseDatabaseListener
 CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager,
                                              bool aRebuildOnSuccess)
   : mManager(aManager)
   , mRebuildOnSuccess(aRebuildOnSuccess)
 {
 }
 
 NS_IMETHODIMP
-CloseDatabaseListener::Complete()
+CloseDatabaseListener::Complete(nsresult, nsISupports*)
 {
   // Help breaking cycles
   nsRefPtr<nsPermissionManager> manager = mManager.forget();
   if (mRebuildOnSuccess && !manager->mIsShuttingDown) {
     return manager->InitDB(true);
   }
   return NS_OK;
 }
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -554,17 +554,17 @@ NS_IMPL_ISUPPORTS1(ReadCookieDBListener,
  ******************************************************************************/
 class CloseCookieDBListener MOZ_FINAL :  public mozIStorageCompletionCallback
 {
 public:
   CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { }
   nsRefPtr<DBState> mDBState;
   NS_DECL_ISUPPORTS
 
-  NS_IMETHOD Complete()
+  NS_IMETHOD Complete(nsresult, nsISupports*)
   {
     gCookieService->HandleDBClosed(mDBState);
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS1(CloseCookieDBListener, mozIStorageCompletionCallback)
 
--- a/storage/public/moz.build
+++ b/storage/public/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'mozIStorageAggregateFunction.idl',
+    'mozIStorageAsyncConnection.idl',
     'mozIStorageAsyncStatement.idl',
     'mozIStorageBaseStatement.idl',
     'mozIStorageBindingParams.idl',
     'mozIStorageBindingParamsArray.idl',
     'mozIStorageCompletionCallback.idl',
     'mozIStorageConnection.idl',
     'mozIStorageError.idl',
     'mozIStorageFunction.idl',
new file mode 100644
--- /dev/null
+++ b/storage/public/mozIStorageAsyncConnection.idl
@@ -0,0 +1,200 @@
+/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIStorageAggregateFunction;
+interface mozIStorageCompletionCallback;
+interface mozIStorageFunction;
+interface mozIStorageProgressHandler;
+interface mozIStorageBaseStatement;
+interface mozIStorageStatement;
+interface mozIStorageAsyncStatement;
+interface mozIStorageStatementCallback;
+interface mozIStoragePendingStatement;
+interface nsIFile;
+
+/**
+ * mozIStorageAsyncConnection represents an asynchronous database
+ * connection attached to a specific file or to an in-memory data
+ * storage.  It is the primary interface for interacting with a
+ * database from the main thread, including creating prepared
+ * statements, executing SQL, and examining database errors.
+ */
+[scriptable, uuid(0e661a1d-27ff-4e6b-ac5a-126314cef61a)]
+interface mozIStorageAsyncConnection : nsISupports {
+  /**
+   * Close this database connection, allowing all pending statements
+   * to complete first.
+   *
+   * @param aCallback [optional]
+   *        A callback that will be notified when the close is completed,
+   *        with the following arguments:
+   *        - status: the status of the call
+   *        - value: |null|
+   *
+   * @throws NS_ERROR_NOT_SAME_THREAD
+   *         If is called on a thread other than the one that opened it.
+   */
+  void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
+
+  /**
+   * Clone a database and make the clone read only if needed.
+   *
+   * @param aReadOnly
+   *        If true, the returned database should be put into read-only mode.
+   *
+   * @param aCallback
+   *        A callback that will be notified when the operation is complete,
+   *        with the following arguments:
+   *        - status: the status of the operation
+   *        - value: in case of success, an intance of
+   *             mozIStorageAsyncConnection cloned from this one.
+   *
+   * @throws NS_ERROR_NOT_SAME_THREAD
+   *         If is called on a thread other than the one that opened it.
+   * @throws NS_ERROR_UNEXPECTED
+   *         If this connection is a memory database.
+   *
+   * @note If your connection is already read-only, you will get a read-only
+   *       clone.
+   * @note Due to a bug in SQLite, if you use the shared cache
+   *       (see mozIStorageService), you end up with the same privileges as the
+   *       first connection opened regardless of what is specified in aReadOnly.
+   * @note The following pragmas are copied over to a read-only clone:
+   *        - cache_size
+   *        - temp_store
+   *       The following pragmas are copied over to a writeable clone:
+   *        - cache_size
+   *        - temp_store
+   *        - foreign_keys
+   *        - journal_size_limit
+   *        - synchronous
+   *        - wal_autocheckpoint
+   */
+  void asyncClone(in boolean aReadOnly,
+                  in mozIStorageCompletionCallback aCallback);
+
+  /**
+   * The current database nsIFile.  Null if the database
+   * connection refers to an in-memory database.
+   */
+  readonly attribute nsIFile databaseFile;
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Statement creation
+
+  /**
+   * Create an asynchronous statement for the given SQL. An
+   * asynchronous statement can only be used to dispatch asynchronous
+   * requests to the asynchronous execution thread and cannot be used
+   * to take any synchronous actions on the database.
+   *
+   * The expression may use ? to indicate sequential numbered arguments,
+   * ?1, ?2 etc. to indicate specific numbered arguments or :name and
+   * $var to indicate named arguments.
+   *
+   * @param aSQLStatement
+   *        The SQL statement to execute.
+   * @return a new mozIStorageAsyncStatement
+   * @note The statement is created lazily on first execution.
+   */
+  mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
+
+  /**
+   * Execute an array of statements created with this connection using
+   * any currently bound parameters. When the array contains multiple
+   * statements, the execution is wrapped in a single
+   * transaction. These statements can be reused immediately, and
+   * reset does not need to be called.
+   *
+   * @param aStatements
+   *        The array of statements to execute asynchronously, in the order they
+   *        are given in the array.
+   * @param aNumStatements
+   *        The number of statements in aStatements.
+   * @param aCallback [optional]
+   *        The callback object that will be notified of progress, errors, and
+   *        completion.
+   * @return an object that can be used to cancel the statements execution.
+   *
+   * @note If you have any custom defined functions, they must be
+   *        re-entrant since they can be called on multiple threads.
+   */
+  mozIStoragePendingStatement executeAsync(
+    [array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
+    in unsigned long aNumStatements,
+    [optional] in mozIStorageStatementCallback aCallback
+  );
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Functions
+
+  /**
+   * Create a new SQL function.  If you use your connection on multiple threads,
+   * your function needs to be threadsafe, or it should only be called on one
+   * thread.
+   *
+   * @param aFunctionName
+   *        The name of function to create, as seen in SQL.
+   * @param aNumArguments
+   *        The number of arguments the function takes. Pass -1 for
+   *        variable-argument functions.
+   * @param aFunction
+   *        The instance of mozIStorageFunction, which implements the function
+   *        in question.
+   */
+  void createFunction(in AUTF8String aFunctionName,
+                      in long aNumArguments,
+                      in mozIStorageFunction aFunction);
+
+  /**
+   * Create a new SQL aggregate function.  If you use your connection on
+   * multiple threads, your function needs to be threadsafe, or it should only
+   * be called on one thread.
+   *
+   * @param aFunctionName
+   *        The name of aggregate function to create, as seen in SQL.
+   * @param aNumArguments
+   *        The number of arguments the function takes. Pass -1 for
+   *        variable-argument functions.
+   * @param aFunction
+   *        The instance of mozIStorageAggreagteFunction, which implements the
+   *        function in question.
+   */
+  void createAggregateFunction(in AUTF8String aFunctionName,
+                               in long aNumArguments,
+                               in mozIStorageAggregateFunction aFunction);
+  /**
+   * Delete custom SQL function (simple or aggregate one).
+   *
+   * @param aFunctionName
+   *        The name of function to remove.
+   */
+  void removeFunction(in AUTF8String aFunctionName);
+
+  /**
+   * Sets a progress handler. Only one handler can be registered at a time.
+   * If you need more than one, you need to chain them yourself.  This progress
+   * handler should be threadsafe if you use this connection object on more than
+   * one thread.
+   *
+   * @param aGranularity
+   *        The number of SQL virtual machine steps between progress handler
+   *        callbacks.
+   * @param aHandler
+   *        The instance of mozIStorageProgressHandler.
+   * @return previous registered handler.
+   */
+  mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity,
+                                                in mozIStorageProgressHandler aHandler);
+
+  /**
+   * Remove a progress handler.
+   *
+   * @return previous registered handler.
+   */
+  mozIStorageProgressHandler removeProgressHandler();
+};
--- a/storage/public/mozIStorageCompletionCallback.idl
+++ b/storage/public/mozIStorageCompletionCallback.idl
@@ -1,15 +1,24 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
   vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable,function, uuid(0bfee0c4-2c24-400e-b18e-b5bb41a032c8)]
+[scriptable, function, uuid(8cbf2dc2-91e0-44bc-984f-553638412071)]
 interface mozIStorageCompletionCallback : nsISupports {
   /**
    * Indicates that the event this callback was passed in for has completed.
+   *
+   * @param status
+   *        The status of the call. Generally NS_OK if the operation
+   *        completed successfully.
+   * @param value
+   *        If the operation produces a result, the result. Otherwise,
+   *        |null|.
+   *
+   * @see The calling method for expected values.
    */
-  void complete();
+  void complete(in nsresult status, [optional] in nsISupports value);
 };
--- a/storage/public/mozIStorageConnection.idl
+++ b/storage/public/mozIStorageConnection.idl
@@ -1,14 +1,15 @@
 /* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
+#include "mozIStorageAsyncConnection.idl"
 
 interface mozIStorageAggregateFunction;
 interface mozIStorageCompletionCallback;
 interface mozIStorageFunction;
 interface mozIStorageProgressHandler;
 interface mozIStorageBaseStatement;
 interface mozIStorageStatement;
 interface mozIStorageAsyncStatement;
@@ -18,93 +19,77 @@ interface nsIFile;
 
 /**
  * mozIStorageConnection represents a database connection attached to
  * a specific file or to the in-memory data storage.  It is the
  * primary interface for interacting with a database, including
  * creating prepared statements, executing SQL, and examining database
  * errors.
  *
+ * @note From the main thread, you should rather use mozIStorageAsyncConnection.
+ *
  * @threadsafe
  */
-[scriptable, uuid(c8646e4b-3e2d-4df3-98a9-e2bbf74f279c)]
-interface mozIStorageConnection : nsISupports {
+[scriptable, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)]
+interface mozIStorageConnection : mozIStorageAsyncConnection {
   /**
    * Closes a database connection.  Callers must finalize all statements created
    * for this connection prior to calling this method.  It is illegal to use
    * call this method if any asynchronous statements have been executed on this
    * connection.
    *
    * @throws NS_ERROR_UNEXPECTED
    *         If any statement has been executed asynchronously on this object.
    * @throws NS_ERROR_UNEXPECTED
    *         If is called on a thread other than the one that opened it.
    */
   void close();
 
   /**
-   * Asynchronously closes a database connection, allowing all pending
-   * asynchronous statements to complete first.
+   * Clones a database connection and makes the clone read only if needed.
    *
-   * @param aCallback [optional]
-   *        A callback that will be notified when the close is completed.
+   * @param aReadOnly
+   *        If true, the returned database should be put into read-only mode.
+   *        Defaults to false.
+   * @return the cloned database connection.
    *
    * @throws NS_ERROR_UNEXPECTED
-   *         If is called on a thread other than the one that opened it.
-   */
-  void asyncClose([optional] in mozIStorageCompletionCallback aCallback);
-
-  /**
-   * Clones a database and makes the clone read only if needed.
-   *
+   *         If this connection is a memory database.
    * @note If your connection is already read-only, you will get a read-only
    *       clone.
    * @note Due to a bug in SQLite, if you use the shared cache (openDatabase),
    *       you end up with the same privileges as the first connection opened
    *       regardless of what is specified in aReadOnly.
    * @note The following pragmas are copied over to a read-only clone:
    *        - cache_size
    *        - temp_store
    *       The following pragmas are copied over to a writeable clone:
    *        - cache_size
    *        - temp_store
    *        - foreign_keys
    *        - journal_size_limit
    *        - synchronous
    *        - wal_autocheckpoint
    *
-   * @throws NS_ERROR_UNEXPECTED
-   *         If this connection is a memory database.
-   *
-   * @param aReadOnly
-   *        If true, the returned database should be put into read-only mode.
-   *        Defaults to false.
-   * @return the cloned database connection.
    */
   mozIStorageConnection clone([optional] in boolean aReadOnly);
 
   /**
    * The default size for SQLite database pages used by mozStorage for new
    * databases.
    */
   readonly attribute long defaultPageSize;
 
   /**
    * Indicates if the connection is open and ready to use.  This will be false
    * if the connection failed to open, or it has been closed.
    */
   readonly attribute boolean connectionReady;
 
   /**
-   * The current database nsIFile.  Null if the database
-   * connection refers to an in-memory database.
-   */
-  readonly attribute nsIFile databaseFile;
-
-  /**
    * lastInsertRowID returns the row ID from the last INSERT
    * operation.
    */
   readonly attribute long long lastInsertRowID;
 
   /**
    * affectedRows returns the number of database rows that were changed or
    * inserted or deleted by last operation.
@@ -139,64 +124,23 @@ interface mozIStorageConnection : nsISup
    *
    * @param aSQLStatement
    *        The SQL statement to execute.
    * @return a new mozIStorageStatement
    */
   mozIStorageStatement createStatement(in AUTF8String aSQLStatement);
 
   /**
-   * Create an asynchronous statement (mozIStorageAsyncStatement) for the given
-   * SQL expression.  An asynchronous statement can only be used to dispatch
-   * asynchronous requests to the asynchronous execution thread and cannot be
-   * used to take any synchronous actions on the database.
-   *
-   * The expression may use ? to indicate sequential numbered arguments,
-   * ?1, ?2 etc. to indicate specific numbered arguments or :name and
-   * $var to indicate named arguments.
-   *
-   * @param aSQLStatement
-   *        The SQL statement to execute.
-   * @return a new mozIStorageAsyncStatement
-   */
-  mozIStorageAsyncStatement createAsyncStatement(in AUTF8String aSQLStatement);
-
-  /**
    * Execute a SQL expression, expecting no arguments.
    *
    * @param aSQLStatement  The SQL statement to execute
    */
   void executeSimpleSQL(in AUTF8String aSQLStatement);
 
   /**
-   * Execute an array of queries created with this connection asynchronously
-   * using any currently bound parameters.  The statements are ran wrapped in a
-   * transaction.  These statements can be reused immediately, and reset does
-   * not need to be called.
-   *
-   * Note:  If you have any custom defined functions, they must be re-entrant
-   *        since they can be called on multiple threads.
-   *
-   * @param aStatements
-   *        The array of statements to execute asynchronously, in the order they
-   *        are given in the array.
-   * @param aNumStatements
-   *        The number of statements in aStatements.
-   * @param aCallback [optional]
-   *        The callback object that will be notified of progress, errors, and
-   *        completion.
-   * @return an object that can be used to cancel the statements execution.
-   */
-  mozIStoragePendingStatement executeAsync(
-    [array, size_is(aNumStatements)] in mozIStorageBaseStatement aStatements,
-    in unsigned long aNumStatements,
-    [optional] in mozIStorageStatementCallback aCallback
-  );
-
-  /**
    * Check if the given table exists.
    *
    * @param aTableName
    *        The table to check
    * @return TRUE if table exists, FALSE otherwise.
    */
   boolean tableExists(in AUTF8String aTableName);
 
@@ -263,85 +207,16 @@ interface mozIStorageConnection : nsISup
    *
    * @throws NS_ERROR_FAILURE
    *         If the table already exists or could not be created for any other
    *         reason.
    */
   void createTable(in string aTableName,
                    in string aTableSchema);
 
-  //////////////////////////////////////////////////////////////////////////////
-  //// Functions
-
-  /**
-   * Create a new SQL function.  If you use your connection on multiple threads,
-   * your function needs to be threadsafe, or it should only be called on one
-   * thread.
-   *
-   * @param aFunctionName
-   *        The name of function to create, as seen in SQL.
-   * @param aNumArguments
-   *        The number of arguments the function takes. Pass -1 for
-   *        variable-argument functions.
-   * @param aFunction
-   *        The instance of mozIStorageFunction, which implements the function
-   *        in question.
-   */
-  void createFunction(in AUTF8String aFunctionName,
-                      in long aNumArguments,
-                      in mozIStorageFunction aFunction);
-
-  /**
-   * Create a new SQL aggregate function.  If you use your connection on
-   * multiple threads, your function needs to be threadsafe, or it should only
-   * be called on one thread.
-   *
-   * @param aFunctionName
-   *        The name of aggregate function to create, as seen in SQL.
-   * @param aNumArguments
-   *        The number of arguments the function takes. Pass -1 for
-   *        variable-argument functions.
-   * @param aFunction
-   *        The instance of mozIStorageAggreagteFunction, which implements the
-   *        function in question.
-   */
-  void createAggregateFunction(in AUTF8String aFunctionName,
-                               in long aNumArguments,
-                               in mozIStorageAggregateFunction aFunction);
-  /**
-   * Delete custom SQL function (simple or aggregate one).
-   *
-   * @param aFunctionName
-   *        The name of function to remove.
-   */
-  void removeFunction(in AUTF8String aFunctionName);
-
-  /**
-   * Sets a progress handler. Only one handler can be registered at a time.
-   * If you need more than one, you need to chain them yourself.  This progress
-   * handler should be threadsafe if you use this connection object on more than
-   * one thread.
-   *
-   * @param aGranularity
-   *        The number of SQL virtual machine steps between progress handler
-   *        callbacks.
-   * @param aHandler
-   *        The instance of mozIStorageProgressHandler.
-   * @return previous registered handler.
-   */
-  mozIStorageProgressHandler setProgressHandler(in int32_t aGranularity,
-                                                in mozIStorageProgressHandler aHandler);
-
-  /**
-   * Remove a progress handler.
-   *
-   * @return previous registered handler.
-   */
-  mozIStorageProgressHandler removeProgressHandler();
-
   /**
    * Controls SQLITE_FCNTL_CHUNK_SIZE setting in sqlite. This helps avoid fragmentation
    * by growing/shrinking the database file in SQLITE_FCNTL_CHUNK_SIZE increments. To
    * conserve memory on systems short on storage space, this function will have no effect
    * on mobile devices or if less than 500MiB of space is left available.
    *
    * @param aIncrement
    *        The database file will grow in multiples of chunkSize.
--- a/storage/public/mozIStorageService.idl
+++ b/storage/public/mozIStorageService.idl
@@ -3,30 +3,81 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface mozIStorageConnection;
 interface nsIFile;
 interface nsIFileURL;
+interface nsIPropertyBag2;
+interface nsIVariant;
+interface mozIStorageCompletionCallback;
 
 /**
  * The mozIStorageService interface is intended to be implemented by
  * a service that can create storage connections (mozIStorageConnection)
  * to either a well-known profile database or to a specific database file.
  *
  * This is the only way to open a database connection.
  *
  * @note The first reference to mozIStorageService must be made on the main
  * thread.
  */
-[scriptable, uuid(12bfad34-cca3-40fb-8736-d8bf9db61a27)]
+[scriptable, uuid(07b6b2f5-6d97-47b4-9584-e65bc467fe9e)]
 interface mozIStorageService : nsISupports {
   /**
+   * Open an asynchronous connection to a database.
+   *
+   * This method MUST be called from the main thread. The connection object
+   * returned by this function is not threadsafe. You MUST use it only from
+   * the main thread.
+   *
+   * If you have more than one connection to a file, you MUST use the EXACT
+   * SAME NAME for the file each time, including case. The sqlite code uses
+   * a simple string compare to see if there is already a connection. Opening
+   * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
+   *
+   * @param aDatabaseStore Either a nsIFile representing the file that contains
+   * the database or a special string to open a special database. The special
+   * string may be:
+   * - "memory" to open an in-memory database.
+   *
+   * @param aOptions A set of options (may be null). Options may contain:
+   * - bool shared (defaults to |false|).
+   *   -- If |true|, opens the database with a shared-cache. The
+   *     shared-cache mode is more memory-efficient when many
+   *     connections to the same database are expected, though, the
+   *     connections will contend the cache resource. In any cases
+   *     where performance matter, working without a shared-cache will
+   *     improve concurrency.  @see openUnsharedDatabase
+   *
+   * - int growthIncrement (defaults to none).
+   *   -- Set the growth increment for the main database.  This hints SQLite to
+   *      grow the database file by a given chunk size and may reduce
+   *      filesystem fragmentation on large databases.
+   *      @see mozIStorageConnection::setGrowthIncrement
+   *
+   * @param aCallback A callback that will receive the result of the operation.
+   *  In case of error, it may receive as status:
+   *  - NS_ERROR_OUT_OF_MEMORY if allocating a new storage object fails.
+   *  - NS_ERROR_FILE_CORRUPTED if the database file is corrupted.
+   *  In case of success, it receives as argument the new database
+   *  connection, as an instance of |mozIStorageAsyncConnection|.
+   *
+   * @throws NS_ERROR_INVALID_ARG if |aDatabaseStore| is neither a file nor
+   *         one of the special strings understood by this method, or if one of
+   *         the options passed through |aOptions| does not have the right type.
+   * @throws NS_ERROR_NOT_SAME_THREAD if called from a thread other than the
+   *         main thread.
+   */
+  void openAsyncDatabase(in nsIVariant aDatabaseStore,
+                         [optional] in nsIPropertyBag2 aOptions,
+                         in mozIStorageCompletionCallback aCallback);
+  /**
    * Get a connection to a named special database storage.
    *
    * @param aStorageKey a string key identifying the type of storage
    * requested.  Valid values include: "profile", "memory".
    *
    * @see openDatabase for restrictions on how database connections may be
    * used. For the profile database, you should only access it from the main
    * thread since other callers may also have connections.
@@ -53,43 +104,36 @@ interface mozIStorageService : nsISuppor
    * If you have more than one connection to a file, you MUST use the EXACT
    * SAME NAME for the file each time, including case. The sqlite code uses
    * a simple string compare to see if there is already a connection. Opening
    * a connection to "Foo.sqlite" and "foo.sqlite" will CORRUPT YOUR DATABASE.
    *
    * The connection object returned by this function is not threadsafe. You must
    * use it only from the thread you created it from.
    *
-   * If your database contains virtual tables (f.e. for full-text indexes), you
-   * must open it with openUnsharedDatabase, as those tables are incompatible
-   * with a shared cache.  If you attempt to use this method to open a database
-   * containing virtual tables, it will think the database is corrupted and
-   * throw NS_ERROR_FILE_CORRUPTED.
-   *
    * @param aDatabaseFile
    *        A nsIFile that represents the database that is to be opened..
    *
    * @returns a mozIStorageConnection for the requested database file.
    *
    * @throws NS_ERROR_OUT_OF_MEMORY
    *         If allocating a new storage object fails.
    * @throws NS_ERROR_FILE_CORRUPTED
    *         If the database file is corrupted.
    */
   mozIStorageConnection openDatabase(in nsIFile aDatabaseFile);
 
   /**
    * Open a connection to the specified file that doesn't share a sqlite cache.
    *
-   * Each connection uses its own sqlite cache, which is inefficient, so you
-   * should use openDatabase instead of this method unless you need a feature
-   * of SQLite that is incompatible with a shared cache, like virtual table
-   * and full text indexing support. If cache contention is expected, for
-   * instance when operating on a database from multiple threads, using
-   * unshared connections may be a performance win.
+   * Without a shared-cache, each connection uses its own pages cache, which
+   * may be memory inefficient with a large number of connections, in such a
+   * case so you should use openDatabase instead.  On the other side, if cache
+   * contention may be an issue, for instance when concurrency is important to
+   * ensure responsiveness, using unshared connections may be a performance win.
    *
    * ==========
    *   DANGER
    * ==========
    *
    * If you have more than one connection to a file, you MUST use the EXACT
    * SAME NAME for the file each time, including case. The sqlite code uses
    * a simple string compare to see if there is already a connection. Opening
--- a/storage/public/mozStorageHelper.h
+++ b/storage/public/mozStorageHelper.h
@@ -3,52 +3,57 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZSTORAGEHELPER_H
 #define MOZSTORAGEHELPER_H
 
 #include "nsAutoPtr.h"
 
+#include "mozIStorageAsyncConnection.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
 #include "nsError.h"
 
-
 /**
  * This class wraps a transaction inside a given C++ scope, guaranteeing that
  * the transaction will be completed even if you have an exception or
  * return early.
  *
  * aCommitOnComplete controls whether the transaction is committed or rolled
  * back when it goes out of scope. A common use is to create an instance with
  * commitOnComplete = FALSE (rollback), then call Commit on this object manually
  * when your function completes successfully.
  *
  * Note that nested transactions are not supported by sqlite, so if a transaction
  * is already in progress, this object does nothing. Note that in this case,
  * you may not get the transaction type you ask for, and you won't be able
  * to rollback.
+ *
+ * Note: This class is templatized to be also usable with internal data
+ * structures. External users of this class should generally use
+ * |mozStorageTransaction| instead.
  */
-class mozStorageTransaction
+template<typename T, typename U>
+class mozStorageTransactionBase
 {
 public:
-  mozStorageTransaction(mozIStorageConnection* aConnection,
-                        bool aCommitOnComplete,
-                        int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED)
+  mozStorageTransactionBase(T* aConnection,
+                            bool aCommitOnComplete,
+                            int32_t aType = mozIStorageConnection::TRANSACTION_DEFERRED)
     : mConnection(aConnection),
       mHasTransaction(false),
       mCommitOnComplete(aCommitOnComplete),
       mCompleted(false)
   {
     // We won't try to get a transaction if one is already in progress.
     if (mConnection)
       mHasTransaction = NS_SUCCEEDED(mConnection->BeginTransactionAs(aType));
   }
-  ~mozStorageTransaction()
+  ~mozStorageTransactionBase()
   {
     if (mConnection && mHasTransaction && ! mCompleted) {
       if (mCommitOnComplete)
         mConnection->CommitTransaction();
       else
         mConnection->RollbackTransaction();
     }
   }
@@ -114,22 +119,31 @@ public:
    * out of scope.
    */
   void SetDefaultAction(bool aCommitOnComplete)
   {
     mCommitOnComplete = aCommitOnComplete;
   }
 
 protected:
-  nsCOMPtr<mozIStorageConnection> mConnection;
+  U mConnection;
   bool mHasTransaction;
   bool mCommitOnComplete;
   bool mCompleted;
 };
 
+/**
+ * An instance of the mozStorageTransaction<> family dedicated
+ * to |mozIStorageConnection|.
+ */
+typedef mozStorageTransactionBase<mozIStorageConnection,
+                                  nsCOMPtr<mozIStorageConnection> >
+mozStorageTransaction;
+
+
 
 /**
  * This class wraps a statement so that it is guaraneed to be reset when
  * this object goes out of scope.
  *
  * Note that this always just resets the statement. If the statement doesn't
  * need resetting, the reset operation is inexpensive.
  */
--- a/storage/public/storage.h
+++ b/storage/public/storage.h
@@ -22,16 +22,17 @@
 #include "mozIStorageService.h"
 #include "mozIStorageStatement.h"
 #include "mozIStorageStatementCallback.h"
 #include "mozIStorageBindingParamsArray.h"
 #include "mozIStorageBindingParams.h"
 #include "mozIStorageVacuumParticipant.h"
 #include "mozIStorageCompletionCallback.h"
 #include "mozIStorageAsyncStatement.h"
+#include "mozIStorageAsyncConnection.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Native Language Helpers
 
 #include "mozStorageHelper.h"
 #include "mozilla/storage/StatementCache.h"
 #include "mozilla/storage/Variant.h"
 
--- a/storage/src/mozStorageAsyncStatementExecution.cpp
+++ b/storage/src/mozStorageAsyncStatementExecution.cpp
@@ -572,18 +572,19 @@ AsyncExecuteStatements::Run()
     MutexAutoLock lockedScope(mMutex);
     if (mCancelRequested)
       mState = CANCELED;
   }
   if (mState == CANCELED)
     return notifyComplete();
 
   if (statementsNeedTransaction()) {
-    mTransactionManager = new mozStorageTransaction(mConnection, false,
-                                                    mozIStorageConnection::TRANSACTION_IMMEDIATE);
+    Connection* rawConnection = static_cast<Connection*>(mConnection.get());
+    mTransactionManager = new mozStorageAsyncTransaction(rawConnection, false,
+                                                         mozIStorageConnection::TRANSACTION_IMMEDIATE);
   }
 
   // Execute each statement, giving the callback results if it returns any.
   for (uint32_t i = 0; i < mStatements.Length(); i++) {
     bool finished = (i == (mStatements.Length() - 1));
 
     sqlite3_stmt *stmt;
     { // lock the sqlite mutex so sqlite3_errmsg cannot change
--- a/storage/src/mozStorageAsyncStatementExecution.h
+++ b/storage/src/mozStorageAsyncStatementExecution.h
@@ -13,27 +13,35 @@
 #include "nsThreadUtils.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
 
 #include "SQLiteMutex.h"
 #include "mozIStoragePendingStatement.h"
 #include "mozIStorageStatementCallback.h"
+#include "mozStorageHelper.h"
 
 struct sqlite3_stmt;
-class mozStorageTransaction;
 
 namespace mozilla {
 namespace storage {
 
 class Connection;
 class ResultSet;
 class StatementData;
 
+/**
+ * An instance of the mozStorageTransaction<> family dedicated
+ * to concrete class |Connection|.
+ */
+typedef mozStorageTransactionBase<mozilla::storage::Connection,
+                                  nsRefPtr<mozilla::storage::Connection> >
+    mozStorageAsyncTransaction;
+
 class AsyncExecuteStatements MOZ_FINAL : public nsIRunnable
                                        , public mozIStoragePendingStatement
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_MOZISTORAGEPENDINGSTATEMENT
 
@@ -174,17 +182,17 @@ private:
    * transaction.
    *
    * @return true if an explicit transaction is needed, false otherwise.
    */
   bool statementsNeedTransaction();
 
   StatementDataArray mStatements;
   nsRefPtr<Connection> mConnection;
-  mozStorageTransaction *mTransactionManager;
+  mozStorageAsyncTransaction *mTransactionManager;
   mozIStorageStatementCallback *mCallback;
   nsCOMPtr<nsIThread> mCallingThread;
   nsRefPtr<ResultSet> mResultSet;
 
   /**
    * The maximum amount of time we want to wait between results.  Defined by
    * MAX_MILLISECONDS_BETWEEN_RESULTS and set at construction.
    */
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -34,16 +34,17 @@
 #include "StorageBaseStatementInternal.h"
 #include "SQLCollations.h"
 #include "FileSystemModule.h"
 #include "mozStorageHelper.h"
 #include "GeckoProfiler.h"
 
 #include "prlog.h"
 #include "prprf.h"
+#include "nsProxyRelease.h"
 #include <algorithm>
 
 #define MIN_AVAILABLE_BYTES_PER_CHUNKED_GROWTH 524288000 // 500 MiB
 
 // Maximum size of the pages cache per connection.
 #define MAX_CACHE_SIZE_KIBIBYTES 2048 // 2 MiB
 
 #ifdef PR_LOGGING
@@ -359,16 +360,17 @@ public:
 #endif
       (void)mCallingThread->Dispatch(this, NS_DISPATCH_NORMAL);
       return NS_OK;
     }
 
     (void)mConnection->internalClose();
     if (mCallbackEvent)
       (void)mCallingThread->Dispatch(mCallbackEvent, NS_DISPATCH_NORMAL);
+
     (void)mAsyncExecutionThread->Shutdown();
 
     // Because we have no guarantee that the invocation of this method on the
     // asynchronous thread has fully completed (including the Release of the
     // reference to this object held by that event loop), we need to explicitly
     // null out our pointers here.  It is possible this object will be destroyed
     // on the asynchronous thread and if the references are still alive we will
     // release them on that thread. We definitely do not want that for
@@ -382,67 +384,150 @@ public:
   }
 private:
   nsRefPtr<Connection> mConnection;
   nsCOMPtr<nsIEventTarget> mCallingThread;
   nsCOMPtr<nsIRunnable> mCallbackEvent;
   nsCOMPtr<nsIThread> mAsyncExecutionThread;
 };
 
+/**
+ * An event used to initialize the clone of a connection.
+ *
+ * Must be executed on the clone's async execution thread.
+ */
+class AsyncInitializeClone MOZ_FINAL: public nsRunnable
+{
+public:
+  /**
+   * @param aConnection The connection being cloned.
+   * @param aClone The clone.
+   * @param aReadOnly If |true|, the clone is read only.
+   * @param aCallback A callback to trigger once initialization
+   *                  is complete. This event will be called on
+   *                  aClone->threadOpenedOn.
+   */
+  AsyncInitializeClone(Connection* aConnection,
+                       Connection* aClone,
+                       const bool aReadOnly,
+                       mozIStorageCompletionCallback* aCallback)
+    : mConnection(aConnection)
+    , mClone(aClone)
+    , mReadOnly(aReadOnly)
+    , mCallback(aCallback)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_IMETHOD Run() {
+    MOZ_ASSERT (NS_GetCurrentThread() == mClone->getAsyncExecutionTarget());
+
+    nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
+    if (NS_FAILED(rv)) {
+      return Dispatch(rv, nullptr);
+    }
+    return Dispatch(NS_OK,
+                    NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, mClone));
+  }
+
+private:
+  nsresult Dispatch(nsresult aResult, nsISupports* aValue) {
+    nsRefPtr<CallbackComplete> event = new CallbackComplete(aResult,
+                                                            aValue,
+                                                            mCallback.forget());
+    return mClone->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
+  }
+
+  ~AsyncInitializeClone() {
+    nsCOMPtr<nsIThread> thread;
+    DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    // Handle ambiguous nsISupports inheritance.
+    Connection *rawConnection = nullptr;
+    mConnection.swap(rawConnection);
+    (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
+                                                    rawConnection));
+
+    Connection *rawClone = nullptr;
+    mClone.swap(rawClone);
+    (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
+                                                    rawClone));
+
+    // Generally, the callback will be released by CallbackComplete.
+    // However, if for some reason Run() is not executed, we still
+    // need to ensure that it is released here.
+    mozIStorageCompletionCallback *rawCallback = nullptr;
+    mCallback.swap(rawCallback);
+    (void)NS_ProxyRelease(thread, rawCallback);
+  }
+
+  nsRefPtr<Connection> mConnection;
+  nsRefPtr<Connection> mClone;
+  const bool mReadOnly;
+  nsCOMPtr<mozIStorageCompletionCallback> mCallback;
+};
+
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Connection
 
 Connection::Connection(Service *aService,
-                       int aFlags)
+                       int aFlags,
+                       bool aAsyncOnly)
 : sharedAsyncExecutionMutex("Connection::sharedAsyncExecutionMutex")
 , sharedDBMutex("Connection::sharedDBMutex")
 , threadOpenedOn(do_GetCurrentThread())
 , mDBConn(nullptr)
 , mAsyncExecutionThreadShuttingDown(false)
 , mTransactionInProgress(false)
 , mProgressHandler(nullptr)
 , mFlags(aFlags)
 , mStorageService(aService)
+, mAsyncOnly(aAsyncOnly)
 {
   mFunctions.Init();
   mStorageService->registerConnection(this);
 }
 
 Connection::~Connection()
 {
   (void)Close();
 
-  MOZ_ASSERT(!mAsyncExecutionThread);
+  MOZ_ASSERT(!mAsyncExecutionThread,
+             "AsyncClose has not been invoked on this connection!");
 }
 
 NS_IMPL_THREADSAFE_ADDREF(Connection)
-NS_IMPL_THREADSAFE_QUERY_INTERFACE2(
-  Connection,
-  mozIStorageConnection,
-  nsIInterfaceRequestor
-)
+
+NS_INTERFACE_MAP_BEGIN(Connection)
+  NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncConnection)
+  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(mozIStorageConnection, !mAsyncOnly)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageConnection)
+NS_INTERFACE_MAP_END
 
 // This is identical to what NS_IMPL_THREADSAFE_RELEASE provides, but with the
 // extra |1 == count| case.
 NS_IMETHODIMP_(nsrefcnt) Connection::Release(void)
 {
   NS_PRECONDITION(0 != mRefCnt, "dup release");
   nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt);
   NS_LOG_RELEASE(this, count, "Connection");
   if (1 == count) {
     // If the refcount is 1, the single reference must be from
     // gService->mConnections (in class |Service|).  Which means we can
     // unregister it safely.
     mStorageService->unregisterConnection(this);
   } else if (0 == count) {
     mRefCnt = 1; /* stabilize */
-    /* enable this to find non-threadsafe destructors: */
-    /* NS_ASSERT_OWNINGTHREAD(Connection); */
+#if 0 /* enable this to find non-threadsafe destructors: */
+    NS_ASSERT_OWNINGTHREAD(Connection);
+#endif
     delete (this);
     return 0;
   }
   return count;
 }
 
 nsIEventTarget *
 Connection::getAsyncExecutionTarget()
@@ -947,16 +1032,19 @@ Connection::Close()
   NS_ENSURE_SUCCESS(rv, rv);
 
   return internalClose();
 }
 
 NS_IMETHODIMP
 Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
 {
+  if (!NS_IsMainThread()) {
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
   if (!mDBConn)
     return NS_ERROR_NOT_INITIALIZED;
 
   nsIEventTarget *asyncThread = getAsyncExecutionTarget();
   NS_ENSURE_TRUE(asyncThread, NS_ERROR_UNEXPECTED);
 
   nsresult rv = setClosedState();
   NS_ENSURE_SUCCESS(rv, rv);
@@ -979,38 +1067,56 @@ Connection::AsyncClose(mozIStorageComple
 
   rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-Connection::Clone(bool aReadOnly,
-                  mozIStorageConnection **_connection)
+Connection::AsyncClone(bool aReadOnly,
+                       mozIStorageCompletionCallback *aCallback)
 {
   PROFILER_LABEL("storage", "Connection::Clone");
+  if (!NS_IsMainThread()) {
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
   if (!mDBConn)
     return NS_ERROR_NOT_INITIALIZED;
   if (!mDatabaseFile)
     return NS_ERROR_UNEXPECTED;
 
   int flags = mFlags;
   if (aReadOnly) {
     // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
     flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
     // Turn off SQLITE_OPEN_CREATE.
     flags = (~SQLITE_OPEN_CREATE & flags);
   }
-  nsRefPtr<Connection> clone = new Connection(mStorageService, flags);
-  NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
+
+  nsRefPtr<Connection> clone = new Connection(mStorageService, flags,
+                                              mAsyncOnly);
 
-  nsresult rv = mFileURL ? clone->initialize(mFileURL)
-                         : clone->initialize(mDatabaseFile);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<AsyncInitializeClone> initEvent =
+    new AsyncInitializeClone(this, clone, aReadOnly, aCallback);
+  nsCOMPtr<nsIEventTarget> target = clone->getAsyncExecutionTarget();
+  if (!target) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return target->Dispatch(initEvent, NS_DISPATCH_NORMAL);
+}
+
+nsresult
+Connection::initializeClone(Connection* aClone, bool aReadOnly)
+{
+  nsresult rv = mFileURL ? aClone->initialize(mFileURL)
+                         : aClone->initialize(mDatabaseFile);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   // Copy over pragmas from the original connection.
   static const char * pragmas[] = {
     "cache_size",
     "temp_store",
     "foreign_keys",
     "journal_size_limit",
     "synchronous",
@@ -1027,26 +1133,57 @@ Connection::Clone(bool aReadOnly,
     pragmaQuery.Append(pragmas[i]);
     nsCOMPtr<mozIStorageStatement> stmt;
     rv = CreateStatement(pragmaQuery, getter_AddRefs(stmt));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     bool hasResult = false;
     if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
       pragmaQuery.AppendLiteral(" = ");
       pragmaQuery.AppendInt(stmt->AsInt32(0));
-      rv = clone->ExecuteSimpleSQL(pragmaQuery);
+      rv = aClone->ExecuteSimpleSQL(pragmaQuery);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
   // Copy any functions that have been added to this connection.
   SQLiteMutexAutoLock lockedScope(sharedDBMutex);
-  (void)mFunctions.EnumerateRead(copyFunctionEnumerator, clone);
+  (void)mFunctions.EnumerateRead(copyFunctionEnumerator, aClone);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::Clone(bool aReadOnly,
+                  mozIStorageConnection **_connection)
+{
+  MOZ_ASSERT(threadOpenedOn == NS_GetCurrentThread());
+
+  PROFILER_LABEL("storage", "Connection::Clone");
+  if (!mDBConn)
+    return NS_ERROR_NOT_INITIALIZED;
+  if (!mDatabaseFile)
+    return NS_ERROR_UNEXPECTED;
 
-  NS_ADDREF(*_connection = clone);
+  int flags = mFlags;
+  if (aReadOnly) {
+    // Turn off SQLITE_OPEN_READWRITE, and set SQLITE_OPEN_READONLY.
+    flags = (~SQLITE_OPEN_READWRITE & flags) | SQLITE_OPEN_READONLY;
+    // Turn off SQLITE_OPEN_CREATE.
+    flags = (~SQLITE_OPEN_CREATE & flags);
+  }
+
+  nsRefPtr<Connection> clone = new Connection(mStorageService, flags,
+                                              mAsyncOnly);
+
+  nsresult rv = initializeClone(clone, aReadOnly);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  NS_IF_ADDREF(*_connection = clone);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Connection::GetDefaultPageSize(int32_t *_defaultPageSize)
 {
   *_defaultPageSize = Service::getDefaultPageSize();
   return NS_OK;
--- a/storage/src/mozStorageConnection.h
+++ b/storage/src/mozStorageConnection.h
@@ -5,23 +5,27 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_storage_Connection_h
 #define mozilla_storage_Connection_h
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Mutex.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
 #include "nsIInterfaceRequestor.h"
 
 #include "nsDataHashtable.h"
 #include "mozIStorageProgressHandler.h"
 #include "SQLiteMutex.h"
 #include "mozIStorageConnection.h"
 #include "mozStorageService.h"
+#include "mozIStorageAsyncConnection.h"
+#include "mozIStorageCompletionCallback.h"
 
 #include "nsIMutableArray.h"
 #include "mozilla/Attributes.h"
 
 #include "sqlite3.h"
 
 struct PRLock;
 class nsIFile;
@@ -32,16 +36,17 @@ class nsIThread;
 namespace mozilla {
 namespace storage {
 
 class Connection MOZ_FINAL : public mozIStorageConnection
                            , public nsIInterfaceRequestor
 {
 public:
   NS_DECL_ISUPPORTS
+  NS_DECL_MOZISTORAGEASYNCCONNECTION
   NS_DECL_MOZISTORAGECONNECTION
   NS_DECL_NSIINTERFACEREQUESTOR
 
   /**
    * Structure used to describe user functions on the database connection.
    */
   struct FunctionInfo {
     enum FunctionType {
@@ -55,18 +60,23 @@ public:
   };
 
   /**
    * @param aService
    *        Pointer to the storage service.  Held onto for the lifetime of the
    *        connection.
    * @param aFlags
    *        The flags to pass to sqlite3_open_v2.
+   * @param aAsyncOnly
+   *        If |true|, the Connection only implements asynchronous interface:
+   *        - |mozIStorageAsyncConnection|;
+   *        If |false|, the result also implements synchronous interface:
+   *        - |mozIStorageConnection|.
    */
-  Connection(Service *aService, int aFlags);
+  Connection(Service *aService, int aFlags, bool aAsyncOnly);
 
   /**
    * Creates the connection to an in-memory database.
    */
   nsresult initialize();
 
   /**
    * Creates the connection to the database.
@@ -157,19 +167,21 @@ public:
   }
 
   /**
    * True if this is an async connection, it is shutting down and it is not
    * closed yet.
    */
   bool isAsyncClosing();
 
+
+  nsresult initializeClone(Connection *aClone, bool aReadOnly);
+
 private:
   ~Connection();
-
   nsresult initializeInternal(nsIFile *aDatabaseFile);
 
   /**
    * Sets the database into a closed state so no further actions can be
    * performed.
    *
    * @note mDBConn is set to NULL in this method.
    */
@@ -257,14 +269,62 @@ private:
    * Stores the flags we passed to sqlite3_open_v2.
    */
   const int mFlags;
 
   // This is here for two reasons: 1) It's used to make sure that the
   // connections do not outlive the service.  2) Our custom collating functions
   // call its localeCompareStrings() method.
   nsRefPtr<Service> mStorageService;
+
+  /**
+   * If |false|, this instance supports synchronous operations
+   * and it can be cast to |mozIStorageConnection|.
+   */
+  const bool mAsyncOnly;
+};
+
+
+/**
+ * A Runnable designed to call a mozIStorageCompletionCallback on
+ * the appropriate thread.
+ */
+class CallbackComplete MOZ_FINAL : public nsRunnable
+{
+public:
+  /**
+   * @param aValue The result to pass to the callback. It must
+   *               already be owned by the main thread.
+   * @param aCallback The callback. It must already be owned by the
+   *                  main thread.
+   */
+  CallbackComplete(nsresult aStatus,
+                   nsISupports* aValue,
+                   already_AddRefed<mozIStorageCompletionCallback> aCallback)
+    : mStatus(aStatus)
+    , mValue(aValue)
+    , mCallback(aCallback)
+  {
+  }
+
+  NS_IMETHOD Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    nsresult rv = mCallback->Complete(mStatus, mValue);
+
+    // Ensure that we release on the main thread
+    mValue = nullptr;
+    mCallback = nullptr;
+    return rv;
+  }
+
+private:
+  nsresult mStatus;
+  nsCOMPtr<nsISupports> mValue;
+  // This is a nsRefPtr<T> and not a nsCOMPtr<T> because
+  // nsCOMP<T> would cause an off-main thread QI, which
+  // is not a good idea (and crashes XPConnect).
+  nsRefPtr<mozIStorageCompletionCallback> mCallback;
 };
 
 } // namespace storage
 } // namespace mozilla
 
 #endif // mozilla_storage_Connection_h
--- a/storage/src/mozStoragePrivateHelpers.cpp
+++ b/storage/src/mozStoragePrivateHelpers.cpp
@@ -156,17 +156,17 @@ class CallbackEvent : public nsRunnable
 public:
   CallbackEvent(mozIStorageCompletionCallback *aCallback)
   : mCallback(aCallback)
   {
   }
 
   NS_IMETHOD Run()
   {
-    (void)mCallback->Complete();
+    (void)mCallback->Complete(NS_OK, nullptr);
     return NS_OK;
   }
 private:
   nsCOMPtr<mozIStorageCompletionCallback> mCallback;
 };
 } // anonymous namespace
 already_AddRefed<nsIRunnable>
 newCompletionEvent(mozIStorageCompletionCallback *aCallback)
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -15,19 +15,21 @@
 #include "nsCollationCID.h"
 #include "nsEmbedCID.h"
 #include "nsThreadUtils.h"
 #include "mozStoragePrivateHelpers.h"
 #include "nsILocale.h"
 #include "nsILocaleService.h"
 #include "nsIXPConnect.h"
 #include "nsIObserverService.h"
+#include "nsIPropertyBag2.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/mozPoisonWrite.h"
+#include "mozIStorageCompletionCallback.h"
 
 #include "sqlite3.h"
 
 #ifdef SQLITE_OS_WIN
 // "windows.h" was included and it can #define lots of things we care about...
 #undef CompareString
 #endif
 
@@ -639,37 +641,182 @@ Service::OpenSpecialDatabase(const char 
     NS_ENSURE_SUCCESS(rv, rv);
 
     // fall through to DB initialization
   }
   else {
     return NS_ERROR_INVALID_ARG;
   }
 
-  nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE);
+  nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
 
   rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
   NS_ENSURE_SUCCESS(rv, rv);
 
   msc.forget(_connection);
   return NS_OK;
 
 }
 
+namespace {
+
+class AsyncInitDatabase MOZ_FINAL : public nsRunnable
+{
+public:
+  AsyncInitDatabase(Connection* aConnection,
+                    nsIFile* aStorageFile,
+                    int32_t aGrowthIncrement,
+                    mozIStorageCompletionCallback* aCallback)
+    : mConnection(aConnection)
+    , mStorageFile(aStorageFile)
+    , mGrowthIncrement(aGrowthIncrement)
+    , mCallback(aCallback)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
+                               : mConnection->initialize();
+    if (NS_FAILED(rv)) {
+      return DispatchResult(rv, nullptr);
+    }
+
+    if (mGrowthIncrement >= 0) {
+      // Ignore errors. In the future, we might wish to log them.
+      (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
+    }
+
+    return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
+                          mConnection));
+  }
+
+private:
+  nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
+    nsRefPtr<CallbackComplete> event =
+      new CallbackComplete(aStatus,
+                           aValue,
+                           mCallback.forget());
+    return NS_DispatchToMainThread(event);
+  }
+
+  ~AsyncInitDatabase()
+  {
+    nsCOMPtr<nsIThread> thread;
+    DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+    (void)NS_ProxyRelease(thread, mStorageFile);
+
+    // Handle ambiguous nsISupports inheritance.
+    Connection *rawConnection = nullptr;
+    mConnection.swap(rawConnection);
+    (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
+                                                    rawConnection));
+
+    // Generally, the callback will be released by CallbackComplete.
+    // However, if for some reason Run() is not executed, we still
+    // need to ensure that it is released here.
+    mozIStorageCompletionCallback *rawCallback = nullptr;
+    mCallback.swap(rawCallback);
+    (void)NS_ProxyRelease(thread, rawCallback);
+  }
+
+  nsRefPtr<Connection> mConnection;
+  nsCOMPtr<nsIFile> mStorageFile;
+  int32_t mGrowthIncrement;
+  nsRefPtr<mozIStorageCompletionCallback> mCallback;
+};
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
+                           nsIPropertyBag2 *aOptions,
+                           mozIStorageCompletionCallback *aCallback)
+{
+  if (!NS_IsMainThread()) {
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+  NS_ENSURE_ARG(aDatabaseStore);
+  NS_ENSURE_ARG(aCallback);
+
+  nsCOMPtr<nsIFile> storageFile;
+  int flags = SQLITE_OPEN_READWRITE;
+
+  nsCOMPtr<nsISupports> dbStore;
+  nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
+  if (NS_SUCCEEDED(rv)) {
+    // Generally, aDatabaseStore holds the database nsIFile.
+    storageFile = do_QueryInterface(dbStore, &rv);
+    if (NS_FAILED(rv)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    rv = storageFile->Clone(getter_AddRefs(storageFile));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
+    flags |= SQLITE_OPEN_CREATE;
+
+    // Extract and apply the shared-cache option.
+    bool shared = false;
+    if (aOptions) {
+      rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
+      if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
+        return NS_ERROR_INVALID_ARG;
+      }
+    }
+    flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
+  } else {
+    // Sometimes, however, it's a special database name.
+    nsAutoCString keyString;
+    rv = aDatabaseStore->GetAsACString(keyString);
+    if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
+      return NS_ERROR_INVALID_ARG;
+    }
+
+    // Just fall through with NULL storageFile, this will cause the storage
+    // connection to use a memory DB.
+  }
+
+  int32_t growthIncrement = -1;
+  if (aOptions && storageFile) {
+    rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
+                                      &growthIncrement);
+    if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+
+  // Create connection on this thread, but initialize it on its helper thread.
+  nsRefPtr<Connection> msc = new Connection(this, flags, true);
+  nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
+  MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
+
+  nsRefPtr<AsyncInitDatabase> asyncInit =
+    new AsyncInitDatabase(msc,
+                          storageFile,
+                          growthIncrement,
+                          aCallback);
+  return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
+}
+
 NS_IMETHODIMP
 Service::OpenDatabase(nsIFile *aDatabaseFile,
                       mozIStorageConnection **_connection)
 {
   NS_ENSURE_ARG(aDatabaseFile);
 
   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   // reasons.
   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
               SQLITE_OPEN_CREATE;
-  nsRefPtr<Connection> msc = new Connection(this, flags);
+  nsRefPtr<Connection> msc = new Connection(this, flags, false);
 
   nsresult rv = msc->initialize(aDatabaseFile);
   NS_ENSURE_SUCCESS(rv, rv);
 
   msc.forget(_connection);
   return NS_OK;
 }
 
@@ -678,17 +825,17 @@ Service::OpenUnsharedDatabase(nsIFile *a
                               mozIStorageConnection **_connection)
 {
   NS_ENSURE_ARG(aDatabaseFile);
 
   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   // reasons.
   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
               SQLITE_OPEN_CREATE;
-  nsRefPtr<Connection> msc = new Connection(this, flags);
+  nsRefPtr<Connection> msc = new Connection(this, flags, false);
 
   nsresult rv = msc->initialize(aDatabaseFile);
   NS_ENSURE_SUCCESS(rv, rv);
 
   msc.forget(_connection);
   return NS_OK;
 }
 
@@ -697,17 +844,17 @@ Service::OpenDatabaseWithFileURL(nsIFile
                                  mozIStorageConnection **_connection)
 {
   NS_ENSURE_ARG(aFileURL);
 
   // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   // reasons.
   int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
               SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
-  nsRefPtr<Connection> msc = new Connection(this, flags);
+  nsRefPtr<Connection> msc = new Connection(this, flags, false);
 
   nsresult rv = msc->initialize(aFileURL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   msc.forget(_connection);
   return NS_OK;
 }
 
--- a/storage/test/storage_test_harness.h
+++ b/storage/test/storage_test_harness.h
@@ -171,17 +171,17 @@ NS_IMETHODIMP
 AsyncStatementSpinner::HandleCompletion(uint16_t aReason)
 {
   completionReason = aReason;
   mCompleted = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-AsyncStatementSpinner::Complete()
+AsyncStatementSpinner::Complete(nsresult, nsISupports*)
 {
   mCompleted = true;
   return NS_OK;
 }
 
 void AsyncStatementSpinner::SpinUntilCompleted()
 {
   nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -217,17 +217,17 @@ class BlockingConnectionCloseCallback MO
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
   BlockingConnectionCloseCallback();
   void Spin();
 };
 
 NS_IMETHODIMP
-BlockingConnectionCloseCallback::Complete()
+BlockingConnectionCloseCallback::Complete(nsresult, nsISupports*)
 {
   mDone = true;
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   MOZ_ASSERT(os);
   if (!os)
     return NS_OK;
   DebugOnly<nsresult> rv = os->NotifyObservers(nullptr,
                                                TOPIC_PLACES_CONNECTION_CLOSED,