author Agi Sferro <>
Wed, 13 Nov 2019 20:29:15 +0000
changeset 501843 bf09025d6f98793965458b5805a8f53564ce540a
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1530402 - Implement {Browser,Page}Action for GeckoView. r=snorp,mixedpuppy,esawin Design doc: Differential Revision:

/* -*- Mode: C++; tab-width: 8; 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 */


#include "nsAutoPtr.h"
#include "nsString.h"
#include "mozilla/DebugOnly.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"

#include "mozIStorageAsyncConnection.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozIStoragePendingStatement.h"
#include "nsError.h"
#include "nsIXPConnect.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.
 * A common use is to create an instance with aCommitOnComplete = false
 * (rollback), then call Commit() on this object manually when your function
 * completes successfully.
 * @note 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 asked for, and you won't be able
 * to rollback.
 * @param aConnection
 *        The connection to create the transaction on.
 * @param aCommitOnComplete
 *        Controls whether the transaction is committed or rolled back when
 *        this object goes out of scope.
 * @param aType [optional]
 *        The transaction type, as defined in mozIStorageConnection. Uses the
 *        default transaction behavior for the connection if unspecified.
 * @param aAsyncCommit [optional]
 *        Whether commit should be executed asynchronously on the helper thread.
 *        This is a special option introduced as an interim solution to reduce
 *        main-thread fsyncs in Places.  Can only be used on main-thread.
 *        Notice that async commit might cause synchronous statements to fail
 *        with SQLITE_BUSY.  A possible mitigation strategy is to use
 *        PRAGMA busy_timeout, but notice that might cause main-thread jank.
 *        Finally, if the database is using WAL journaling mode, other
 *        connections won't see the changes done in async committed transactions
 *        until commit is complete.
 *        For all of the above reasons, this should only be used as an interim
 *        solution and avoided completely if possible.
class mozStorageTransaction {
      mozIStorageConnection* aConnection, bool aCommitOnComplete,
      int32_t aType = mozIStorageConnection::TRANSACTION_DEFAULT,
      bool aAsyncCommit = false)
      : mConnection(aConnection),
        mAsyncCommit(aAsyncCommit) {
    if (mConnection) {
      nsAutoCString query("BEGIN");
      int32_t type = aType;
      if (type == mozIStorageConnection::TRANSACTION_DEFAULT) {
      switch (type) {
        case mozIStorageConnection::TRANSACTION_IMMEDIATE:
          query.AppendLiteral(" IMMEDIATE");
        case mozIStorageConnection::TRANSACTION_EXCLUSIVE:
          query.AppendLiteral(" EXCLUSIVE");
        case mozIStorageConnection::TRANSACTION_DEFERRED:
          query.AppendLiteral(" DEFERRED");
          MOZ_ASSERT(false, "Unknown transaction type");
      // If a transaction is already in progress, this will fail, since Sqlite
      // doesn't support nested transactions.
      mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query));

  ~mozStorageTransaction() {
    if (mConnection && mHasTransaction && !mCompleted) {
      if (mCommitOnComplete) {
        mozilla::DebugOnly<nsresult> rv = Commit();
                             "A transaction didn't commit correctly");
      } else {
        mozilla::DebugOnly<nsresult> rv = Rollback();
                             "A transaction didn't rollback correctly");

   * Commits the transaction if one is in progress. If one is not in progress,
   * this is a NOP since the actual owner of the transaction outside of our
   * scope is in charge of finally committing or rolling back the transaction.
  nsresult Commit() {
    if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
    mCompleted = true;

    // TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
    // it, thus the transaction might stay open until the next COMMIT.
    nsresult rv;
    if (mAsyncCommit) {
      nsCOMPtr<mozIStoragePendingStatement> ps;
      rv = mConnection->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING("COMMIT"),
                                              nullptr, getter_AddRefs(ps));
    } else {
      rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("COMMIT"));

    if (NS_SUCCEEDED(rv)) mHasTransaction = false;

    return rv;

   * Rolls back the transaction if one is in progress. If one is not in
   * progress, this is a NOP since the actual owner of the transaction outside
   * of our scope is in charge of finally rolling back the transaction.
  nsresult Rollback() {
    if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
    mCompleted = true;

    // TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
    // a busy error, so this handling can be removed.
    nsresult rv;
    do {
      rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("ROLLBACK"));
      if (rv == NS_ERROR_STORAGE_BUSY) (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
    } while (rv == NS_ERROR_STORAGE_BUSY);

    if (NS_SUCCEEDED(rv)) mHasTransaction = false;

    return rv;

  nsCOMPtr<mozIStorageConnection> mConnection;
  bool mHasTransaction;
  bool mCommitOnComplete;
  bool mCompleted;
  bool mAsyncCommit;

 * 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.
class MOZ_STACK_CLASS mozStorageStatementScoper {
  explicit mozStorageStatementScoper(mozIStorageStatement* aStatement)
      : mStatement(aStatement) {}
  ~mozStorageStatementScoper() {
    if (mStatement) mStatement->Reset();

   * Call this to make the statement not reset. You might do this if you know
   * that the statement has been reset.
  void Abandon() { mStatement = nullptr; }

  nsCOMPtr<mozIStorageStatement> mStatement;

// Use this to make queries uniquely identifiable in telemetry
// statistics, especially PRAGMAs.  We don't include __LINE__ so that
// queries are stable in the face of source code changes.
#define MOZ_STORAGE_UNIQUIFY_QUERY_STR "/* " __FILE__ " */ "