blob: d36147f076e474a6b5ba7fdb7253eefae39d8dc8 [file] [log] [blame]
// Copyright 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/dom_storage/dom_storage_namespace.h"
#include <set>
#include <utility>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "content/browser/dom_storage/dom_storage_area.h"
#include "content/browser/dom_storage/dom_storage_context_impl.h"
#include "content/browser/dom_storage/dom_storage_task_runner.h"
#include "content/browser/dom_storage/session_storage_database.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "content/public/common/child_process_host.h"
namespace content {
namespace {
static const unsigned int kMaxTransactionLogEntries = 8 * 1024;
} // namespace
DOMStorageNamespace::DOMStorageNamespace(
const base::FilePath& directory,
DOMStorageTaskRunner* task_runner)
: namespace_id_(kLocalStorageNamespaceId),
directory_(directory),
task_runner_(task_runner),
num_aliases_(0),
old_master_for_close_area_(NULL),
master_alias_count_decremented_(false),
ready_for_deletion_pending_aliases_(false),
must_persist_at_shutdown_(false) {
}
DOMStorageNamespace::DOMStorageNamespace(
int64 namespace_id,
const std::string& persistent_namespace_id,
SessionStorageDatabase* session_storage_database,
DOMStorageTaskRunner* task_runner)
: namespace_id_(namespace_id),
persistent_namespace_id_(persistent_namespace_id),
task_runner_(task_runner),
session_storage_database_(session_storage_database),
num_aliases_(0),
old_master_for_close_area_(NULL),
master_alias_count_decremented_(false),
ready_for_deletion_pending_aliases_(false),
must_persist_at_shutdown_(false) {
DCHECK_NE(kLocalStorageNamespaceId, namespace_id);
}
DOMStorageNamespace::~DOMStorageNamespace() {
STLDeleteValues(&transactions_);
DecrementMasterAliasCount();
}
DOMStorageArea* DOMStorageNamespace::OpenStorageArea(const GURL& origin) {
if (alias_master_namespace_)
return alias_master_namespace_->OpenStorageArea(origin);
if (AreaHolder* holder = GetAreaHolder(origin)) {
++(holder->open_count_);
return holder->area_.get();
}
DOMStorageArea* area;
if (namespace_id_ == kLocalStorageNamespaceId) {
area = new DOMStorageArea(origin, directory_, task_runner_.get());
} else {
area = new DOMStorageArea(
namespace_id_, persistent_namespace_id_, origin,
session_storage_database_.get(), task_runner_.get());
}
areas_[origin] = AreaHolder(area, 1);
return area;
}
void DOMStorageNamespace::CloseStorageArea(DOMStorageArea* area) {
AreaHolder* holder = GetAreaHolder(area->origin());
if (alias_master_namespace_) {
DCHECK(!holder);
if (old_master_for_close_area_)
old_master_for_close_area_->CloseStorageArea(area);
else
alias_master_namespace_->CloseStorageArea(area);
return;
}
DCHECK(holder);
DCHECK_EQ(holder->area_.get(), area);
--(holder->open_count_);
// TODO(michaeln): Clean up areas that aren't needed in memory anymore.
// The in-process-webkit based impl didn't do this either, but would be nice.
}
DOMStorageArea* DOMStorageNamespace::GetOpenStorageArea(const GURL& origin) {
if (alias_master_namespace_)
return alias_master_namespace_->GetOpenStorageArea(origin);
AreaHolder* holder = GetAreaHolder(origin);
if (holder && holder->open_count_)
return holder->area_.get();
return NULL;
}
DOMStorageNamespace* DOMStorageNamespace::Clone(
int64 clone_namespace_id,
const std::string& clone_persistent_namespace_id) {
if (alias_master_namespace_) {
return alias_master_namespace_->Clone(clone_namespace_id,
clone_persistent_namespace_id);
}
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, clone_namespace_id);
DOMStorageNamespace* clone = new DOMStorageNamespace(
clone_namespace_id, clone_persistent_namespace_id,
session_storage_database_.get(), task_runner_.get());
AreaMap::const_iterator it = areas_.begin();
// Clone the in-memory structures.
for (; it != areas_.end(); ++it) {
DOMStorageArea* area = it->second.area_->ShallowCopy(
clone_namespace_id, clone_persistent_namespace_id);
clone->areas_[it->first] = AreaHolder(area, 0);
}
// And clone the on-disk structures, too.
if (session_storage_database_.get()) {
task_runner_->PostShutdownBlockingTask(
FROM_HERE,
DOMStorageTaskRunner::COMMIT_SEQUENCE,
base::Bind(base::IgnoreResult(&SessionStorageDatabase::CloneNamespace),
session_storage_database_.get(), persistent_namespace_id_,
clone_persistent_namespace_id));
}
return clone;
}
DOMStorageNamespace* DOMStorageNamespace::CreateAlias(
int64 alias_namespace_id) {
// Creates an alias of the current DOMStorageNamespace.
// The alias will have a reference to this namespace (called the master),
// and all operations will be redirected to the master (in particular,
// the alias will never open any areas of its own, but always redirect
// to the master). Accordingly, an alias will also never undergo the shutdown
// procedure which initiates persisting to disk, since there is never any data
// of its own to persist to disk. DOMStorageContextImpl is the place where
// shutdowns are initiated, but only for non-alias DOMStorageNamespaces.
DCHECK_NE(kLocalStorageNamespaceId, namespace_id_);
DCHECK_NE(kLocalStorageNamespaceId, alias_namespace_id);
DOMStorageNamespace* alias = new DOMStorageNamespace(
alias_namespace_id, persistent_namespace_id_,
session_storage_database_.get(), task_runner_.get());
if (alias_master_namespace_ != NULL) {
DCHECK(alias_master_namespace_->alias_master_namespace_ == NULL);
alias->alias_master_namespace_ = alias_master_namespace_;
} else {
alias->alias_master_namespace_ = this;
}
alias->alias_master_namespace_->num_aliases_++;
return alias;
}
void DOMStorageNamespace::DeleteLocalStorageOrigin(const GURL& origin) {
DCHECK(!session_storage_database_.get());
DCHECK(!alias_master_namespace_.get());
AreaHolder* holder = GetAreaHolder(origin);
if (holder) {
holder->area_->DeleteOrigin();
return;
}
if (!directory_.empty()) {
scoped_refptr<DOMStorageArea> area =
new DOMStorageArea(origin, directory_, task_runner_.get());
area->DeleteOrigin();
}
}
void DOMStorageNamespace::DeleteSessionStorageOrigin(const GURL& origin) {
if (alias_master_namespace_) {
alias_master_namespace_->DeleteSessionStorageOrigin(origin);
return;
}
DOMStorageArea* area = OpenStorageArea(origin);
area->FastClear();
CloseStorageArea(area);
}
void DOMStorageNamespace::PurgeMemory(PurgeOption option) {
if (alias_master_namespace_) {
alias_master_namespace_->PurgeMemory(option);
return;
}
if (directory_.empty())
return; // We can't purge w/o backing on disk.
AreaMap::iterator it = areas_.begin();
while (it != areas_.end()) {
// Leave it alone if changes are pending
if (it->second.area_->HasUncommittedChanges()) {
++it;
continue;
}
// If not in use, we can shut it down and remove
// it from our collection entirely.
if (it->second.open_count_ == 0) {
it->second.area_->Shutdown();
areas_.erase(it++);
continue;
}
if (option == PURGE_AGGRESSIVE) {
// If aggressive is true, we clear caches and such
// for opened areas.
it->second.area_->PurgeMemory();
}
++it;
}
}
void DOMStorageNamespace::Shutdown() {
AreaMap::const_iterator it = areas_.begin();
for (; it != areas_.end(); ++it)
it->second.area_->Shutdown();
}
unsigned int DOMStorageNamespace::CountInMemoryAreas() const {
if (alias_master_namespace_)
return alias_master_namespace_->CountInMemoryAreas();
unsigned int area_count = 0;
for (AreaMap::const_iterator it = areas_.begin(); it != areas_.end(); ++it) {
if (it->second.area_->IsLoadedInMemory())
++area_count;
}
return area_count;
}
DOMStorageNamespace::AreaHolder*
DOMStorageNamespace::GetAreaHolder(const GURL& origin) {
AreaMap::iterator found = areas_.find(origin);
if (found == areas_.end())
return NULL;
return &(found->second);
}
void DOMStorageNamespace::AddTransactionLogProcessId(int process_id) {
DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
DCHECK(transactions_.count(process_id) == 0);
TransactionData* transaction_data = new TransactionData;
transactions_[process_id] = transaction_data;
}
void DOMStorageNamespace::RemoveTransactionLogProcessId(int process_id) {
DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
DCHECK(transactions_.count(process_id) == 1);
delete transactions_[process_id];
transactions_.erase(process_id);
}
SessionStorageNamespace::MergeResult DOMStorageNamespace::Merge(
bool actually_merge,
int process_id,
DOMStorageNamespace* other,
DOMStorageContextImpl* context) {
if (!alias_master_namespace())
return SessionStorageNamespace::MERGE_RESULT_NAMESPACE_NOT_ALIAS;
if (transactions_.count(process_id) < 1)
return SessionStorageNamespace::MERGE_RESULT_NOT_LOGGING;
TransactionData* data = transactions_[process_id];
if (data->max_log_size_exceeded)
return SessionStorageNamespace::MERGE_RESULT_TOO_MANY_TRANSACTIONS;
if (data->log.size() < 1) {
if (actually_merge)
SwitchToNewAliasMaster(other, context);
return SessionStorageNamespace::MERGE_RESULT_NO_TRANSACTIONS;
}
// skip_areas and skip_keys store areas and (area, key) pairs, respectively,
// that have already been handled previously. Any further modifications to
// them will not change the result of the hypothetical merge.
std::set<GURL> skip_areas;
typedef std::pair<GURL, base::string16> OriginKey;
std::set<OriginKey> skip_keys;
// Indicates whether we could still merge the namespaces preserving all
// individual transactions.
for (unsigned int i = 0; i < data->log.size(); i++) {
TransactionRecord& transaction = data->log[i];
if (transaction.transaction_type == TRANSACTION_CLEAR) {
skip_areas.insert(transaction.origin);
continue;
}
if (skip_areas.find(transaction.origin) != skip_areas.end())
continue;
if (skip_keys.find(OriginKey(transaction.origin, transaction.key))
!= skip_keys.end()) {
continue;
}
if (transaction.transaction_type == TRANSACTION_REMOVE ||
transaction.transaction_type == TRANSACTION_WRITE) {
skip_keys.insert(OriginKey(transaction.origin, transaction.key));
continue;
}
if (transaction.transaction_type == TRANSACTION_READ) {
DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
base::NullableString16 other_value = area->GetItem(transaction.key);
other->CloseStorageArea(area);
if (transaction.value != other_value)
return SessionStorageNamespace::MERGE_RESULT_NOT_MERGEABLE;
continue;
}
NOTREACHED();
}
if (!actually_merge)
return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
// Actually perform the merge.
for (unsigned int i = 0; i < data->log.size(); i++) {
TransactionRecord& transaction = data->log[i];
if (transaction.transaction_type == TRANSACTION_READ)
continue;
DOMStorageArea* area = other->OpenStorageArea(transaction.origin);
if (transaction.transaction_type == TRANSACTION_CLEAR) {
area->Clear();
if (context)
context->NotifyAreaCleared(area, transaction.page_url);
}
if (transaction.transaction_type == TRANSACTION_REMOVE) {
base::string16 old_value;
area->RemoveItem(transaction.key, &old_value);
if (context) {
context->NotifyItemRemoved(area, transaction.key, old_value,
transaction.page_url);
}
}
if (transaction.transaction_type == TRANSACTION_WRITE) {
base::NullableString16 old_value;
area->SetItem(transaction.key, base::string16(transaction.value.string()),
&old_value);
if (context) {
context->NotifyItemSet(area, transaction.key,transaction.value.string(),
old_value, transaction.page_url);
}
}
other->CloseStorageArea(area);
}
SwitchToNewAliasMaster(other, context);
return SessionStorageNamespace::MERGE_RESULT_MERGEABLE;
}
bool DOMStorageNamespace::IsLoggingRenderer(int process_id) {
DCHECK(process_id != ChildProcessHost::kInvalidUniqueID);
if (transactions_.count(process_id) < 1)
return false;
return !transactions_[process_id]->max_log_size_exceeded;
}
void DOMStorageNamespace::AddTransaction(
int process_id, const TransactionRecord& transaction) {
if (!IsLoggingRenderer(process_id))
return;
TransactionData* transaction_data = transactions_[process_id];
DCHECK(transaction_data);
if (transaction_data->max_log_size_exceeded)
return;
transaction_data->log.push_back(transaction);
if (transaction_data->log.size() > kMaxTransactionLogEntries) {
transaction_data->max_log_size_exceeded = true;
transaction_data->log.clear();
}
}
bool DOMStorageNamespace::DecrementMasterAliasCount() {
if (!alias_master_namespace_ || master_alias_count_decremented_)
return false;
DCHECK_GT(alias_master_namespace_->num_aliases_, 0);
alias_master_namespace_->num_aliases_--;
master_alias_count_decremented_ = true;
return (alias_master_namespace_->num_aliases_ == 0);
}
void DOMStorageNamespace::SwitchToNewAliasMaster(
DOMStorageNamespace* new_master,
DOMStorageContextImpl* context) {
DCHECK(alias_master_namespace());
scoped_refptr<DOMStorageNamespace> old_master = alias_master_namespace();
if (new_master->alias_master_namespace())
new_master = new_master->alias_master_namespace();
DCHECK(!new_master->alias_master_namespace());
DCHECK(old_master != this);
DCHECK(old_master != new_master);
DecrementMasterAliasCount();
alias_master_namespace_ = new_master;
alias_master_namespace_->num_aliases_++;
master_alias_count_decremented_ = false;
// There are three things that we need to clean up:
// -- the old master may ready for shutdown, if its last alias has disappeared
// -- The dom_storage hosts need to close and reopen their areas, so that
// they point to the correct new areas.
// -- The renderers will need to reset their local caches.
// All three of these things are accomplished with the following call below.
// |context| will be NULL in unit tests, which is when this will
// not apply, of course.
// During this call, open areas will be closed & reopened, so that they now
// come from the correct new master. Therefore, we must send close operations
// to the old master.
old_master_for_close_area_ = old_master.get();
if (context)
context->NotifyAliasSessionMerged(namespace_id(), old_master.get());
old_master_for_close_area_ = NULL;
}
DOMStorageNamespace::TransactionData::TransactionData()
: max_log_size_exceeded(false) {
}
DOMStorageNamespace::TransactionData::~TransactionData() {
}
DOMStorageNamespace::TransactionRecord::TransactionRecord() {
}
DOMStorageNamespace::TransactionRecord::~TransactionRecord() {
}
// AreaHolder
DOMStorageNamespace::AreaHolder::AreaHolder()
: open_count_(0) {
}
DOMStorageNamespace::AreaHolder::AreaHolder(
DOMStorageArea* area, int count)
: area_(area), open_count_(count) {
}
DOMStorageNamespace::AreaHolder::~AreaHolder() {
}
} // namespace content