blob: ae4d0f92cbb1aebd5ee9af6e0153feeb99fa4688 [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_context_wrapper.h"
#include <string>
#include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/memory/memory_coordinator_client_registry.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.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/local_storage_context_mojo.h"
#include "content/browser/dom_storage/session_storage_context_mojo.h"
#include "content/browser/dom_storage/session_storage_namespace_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/local_storage_usage_info.h"
#include "content/public/browser/session_storage_usage_info.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "sql/database.h"
namespace content {
namespace {
const char kLocalStorageDirectory[] = "Local Storage";
const char kSessionStorageDirectory[] = "Session Storage";
void GetLegacyLocalStorageUsage(
const base::FilePath& directory,
scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner,
DOMStorageContext::GetLocalStorageUsageCallback callback) {
std::vector<LocalStorageUsageInfo> infos;
base::FileEnumerator enumerator(directory, false,
base::FileEnumerator::FILES);
for (base::FilePath path = enumerator.Next(); !path.empty();
path = enumerator.Next()) {
if (path.MatchesExtension(DOMStorageArea::kDatabaseFileExtension)) {
LocalStorageUsageInfo info;
info.origin = DOMStorageArea::OriginFromDatabaseFileName(path).GetURL();
base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
info.data_size = find_info.GetSize();
info.last_modified = find_info.GetLastModifiedTime();
infos.push_back(info);
}
}
reply_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(infos)));
}
void InvokeLocalStorageUsageCallbackHelper(
DOMStorageContext::GetLocalStorageUsageCallback callback,
std::unique_ptr<std::vector<LocalStorageUsageInfo>> infos) {
std::move(callback).Run(*infos);
}
void GetSessionStorageUsageHelper(
base::SingleThreadTaskRunner* reply_task_runner,
DOMStorageContextImpl* context,
DOMStorageContext::GetSessionStorageUsageCallback callback) {
std::vector<SessionStorageUsageInfo> infos;
context->GetSessionStorageUsage(&infos);
reply_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(infos)));
}
void CollectLocalStorageUsage(
std::vector<LocalStorageUsageInfo>* out_info,
base::OnceClosure done_callback,
const std::vector<LocalStorageUsageInfo>& in_info) {
out_info->insert(out_info->end(), in_info.begin(), in_info.end());
std::move(done_callback).Run();
}
void GotMojoDeletionCallback(
scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner,
base::OnceClosure callback) {
reply_task_runner->PostTask(FROM_HERE, std::move(callback));
}
void GotMojoLocalStorageUsage(
scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner,
DOMStorageContext::GetLocalStorageUsageCallback callback,
std::vector<LocalStorageUsageInfo> usage) {
reply_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(usage)));
}
void GotMojoSessionStorageUsage(
scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner,
DOMStorageContext::GetSessionStorageUsageCallback callback,
std::vector<SessionStorageUsageInfo> usage) {
reply_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(usage)));
}
} // namespace
DOMStorageContextWrapper::DOMStorageContextWrapper(
service_manager::Connector* connector,
const base::FilePath& profile_path,
const base::FilePath& local_partition_path,
storage::SpecialStoragePolicy* special_storage_policy) {
base::FilePath data_path;
if (!profile_path.empty())
data_path = profile_path.Append(local_partition_path);
scoped_refptr<base::SequencedTaskRunner> primary_sequence =
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
scoped_refptr<base::SequencedTaskRunner> commit_sequence =
base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
legacy_localstorage_path_ =
data_path.empty() ? data_path
: data_path.AppendASCII(kLocalStorageDirectory);
context_ = new DOMStorageContextImpl(
data_path.empty() ? data_path
: data_path.AppendASCII(kSessionStorageDirectory),
special_storage_policy,
new DOMStorageWorkerPoolTaskRunner(std::move(primary_sequence),
std::move(commit_sequence)));
base::FilePath storage_dir;
if (!profile_path.empty())
storage_dir = local_partition_path.AppendASCII(kLocalStorageDirectory);
// TODO(dmurph): Change this to a sequenced task runner after
// https://crbug.com/809255 is fixed.
mojo_task_runner_ =
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
mojo_state_ = new LocalStorageContextMojo(
mojo_task_runner_, connector, context_->task_runner(),
legacy_localstorage_path_, storage_dir, special_storage_policy);
if (base::FeatureList::IsEnabled(features::kMojoSessionStorage)) {
mojo_session_state_ = new SessionStorageContextMojo(
mojo_task_runner_, connector,
#if defined(OS_ANDROID)
// On Android there is no support for session storage restoring, and
// since the restoring code is responsible for database cleanup, we must
// manually delete the old database here before we open it.
SessionStorageContextMojo::BackingMode::kClearDiskStateOnOpen,
#else
profile_path.empty()
? SessionStorageContextMojo::BackingMode::kNoDisk
: SessionStorageContextMojo::BackingMode::kRestoreDiskState,
#endif
local_partition_path, std::string(kSessionStorageDirectory));
}
if (base::FeatureList::IsEnabled(features::kMemoryCoordinator)) {
base::MemoryCoordinatorClientRegistry::GetInstance()->Register(this);
} else {
memory_pressure_listener_.reset(new base::MemoryPressureListener(
base::BindRepeating(&DOMStorageContextWrapper::OnMemoryPressure,
base::Unretained(this))));
}
}
DOMStorageContextWrapper::~DOMStorageContextWrapper() {
DCHECK(!mojo_state_) << "Shutdown should be called before destruction";
DCHECK(!mojo_session_state_)
<< "Shutdown should be called before destruction";
}
void DOMStorageContextWrapper::GetLocalStorageUsage(
GetLocalStorageUsageCallback callback) {
DCHECK(context_.get());
auto infos = std::make_unique<std::vector<LocalStorageUsageInfo>>();
auto* infos_ptr = infos.get();
base::RepeatingClosure got_local_storage_usage = base::BarrierClosure(
2, base::BindOnce(&InvokeLocalStorageUsageCallbackHelper,
std::move(callback), std::move(infos)));
auto collect_callback = base::BindRepeating(
CollectLocalStorageUsage, infos_ptr, std::move(got_local_storage_usage));
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalStorageContextMojo::GetStorageUsage,
base::Unretained(mojo_state_),
base::BindOnce(&GotMojoLocalStorageUsage,
base::ThreadTaskRunnerHandle::Get(),
collect_callback)));
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&GetLegacyLocalStorageUsage, legacy_localstorage_path_,
base::ThreadTaskRunnerHandle::Get(),
std::move(collect_callback)));
}
void DOMStorageContextWrapper::GetSessionStorageUsage(
GetSessionStorageUsageCallback callback) {
if (mojo_session_state_) {
// base::Unretained is safe here, because the mojo_session_state_ won't be
// deleted until a ShutdownAndDelete task has been ran on the
// mojo_task_runner_, and as soon as that task is posted,
// mojo_session_state_ is set to null, preventing further tasks from being
// queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SessionStorageContextMojo::GetStorageUsage,
base::Unretained(mojo_session_state_),
base::BindOnce(&GotMojoSessionStorageUsage,
base::ThreadTaskRunnerHandle::Get(),
std::move(callback))));
return;
}
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&GetSessionStorageUsageHelper,
base::RetainedRef(base::ThreadTaskRunnerHandle::Get()),
base::RetainedRef(context_), std::move(callback)));
}
void DOMStorageContextWrapper::DeleteLocalStorage(const GURL& origin,
base::OnceClosure callback) {
DCHECK(context_.get());
DCHECK(callback);
if (!legacy_localstorage_path_.empty()) {
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(base::IgnoreResult(&sql::Database::Delete),
legacy_localstorage_path_.Append(
DOMStorageArea::DatabaseFileNameFromOrigin(
url::Origin::Create(origin)))));
}
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalStorageContextMojo::DeleteStorage,
base::Unretained(mojo_state_), url::Origin::Create(origin),
base::BindOnce(&GotMojoDeletionCallback,
base::ThreadTaskRunnerHandle::Get(),
std::move(callback))));
}
void DOMStorageContextWrapper::DeleteSessionStorage(
const SessionStorageUsageInfo& usage_info) {
if (mojo_session_state_) {
// base::Unretained is safe here, because the mojo_session_state_ won't be
// deleted until a ShutdownAndDelete task has been ran on the
// mojo_task_runner_, and as soon as that task is posted,
// mojo_session_state_ is set to null, preventing further tasks from being
// queued.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SessionStorageContextMojo::DeleteStorage,
base::Unretained(mojo_session_state_),
url::Origin::Create(usage_info.origin),
usage_info.namespace_id));
return;
}
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::DeleteSessionStorage, context_,
usage_info));
}
void DOMStorageContextWrapper::SetSaveSessionStorageOnDisk() {
DCHECK(context_.get());
context_->SetSaveSessionStorageOnDisk();
}
scoped_refptr<SessionStorageNamespace>
DOMStorageContextWrapper::RecreateSessionStorage(
const std::string& namespace_id) {
return SessionStorageNamespaceImpl::Create(this, namespace_id);
}
void DOMStorageContextWrapper::StartScavengingUnusedSessionStorage() {
if (mojo_session_state_) {
// base::Unretained is safe here, because the mojo_session_state_ won't be
// deleted until a ShutdownAndDelete task has been ran on the
// mojo_task_runner_, and as soon as that task is posted,
// mojo_session_state_ is set to null, preventing further tasks from being
// queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SessionStorageContextMojo::ScavengeUnusedNamespaces,
base::Unretained(mojo_session_state_),
base::OnceClosure()));
return;
}
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(
&DOMStorageContextImpl::StartScavengingUnusedSessionStorage,
context_));
}
void DOMStorageContextWrapper::SetForceKeepSessionState() {
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::SetForceKeepSessionState,
context_));
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalStorageContextMojo::SetForceKeepSessionState,
base::Unretained(mojo_state_)));
}
void DOMStorageContextWrapper::Shutdown() {
DCHECK(context_.get());
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&LocalStorageContextMojo::ShutdownAndDelete,
base::Unretained(mojo_state_)));
mojo_state_ = nullptr;
if (mojo_session_state_) {
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SessionStorageContextMojo::ShutdownAndDelete,
base::Unretained(mojo_session_state_)));
mojo_session_state_ = nullptr;
}
memory_pressure_listener_.reset();
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::Shutdown, context_));
if (base::FeatureList::IsEnabled(features::kMemoryCoordinator)) {
base::MemoryCoordinatorClientRegistry::GetInstance()->Unregister(this);
}
}
void DOMStorageContextWrapper::Flush() {
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::Flush, context_));
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&LocalStorageContextMojo::Flush,
base::Unretained(mojo_state_)));
if (mojo_session_state_) {
// base::Unretained is safe here, because the mojo_session_state_ won't be
// deleted until a ShutdownAndDelete task has been ran on the
// mojo_task_runner_, and as soon as that task is posted,
// mojo_session_state_ is set to null, preventing further tasks from being
// queued.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SessionStorageContextMojo::Flush,
base::Unretained(mojo_session_state_)));
}
}
void DOMStorageContextWrapper::OpenLocalStorage(
const url::Origin& origin,
blink::mojom::StorageAreaRequest request) {
DCHECK(mojo_state_);
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&LocalStorageContextMojo::OpenLocalStorage,
base::Unretained(mojo_state_), origin,
std::move(request)));
}
void DOMStorageContextWrapper::OpenSessionStorage(
int process_id,
const std::string& namespace_id,
mojo::ReportBadMessageCallback bad_message_callback,
blink::mojom::SessionStorageNamespaceRequest request) {
if (!mojo_session_state_)
return;
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
base::Unretained(mojo_session_state_), process_id,
namespace_id, std::move(bad_message_callback),
std::move(request)));
}
void DOMStorageContextWrapper::SetLocalStorageDatabaseForTesting(
leveldb::mojom::LevelDBDatabaseAssociatedPtr database) {
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LocalStorageContextMojo::SetDatabaseForTesting,
base::Unretained(mojo_state_), std::move(database)));
}
scoped_refptr<SessionStorageNamespaceImpl>
DOMStorageContextWrapper::MaybeGetExistingNamespace(
const std::string& namespace_id) const {
base::AutoLock lock(alive_namespaces_lock_);
auto it = alive_namespaces_.find(namespace_id);
return (it != alive_namespaces_.end()) ? it->second : nullptr;
}
void DOMStorageContextWrapper::AddNamespace(
const std::string& namespace_id,
SessionStorageNamespaceImpl* session_namespace) {
base::AutoLock lock(alive_namespaces_lock_);
DCHECK(alive_namespaces_.find(namespace_id) == alive_namespaces_.end());
alive_namespaces_[namespace_id] = session_namespace;
}
void DOMStorageContextWrapper::RemoveNamespace(
const std::string& namespace_id) {
base::AutoLock lock(alive_namespaces_lock_);
DCHECK(alive_namespaces_.find(namespace_id) != alive_namespaces_.end());
alive_namespaces_.erase(namespace_id);
}
void DOMStorageContextWrapper::OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
DOMStorageContextImpl::PurgeOption purge_option =
DOMStorageContextImpl::PURGE_UNOPENED;
if (memory_pressure_level ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
purge_option = DOMStorageContextImpl::PURGE_AGGRESSIVE;
}
PurgeMemory(purge_option);
}
void DOMStorageContextWrapper::OnPurgeMemory() {
PurgeMemory(DOMStorageContextImpl::PURGE_AGGRESSIVE);
}
void DOMStorageContextWrapper::PurgeMemory(DOMStorageContextImpl::PurgeOption
purge_option) {
context_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&DOMStorageContextImpl::PurgeMemory, context_,
purge_option));
if (mojo_state_ && purge_option == DOMStorageContextImpl::PURGE_AGGRESSIVE) {
// base::Unretained is safe here, because the mojo_state_ won't be deleted
// until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
// as soon as that task is posted, mojo_state_ is set to null, preventing
// further tasks from being queued.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&LocalStorageContextMojo::PurgeMemory,
base::Unretained(mojo_state_)));
}
if (mojo_session_state_ &&
purge_option == DOMStorageContextImpl::PURGE_AGGRESSIVE) {
// base::Unretained is safe here, because the mojo_session_state_ won't be
// deleted until a ShutdownAndDelete task has been ran on the
// mojo_task_runner_, and as soon as that task is posted,
// mojo_session_state_ is set to null, preventing further tasks from being
// queued.
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&SessionStorageContextMojo::PurgeMemory,
base::Unretained(mojo_session_state_)));
}
}
} // namespace content