blob: 9089920a8329378b1e6d81c2caa93fa436c6a51a [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/indexed_db/indexed_db_factory.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "content/browser/indexed_db/indexed_db_backing_store.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_tracing.h"
#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
#include "webkit/common/database/database_identifier.h"
namespace content {
const int64 kBackingStoreGracePeriodMs = 2000;
IndexedDBFactory::IndexedDBFactory(IndexedDBContextImpl* context)
: context_(context) {}
IndexedDBFactory::~IndexedDBFactory() {}
void IndexedDBFactory::ReleaseDatabase(
const IndexedDBDatabase::Identifier& identifier,
const GURL& origin_url,
bool forcedClose) {
IndexedDBDatabaseMap::iterator it = database_map_.find(identifier);
DCHECK(it != database_map_.end());
DCHECK(!it->second->backing_store());
database_map_.erase(it);
// No grace period on a forced-close, as the initiator is
// assuming the backing store will be released once all
// connections are closed.
ReleaseBackingStore(origin_url, forcedClose);
}
void IndexedDBFactory::ReleaseBackingStore(const GURL& origin_url,
bool immediate) {
// Only close if this is the last reference.
if (!HasLastBackingStoreReference(origin_url))
return;
// If this factory does hold the last reference to the backing store, it can
// be closed - but unless requested to close it immediately, keep it around
// for a short period so that a re-open is fast.
if (immediate) {
CloseBackingStore(origin_url);
return;
}
// Start a timer to close the backing store, unless something else opens it
// in the mean time.
DCHECK(!backing_store_map_[origin_url]->close_timer()->IsRunning());
backing_store_map_[origin_url]->close_timer()->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kBackingStoreGracePeriodMs),
base::Bind(&IndexedDBFactory::MaybeCloseBackingStore, this, origin_url));
}
void IndexedDBFactory::MaybeCloseBackingStore(const GURL& origin_url) {
// Another reference may have opened since the maybe-close was posted, so it
// is necessary to check again.
if (HasLastBackingStoreReference(origin_url))
CloseBackingStore(origin_url);
}
void IndexedDBFactory::CloseBackingStore(const GURL& origin_url) {
IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url);
DCHECK(it != backing_store_map_.end());
// Stop the timer (if it's running) - this may happen if the timer was started
// and then a forced close occurs.
it->second->close_timer()->Stop();
backing_store_map_.erase(it);
}
bool IndexedDBFactory::HasLastBackingStoreReference(const GURL& origin_url)
const {
IndexedDBBackingStore* ptr;
{
// Scope so that the implicit scoped_refptr<> is freed.
IndexedDBBackingStoreMap::const_iterator it =
backing_store_map_.find(origin_url);
DCHECK(it != backing_store_map_.end());
ptr = it->second.get();
}
return ptr->HasOneRef();
}
void IndexedDBFactory::ContextDestroyed() {
// Timers on backing stores hold a reference to this factory. When the
// context (which nominally owns this factory) is destroyed during thread
// termination the timers must be stopped so that this factory and the
// stores can be disposed of.
for (IndexedDBBackingStoreMap::iterator it = backing_store_map_.begin();
it != backing_store_map_.end();
++it)
it->second->close_timer()->Stop();
backing_store_map_.clear();
context_ = NULL;
}
void IndexedDBFactory::GetDatabaseNames(
scoped_refptr<IndexedDBCallbacks> callbacks,
const GURL& origin_url,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactory::GetDatabaseNames");
// TODO(dgrogan): Plumb data_loss back to script eventually?
WebKit::WebIDBCallbacks::DataLoss data_loss;
bool disk_full;
scoped_refptr<IndexedDBBackingStore> backing_store =
OpenBackingStore(origin_url, data_directory, &data_loss, &disk_full);
if (!backing_store) {
callbacks->OnError(
IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
"Internal error opening backing store for "
"indexedDB.webkitGetDatabaseNames."));
return;
}
callbacks->OnSuccess(backing_store->GetDatabaseNames());
}
void IndexedDBFactory::DeleteDatabase(
const string16& name,
scoped_refptr<IndexedDBCallbacks> callbacks,
const GURL& origin_url,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactory::DeleteDatabase");
IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
if (it != database_map_.end()) {
// If there are any connections to the database, directly delete the
// database.
it->second->DeleteDatabase(callbacks);
return;
}
// TODO(dgrogan): Plumb data_loss back to script eventually?
WebKit::WebIDBCallbacks::DataLoss data_loss;
bool disk_full = false;
scoped_refptr<IndexedDBBackingStore> backing_store =
OpenBackingStore(origin_url, data_directory, &data_loss, &disk_full);
if (!backing_store) {
callbacks->OnError(
IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16(
"Internal error opening backing store "
"for indexedDB.deleteDatabase.")));
return;
}
scoped_refptr<IndexedDBDatabase> database =
IndexedDBDatabase::Create(name, backing_store, this, unique_identifier);
if (!database) {
callbacks->OnError(IndexedDBDatabaseError(
WebKit::WebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16(
"Internal error creating database backend for "
"indexedDB.deleteDatabase.")));
return;
}
database_map_[unique_identifier] = database;
database->DeleteDatabase(callbacks);
database_map_.erase(unique_identifier);
}
void IndexedDBFactory::HandleBackingStoreFailure(const GURL& origin_url) {
// NULL after ContextDestroyed() called, and in some unit tests.
if (!context_)
return;
context_->ForceClose(origin_url);
}
bool IndexedDBFactory::IsBackingStoreOpenForTesting(const GURL& origin_url)
const {
return backing_store_map_.find(origin_url) != backing_store_map_.end();
}
scoped_refptr<IndexedDBBackingStore> IndexedDBFactory::OpenBackingStore(
const GURL& origin_url,
const base::FilePath& data_directory,
WebKit::WebIDBCallbacks::DataLoss* data_loss,
bool* disk_full) {
const bool open_in_memory = data_directory.empty();
IndexedDBBackingStoreMap::iterator it2 = backing_store_map_.find(origin_url);
if (it2 != backing_store_map_.end()) {
it2->second->close_timer()->Stop();
return it2->second;
}
scoped_refptr<IndexedDBBackingStore> backing_store;
if (open_in_memory) {
backing_store = IndexedDBBackingStore::OpenInMemory(origin_url);
} else {
backing_store = IndexedDBBackingStore::Open(
origin_url, data_directory, data_loss, disk_full);
}
if (backing_store.get()) {
backing_store_map_[origin_url] = backing_store;
// If an in-memory database, bind lifetime to this factory instance.
if (open_in_memory)
session_only_backing_stores_.insert(backing_store);
// All backing stores associated with this factory should be of the same
// type.
DCHECK(session_only_backing_stores_.empty() || open_in_memory);
return backing_store;
}
return 0;
}
void IndexedDBFactory::Open(
const string16& name,
int64 version,
int64 transaction_id,
scoped_refptr<IndexedDBCallbacks> callbacks,
scoped_refptr<IndexedDBDatabaseCallbacks> database_callbacks,
const GURL& origin_url,
const base::FilePath& data_directory) {
IDB_TRACE("IndexedDBFactory::Open");
scoped_refptr<IndexedDBDatabase> database;
IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
WebKit::WebIDBCallbacks::DataLoss data_loss =
WebKit::WebIDBCallbacks::DataLossNone;
bool disk_full = false;
if (it == database_map_.end()) {
scoped_refptr<IndexedDBBackingStore> backing_store =
OpenBackingStore(origin_url, data_directory, &data_loss, &disk_full);
if (!backing_store) {
if (disk_full) {
callbacks->OnError(
IndexedDBDatabaseError(WebKit::WebIDBDatabaseExceptionQuotaError,
ASCIIToUTF16(
"Encountered full disk while opening "
"backing store for indexedDB.open.")));
return;
}
callbacks->OnError(IndexedDBDatabaseError(
WebKit::WebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16(
"Internal error opening backing store for indexedDB.open.")));
return;
}
database =
IndexedDBDatabase::Create(name, backing_store, this, unique_identifier);
if (!database) {
callbacks->OnError(IndexedDBDatabaseError(
WebKit::WebIDBDatabaseExceptionUnknownError,
ASCIIToUTF16(
"Internal error creating database backend for indexedDB.open.")));
return;
}
database_map_[unique_identifier] = database;
} else {
database = it->second;
}
database->OpenConnection(
callbacks, database_callbacks, transaction_id, version, data_loss);
}
std::vector<IndexedDBDatabase*> IndexedDBFactory::GetOpenDatabasesForOrigin(
const GURL& origin_url) const {
std::vector<IndexedDBDatabase*> result;
for (IndexedDBDatabaseMap::const_iterator it = database_map_.begin();
it != database_map_.end();
++it) {
if (it->first.first == origin_url)
result.push_back(it->second.get());
}
return result;
}
} // namespace content