Bug 702559 - First implementation of mozIStorageAsyncConnection;r=mak
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 27 Jun 2013 09:00:59 -0400
changeset 136683 90143e24ad66e9c30b843ea3d6547e3d06aa55ce
parent 136682 aeae60c65cc7669c139bf42cdaab8f2a7d936362
child 136684 e3aab5e3d15e2f6e4e6e5ea2651d122ceff5cbec
push idunknown
push userunknown
push dateunknown
reviewersmak
bugs702559
milestone25.0a1
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,