blob: e83cb0c5c61c86683c90c81f6c3dfe97392a0c42 [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/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.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 "components/services/storage/dom_storage/local_storage_impl.h"
#include "components/services/storage/public/cpp/constants.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 "storage/browser/quota/special_storage_policy.h"
#include "third_party/blink/public/common/features.h"
#include "url/origin.h"
namespace content {
namespace {
const char kSessionStorageDirectory[] = "Session Storage";
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)));
}
void AdaptUsageInfo(
DOMStorageContext::GetLocalStorageUsageCallback callback,
std::vector<storage::mojom::LocalStorageUsageInfoPtr> usage) {
std::vector<StorageUsageInfo> result;
result.reserve(usage.size());
for (const auto& info : usage) {
result.emplace_back(info->origin, info->size_in_bytes,
info->last_modified_time);
}
std::move(callback).Run(result);
}
} // namespace
class DOMStorageContextWrapper::StoragePolicyObserver
: public storage::SpecialStoragePolicy::Observer {
public:
explicit StoragePolicyObserver(
scoped_refptr<storage::SpecialStoragePolicy> storage_policy,
scoped_refptr<DOMStorageContextWrapper> context_wrapper)
: storage_policy_(std::move(storage_policy)),
context_wrapper_(std::move(context_wrapper)) {
storage_policy_->AddObserver(this);
}
StoragePolicyObserver(const StoragePolicyObserver&) = delete;
StoragePolicyObserver& operator=(const StoragePolicyObserver&) = delete;
~StoragePolicyObserver() override {
DCHECK(!context_wrapper_);
storage_policy_->RemoveObserver(this);
}
void DidShutdownContextWrapper() { context_wrapper_.reset(); }
private:
// storage::SpecialStoragePolicy::Observer:
void OnPolicyChanged() override {
if (!context_wrapper_)
return;
base::PostTask(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&DOMStorageContextWrapper::OnStoragePolicyChanged,
context_wrapper_));
}
const scoped_refptr<storage::SpecialStoragePolicy> storage_policy_;
scoped_refptr<DOMStorageContextWrapper> context_wrapper_;
};
scoped_refptr<DOMStorageContextWrapper> DOMStorageContextWrapper::Create(
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);
auto mojo_task_runner =
base::CreateSingleThreadTaskRunner({BrowserThread::IO});
// TODO(https://crbug.com/1000959): This should be bound in an instance of
// the Storage Service. For now we bind it alone on the IO thread because
// that's where the implementation has effectively lived for some time.
mojo::Remote<storage::mojom::LocalStorageControl> local_storage_control;
mojo_task_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](const base::FilePath& storage_root,
mojo::PendingReceiver<storage::mojom::LocalStorageControl>
receiver) {
// Deletes itself on shutdown completion.
new storage::LocalStorageImpl(
storage_root,
base::CreateSingleThreadTaskRunner({BrowserThread::IO}),
base::CreateSequencedTaskRunner(
{base::ThreadPool(), base::MayBlock(),
base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
std::move(receiver));
},
data_path, local_storage_control.BindNewPipeAndPassReceiver()));
SessionStorageContextMojo* mojo_session_state = nullptr;
mojo_session_state = new SessionStorageContextMojo(
data_path,
base::CreateSequencedTaskRunner(
{base::MayBlock(), base::ThreadPool(),
base::TaskShutdownBehavior::BLOCK_SHUTDOWN}),
mojo_task_runner,
#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
std::string(kSessionStorageDirectory));
auto wrapper = base::WrapRefCounted(new DOMStorageContextWrapper(
mojo_task_runner, mojo_session_state, std::move(local_storage_control),
special_storage_policy));
if (special_storage_policy) {
// If there's a SpecialStoragePolicy, ensure the wrapper is observing it on
// the IO thread and query the initial set of in-use origins ASAP.
wrapper->storage_policy_observer_ =
base::SequenceBound<StoragePolicyObserver>(
base::CreateSequencedTaskRunner(BrowserThread::IO),
base::WrapRefCounted(special_storage_policy), wrapper);
wrapper->local_storage_control_->GetUsage(base::BindOnce(
&DOMStorageContextWrapper::OnStartupUsageRetrieved, wrapper));
}
return wrapper;
}
DOMStorageContextWrapper::DOMStorageContextWrapper(
scoped_refptr<base::SequencedTaskRunner> mojo_task_runner,
SessionStorageContextMojo* mojo_session_storage_context,
mojo::Remote<storage::mojom::LocalStorageControl> local_storage_control,
storage::SpecialStoragePolicy* special_storage_policy)
: mojo_session_state_(mojo_session_storage_context),
mojo_task_runner_(std::move(mojo_task_runner)),
local_storage_control_(std::move(local_storage_control)),
storage_policy_(special_storage_policy) {
memory_pressure_listener_.reset(new base::MemoryPressureListener(
base::BindRepeating(&DOMStorageContextWrapper::OnMemoryPressure,
base::Unretained(this))));
}
DOMStorageContextWrapper::~DOMStorageContextWrapper() {
DCHECK(!local_storage_control_)
<< "Shutdown should be called before destruction";
}
storage::mojom::LocalStorageControl*
DOMStorageContextWrapper::GetLocalStorageControl() {
DCHECK(local_storage_control_);
return local_storage_control_.get();
}
void DOMStorageContextWrapper::GetLocalStorageUsage(
GetLocalStorageUsageCallback callback) {
if (!local_storage_control_) {
// Shutdown() has been called.
std::move(callback).Run(std::vector<StorageUsageInfo>());
return;
}
local_storage_control_->GetUsage(
base::BindOnce(&AdaptUsageInfo, std::move(callback)));
}
void DOMStorageContextWrapper::GetSessionStorageUsage(
GetSessionStorageUsageCallback callback) {
if (!mojo_session_state_) {
// Shutdown() has been called.
std::move(callback).Run(std::vector<SessionStorageUsageInfo>());
return;
}
// 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))));
}
void DOMStorageContextWrapper::DeleteLocalStorage(const url::Origin& origin,
base::OnceClosure callback) {
DCHECK(callback);
if (!local_storage_control_) {
// Shutdown() has been called.
std::move(callback).Run();
return;
}
local_storage_control_->DeleteStorage(origin, std::move(callback));
}
void DOMStorageContextWrapper::PerformLocalStorageCleanup(
base::OnceClosure callback) {
DCHECK(callback);
if (!local_storage_control_) {
// Shutdown() has been called.
std::move(callback).Run();
return;
}
local_storage_control_->CleanUpStorage(std::move(callback));
}
void DOMStorageContextWrapper::DeleteSessionStorage(
const SessionStorageUsageInfo& usage_info,
base::OnceClosure callback) {
if (!mojo_session_state_) {
// Shutdown() has been called.
std::move(callback).Run();
}
// 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)));
}
void DOMStorageContextWrapper::PerformSessionStorageCleanup(
base::OnceClosure callback) {
DCHECK(callback);
if (!mojo_session_state_) {
// Shutdown() has been called.
std::move(callback).Run();
}
// 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)));
}
scoped_refptr<SessionStorageNamespace>
DOMStorageContextWrapper::RecreateSessionStorage(
const std::string& namespace_id) {
return SessionStorageNamespaceImpl::Create(this, namespace_id);
}
void DOMStorageContextWrapper::StartScavengingUnusedSessionStorage() {
if (!mojo_session_state_) {
// Shutdown() has been called.
return;
}
// 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()));
}
void DOMStorageContextWrapper::SetForceKeepSessionState() {
if (!local_storage_control_) {
// Shutdown() has been called.
return;
}
local_storage_control_->ForceKeepSessionState();
}
void DOMStorageContextWrapper::Shutdown() {
// Signals the implementation to perform shutdown operations.
local_storage_control_.reset();
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 (storage_policy_observer_) {
// Make sure the observer drops its reference to |this|.
storage_policy_observer_.Post(
FROM_HERE, &StoragePolicyObserver::DidShutdownContextWrapper);
}
}
void DOMStorageContextWrapper::Flush() {
if (local_storage_control_)
local_storage_control_->Flush(base::NullCallback());
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,
mojo::PendingReceiver<blink::mojom::StorageArea> receiver) {
DCHECK(local_storage_control_);
local_storage_control_->BindStorageArea(origin, std::move(receiver));
if (storage_policy_) {
EnsureLocalStorageOriginIsTracked(origin.GetURL());
OnStoragePolicyChanged();
}
}
void DOMStorageContextWrapper::OpenSessionStorage(
int process_id,
const std::string& namespace_id,
mojo::ReportBadMessageCallback bad_message_callback,
mojo::PendingReceiver<blink::mojom::SessionStorageNamespace> receiver) {
DCHECK(mojo_session_state_);
// 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(receiver)));
}
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) {
PurgeOption purge_option = PURGE_UNOPENED;
if (memory_pressure_level ==
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
purge_option = PURGE_AGGRESSIVE;
}
PurgeMemory(purge_option);
}
void DOMStorageContextWrapper::PurgeMemory(PurgeOption purge_option) {
if (!local_storage_control_) {
// Shutdown was called.
return;
}
if (purge_option == PURGE_AGGRESSIVE) {
local_storage_control_->PurgeMemory();
// 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_)));
}
}
void DOMStorageContextWrapper::OnStartupUsageRetrieved(
std::vector<storage::mojom::LocalStorageUsageInfoPtr> usage) {
for (const auto& info : usage)
EnsureLocalStorageOriginIsTracked(info->origin.GetURL());
OnStoragePolicyChanged();
}
void DOMStorageContextWrapper::EnsureLocalStorageOriginIsTracked(
const GURL& origin) {
DCHECK(storage_policy_);
auto it = local_storage_origins_.find(origin);
if (it == local_storage_origins_.end())
local_storage_origins_[origin] = {};
}
void DOMStorageContextWrapper::OnStoragePolicyChanged() {
if (!local_storage_control_)
return;
// Scan for any relevant changes to policy regarding origins we know we're
// managing.
std::vector<storage::mojom::LocalStoragePolicyUpdatePtr> policy_updates;
for (auto& entry : local_storage_origins_) {
const GURL& origin = entry.first;
LocalStorageOriginState& state = entry.second;
state.should_purge_on_shutdown = ShouldPurgeLocalStorageOnShutdown(origin);
if (state.should_purge_on_shutdown != state.will_purge_on_shutdown) {
state.will_purge_on_shutdown = state.should_purge_on_shutdown;
policy_updates.push_back(storage::mojom::LocalStoragePolicyUpdate::New(
url::Origin::Create(origin), state.should_purge_on_shutdown));
}
}
if (!policy_updates.empty())
local_storage_control_->ApplyPolicyUpdates(std::move(policy_updates));
}
bool DOMStorageContextWrapper::ShouldPurgeLocalStorageOnShutdown(
const GURL& origin) {
if (!storage_policy_)
return false;
return storage_policy_->IsStorageSessionOnly(origin) &&
!storage_policy_->IsStorageProtected(origin);
}
} // namespace content