blob: 1c042880d33c5a1d852876fb4f9ed1b11e2bf270 [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/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/session_storage_usage_info.h"
#include "content/public/browser/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"
#include "third_party/blink/public/common/features.h"
#include "url/origin.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<StorageUsageInfo> 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)) {
base::FileEnumerator::FileInfo find_info = enumerator.GetInfo();
infos.emplace_back(DOMStorageArea::OriginFromDatabaseFileName(path),
find_info.GetSize(), find_info.GetLastModifiedTime());
}
}
reply_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(infos)));
}
void InvokeLocalStorageUsageCallbackHelper(
DOMStorageContext::GetLocalStorageUsageCallback callback,
std::unique_ptr<std::vector<StorageUsageInfo>> 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<StorageUsageInfo>* out_info,
base::OnceClosure done_callback,
const std::vector<StorageUsageInfo>& in_info) {
out_info->insert(out_info->end(), in_info.begin(), in_info.end());
std::move(done_callback).Run();
}
void GotMojoCallback(
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<StorageUsageInfo> 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
scoped_refptr<DOMStorageContextWrapper> DOMStorageContextWrapper::Create(
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});
scoped_refptr<DOMStorageContextImpl> old_context_impl =
base::MakeRefCounted<DOMStorageContextImpl>(
data_path.empty() ? data_path
: data_path.AppendASCII(kSessionStorageDirectory),
special_storage_policy,
new DOMStorageWorkerPoolTaskRunner(std::move(primary_sequence),
std::move(commit_sequence)));
auto mojo_task_runner =
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
base::FilePath legacy_localstorage_path =
data_path.empty() ? data_path
: data_path.AppendASCII(kLocalStorageDirectory);
base::FilePath new_localstorage_path =
profile_path.empty()
? base::FilePath()
: local_partition_path.AppendASCII(kLocalStorageDirectory);
LocalStorageContextMojo* mojo_local_state = new LocalStorageContextMojo(
mojo_task_runner, connector, old_context_impl->task_runner(),
legacy_localstorage_path, new_localstorage_path, special_storage_policy);
SessionStorageContextMojo* mojo_session_state = nullptr;
if (base::FeatureList::IsEnabled(blink::features::kOnionSoupDOMStorage)) {
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));
}
return base::WrapRefCounted(new DOMStorageContextWrapper(
std::move(legacy_localstorage_path), std::move(old_context_impl),
mojo_task_runner, mojo_local_state, mojo_session_state));
}
DOMStorageContextWrapper::DOMStorageContextWrapper(
base::FilePath legacy_local_storage_path,
scoped_refptr<DOMStorageContextImpl> context_impl,
scoped_refptr<base::SequencedTaskRunner> mojo_task_runner,
LocalStorageContextMojo* mojo_local_storage_context,
SessionStorageContextMojo* mojo_session_storage_context)
: mojo_state_(mojo_local_storage_context),
mojo_session_state_(mojo_session_storage_context),
mojo_task_runner_(std::move(mojo_task_runner)),
legacy_localstorage_path_(std::move(legacy_local_storage_path)),
context_(std::move(context_impl)) {
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<StorageUsageInfo>>();
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 url::Origin& 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(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_), origin,
base::BindOnce(&GotMojoCallback, base::ThreadTaskRunnerHandle::Get(),
std::move(callback))));
}
void DOMStorageContextWrapper::PerformLocalStorageCleanup(
base::OnceClosure callback) {
DCHECK(context_.get());
DCHECK(callback);
mojo_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&LocalStorageContextMojo::PerformStorageCleanup,
base::Unretained(mojo_state_),
base::BindOnce(&GotMojoCallback, base::ThreadTaskRunnerHandle::Get(),
std::move(callback))));
}
void DOMStorageContextWrapper::DeleteSessionStorage(
const SessionStorageUsageInfo& usage_info,
base::OnceClosure 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::DeleteStorage,
base::Unretained(mojo_session_state_),
url::Origin::Create(usage_info.origin),
usage_info.namespace_id, std::move(callback)));
return;
}
DCHECK(context_.get());
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::DeleteSessionStorage, context_,
usage_info));
std::move(callback).Run();
}
void DOMStorageContextWrapper::PerformSessionStorageCleanup(
base::OnceClosure callback) {
DCHECK(context_.get());
DCHECK(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::PerformStorageCleanup,
base::Unretained(mojo_session_state_),
std::move(callback)));
return;
}
std::move(callback).Run();
}
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() {
if (mojo_state_) {
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();
if (context_) {
context_->task_runner()->PostShutdownBlockingTask(
FROM_HERE, DOMStorageTaskRunner::PRIMARY_SEQUENCE,
base::BindOnce(&DOMStorageContextImpl::Shutdown, context_));
}
}
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;
// The bad message callback must be called on the same sequenced task runner
// as the binding set. It cannot be called from our own mojo task runner.
auto wrapped_bad_message_callback = base::BindOnce(
[](mojo::ReportBadMessageCallback bad_message_callback,
scoped_refptr<base::SequencedTaskRunner> bindings_runner,
const std::string& error) {
bindings_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(bad_message_callback), error));
},
std::move(bad_message_callback), base::SequencedTaskRunnerHandle::Get());
// 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(wrapped_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::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