dom/cache/DBAction.cpp
author James Teh <jteh@mozilla.com>
Thu, 07 Mar 2019 18:10:13 +0000
changeset 520910 c26d4a8d43c4d0d6f6e13135b8f35924123fe99e
parent 505383 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1527922: Ensure the Reload button is disabled when testing against blank tabs in the browser toolbar key nav tests. r=Gijs For a blank tab, the Reload button should be disabled. These tests depend on this. This seems to be true when setting the new tab page to blank in Firefox Options. However, when we open about:blank with BrowserTestUtils.withNewTab, this is unreliable. That is, sometimes the Reload button is enabled, sometimes it isn't. I don't understand why this happens. For the purposes of these tests, just force the Reload button to be disabled for new, blank tabs so we get consistent results. Differential Revision: https://phabricator.services.mozilla.com/D22449

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "mozilla/dom/cache/DBAction.h"

#include "mozilla/dom/cache/Connection.h"
#include "mozilla/dom/cache/DBSchema.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/QuotaClient.h"
#include "mozilla/dom/quota/PersistenceType.h"
#include "mozilla/net/nsFileProtocolHandler.h"
#include "mozIStorageConnection.h"
#include "mozIStorageService.h"
#include "mozStorageCID.h"
#include "nsIFile.h"
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsIFileURL.h"
#include "nsThreadUtils.h"

namespace mozilla {
namespace dom {
namespace cache {

using mozilla::dom::quota::AssertIsOnIOThread;
using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
using mozilla::dom::quota::PersistenceType;

namespace {

nsresult WipeDatabase(const QuotaInfo& aQuotaInfo, nsIFile* aDBFile,
                      nsIFile* aDBDir) {
  MOZ_DIAGNOSTIC_ASSERT(aDBFile);
  MOZ_DIAGNOSTIC_ASSERT(aDBDir);

  nsresult rv = RemoveNsIFile(aQuotaInfo, aDBFile);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Note, the -wal journal file will be automatically deleted by sqlite when
  // the new database is created.  No need to explicitly delete it here.

  // Delete the morgue as well.
  rv = BodyDeleteDir(aQuotaInfo, aDBDir);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = WipePaddingFile(aQuotaInfo, aDBDir);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return rv;
}

}  // namespace

DBAction::DBAction(Mode aMode) : mMode(aMode) {}

DBAction::~DBAction() {}

void DBAction::RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo,
                           Data* aOptionalData) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aResolver);
  MOZ_DIAGNOSTIC_ASSERT(aQuotaInfo.mDir);

  if (IsCanceled()) {
    aResolver->Resolve(NS_ERROR_ABORT);
    return;
  }

  nsCOMPtr<nsIFile> dbDir;
  nsresult rv = aQuotaInfo.mDir->Clone(getter_AddRefs(dbDir));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aResolver->Resolve(rv);
    return;
  }

  rv = dbDir->Append(NS_LITERAL_STRING("cache"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    aResolver->Resolve(rv);
    return;
  }

  nsCOMPtr<mozIStorageConnection> conn;

  // Attempt to reuse the connection opened by a previous Action.
  if (aOptionalData) {
    conn = aOptionalData->GetConnection();
  }

  // If there is no previous Action, then we must open one.
  if (!conn) {
    rv = OpenConnection(aQuotaInfo, dbDir, getter_AddRefs(conn));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      aResolver->Resolve(rv);
      return;
    }
    MOZ_DIAGNOSTIC_ASSERT(conn);

    // Save this connection in the shared Data object so later Actions can
    // use it.  This avoids opening a new connection for every Action.
    if (aOptionalData) {
      // Since we know this connection will be around for as long as the
      // Cache is open, use our special wrapped connection class.  This
      // will let us perform certain operations once the Cache origin
      // is closed.
      nsCOMPtr<mozIStorageConnection> wrapped = new Connection(conn);
      aOptionalData->SetConnection(wrapped);
    }
  }

  RunWithDBOnTarget(aResolver, aQuotaInfo, dbDir, conn);
}

nsresult DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
                                  mozIStorageConnection** aConnOut) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aDBDir);
  MOZ_DIAGNOSTIC_ASSERT(aConnOut);

  bool exists;
  nsresult rv = aDBDir->Exists(&exists);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (!exists) {
    if (NS_WARN_IF(mMode != Create)) {
      return NS_ERROR_FILE_NOT_FOUND;
    }
    rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  rv = OpenDBConnection(aQuotaInfo, aDBDir, aConnOut);

  return rv;
}

SyncDBAction::SyncDBAction(Mode aMode) : DBAction(aMode) {}

SyncDBAction::~SyncDBAction() {}

void SyncDBAction::RunWithDBOnTarget(Resolver* aResolver,
                                     const QuotaInfo& aQuotaInfo,
                                     nsIFile* aDBDir,
                                     mozIStorageConnection* aConn) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aResolver);
  MOZ_DIAGNOSTIC_ASSERT(aDBDir);
  MOZ_DIAGNOSTIC_ASSERT(aConn);

  nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn);
  aResolver->Resolve(rv);
}

// static
nsresult OpenDBConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
                          mozIStorageConnection** aConnOut) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aDBDir);
  MOZ_DIAGNOSTIC_ASSERT(aConnOut);

  nsCOMPtr<mozIStorageConnection> conn;

  nsCOMPtr<nsIFile> dbFile;
  nsresult rv = aDBDir->Clone(getter_AddRefs(dbFile));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  bool exists = false;
  rv = dbFile->Exists(&exists);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Use our default file:// protocol handler directly to construct the database
  // URL.  This avoids any problems if a plugin registers a custom file://
  // handler.  If such a custom handler used javascript, then we would have a
  // bad time running off the main thread here.
  RefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler();
  rv = handler->Init();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIURIMutator> mutator;
  rv = handler->NewFileURIMutator(dbFile, getter_AddRefs(mutator));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIFileURL> dbFileUrl;

  nsAutoCString type;
  PersistenceTypeToText(PERSISTENCE_TYPE_DEFAULT, type);

  rv = NS_MutateURI(mutator)
           .SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
                     NS_LITERAL_CSTRING("&group=") + aQuotaInfo.mGroup +
                     NS_LITERAL_CSTRING("&origin=") + aQuotaInfo.mOrigin +
                     NS_LITERAL_CSTRING("&cache=private"))
           .Finalize(dbFileUrl);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<mozIStorageService> ss =
      do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
  if (NS_WARN_IF(!ss)) {
    return NS_ERROR_UNEXPECTED;
  }

  rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
  if (rv == NS_ERROR_FILE_CORRUPTED) {
    NS_WARNING("Cache database corrupted. Recreating empty database.");

    conn = nullptr;

    // There is nothing else we can do to recover.  Also, this data can
    // be deleted by QuotaManager at any time anyways.
    rv = WipeDatabase(aQuotaInfo, dbFile, aDBDir);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Check the schema to make sure it is not too old.
  int32_t schemaVersion = 0;
  rv = conn->GetSchemaVersion(&schemaVersion);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }
  if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
    conn = nullptr;
    rv = WipeDatabase(aQuotaInfo, dbFile, aDBDir);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(conn));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  rv = db::InitializeConnection(conn);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  conn.forget(aConnOut);

  return rv;
}

}  // namespace cache
}  // namespace dom
}  // namespace mozilla