blob: 38e2710e6e9a79b6087d5e00e14c97f9cac88e0f [file] [log] [blame]
// Copyright 2016 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 "chrome/browser/ui/webui/settings/settings_cookies_view_handler.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/i18n/number_formatting.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browsing_data/third_party_data_remover.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/cookies_tree_model_util.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_ui.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/text/bytes_formatting.h"
namespace storage {
class FileSystemContext;
}
namespace {
int GetCategoryLabelID(CookieTreeNode::DetailedInfo::NodeType node_type) {
constexpr struct {
CookieTreeNode::DetailedInfo::NodeType node_type;
int id;
} kCategoryLabels[] = {
// Multiple keys (node_type) may have the same value (id).
{CookieTreeNode::DetailedInfo::TYPE_DATABASES,
IDS_SETTINGS_COOKIES_DATABASE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_DATABASE,
IDS_SETTINGS_COOKIES_DATABASE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGES,
IDS_SETTINGS_COOKIES_LOCAL_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGE,
IDS_SETTINGS_COOKIES_LOCAL_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_INDEXED_DBS,
IDS_SETTINGS_COOKIES_DATABASE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_INDEXED_DB,
IDS_SETTINGS_COOKIES_DATABASE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_FILE_SYSTEMS,
IDS_SETTINGS_COOKIES_FILE_SYSTEM},
{CookieTreeNode::DetailedInfo::TYPE_FILE_SYSTEM,
IDS_SETTINGS_COOKIES_FILE_SYSTEM},
{CookieTreeNode::DetailedInfo::TYPE_SERVICE_WORKERS,
IDS_SETTINGS_COOKIES_SERVICE_WORKER},
{CookieTreeNode::DetailedInfo::TYPE_SERVICE_WORKER,
IDS_SETTINGS_COOKIES_SERVICE_WORKER},
{CookieTreeNode::DetailedInfo::TYPE_SHARED_WORKERS,
IDS_SETTINGS_COOKIES_SHARED_WORKER},
{CookieTreeNode::DetailedInfo::TYPE_SHARED_WORKER,
IDS_SETTINGS_COOKIES_SHARED_WORKER},
{CookieTreeNode::DetailedInfo::TYPE_CACHE_STORAGES,
IDS_SETTINGS_COOKIES_CACHE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_CACHE_STORAGE,
IDS_SETTINGS_COOKIES_CACHE_STORAGE},
{CookieTreeNode::DetailedInfo::TYPE_QUOTA,
IDS_SETTINGS_COOKIES_QUOTA_STORAGE},
};
// Before optimizing, consider the data size and the cost of L2 cache misses.
// A linear search over a couple dozen integers is very fast.
for (size_t i = 0; i < std::size(kCategoryLabels); ++i) {
if (kCategoryLabels[i].node_type == node_type) {
return kCategoryLabels[i].id;
}
}
NOTREACHED();
return 0;
}
} // namespace
namespace settings {
constexpr char kLocalData[] = "localData";
constexpr char kSite[] = "site";
CookiesViewHandler::Request::Request(TreeModelBatchBehavior batch_behavior,
base::OnceClosure initial_task)
: batch_behavior(batch_behavior), initial_task(std::move(initial_task)) {
if (batch_behavior == Request::ASYNC_BATCH)
batch_end_task = base::DoNothing();
}
CookiesViewHandler::Request::Request(base::OnceClosure initial_task,
base::OnceClosure batch_end_task)
: batch_behavior(Request::TreeModelBatchBehavior::ASYNC_BATCH),
initial_task(std::move(initial_task)),
batch_end_task(std::move(batch_end_task)) {}
CookiesViewHandler::Request::~Request() = default;
CookiesViewHandler::Request::Request(Request&& other) {
initial_task = std::move(other.initial_task);
batch_end_task = std::move(other.batch_end_task);
}
CookiesViewHandler::CookiesViewHandler()
: batch_update_(false), model_util_(new CookiesTreeModelUtil) {}
CookiesViewHandler::~CookiesViewHandler() {
}
void CookiesViewHandler::OnJavascriptAllowed() {
// Some requests assume that a tree model has already been created, creating
// here ensures this is true.
pending_requests_.emplace(
Request::ASYNC_BATCH,
base::BindOnce(&CookiesViewHandler::RecreateCookiesTreeModel,
callback_weak_ptr_factory_.GetWeakPtr()));
ProcessPendingRequests();
}
void CookiesViewHandler::OnJavascriptDisallowed() {
callback_weak_ptr_factory_.InvalidateWeakPtrs();
pending_requests_ = std::queue<Request>();
}
void CookiesViewHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"localData.getDisplayList",
base::BindRepeating(&CookiesViewHandler::HandleGetDisplayList,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.removeAll",
base::BindRepeating(&CookiesViewHandler::HandleRemoveAll,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.removeShownItems",
base::BindRepeating(&CookiesViewHandler::HandleRemoveShownItems,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.removeItem",
base::BindRepeating(&CookiesViewHandler::HandleRemoveItem,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.getCookieDetails",
base::BindRepeating(&CookiesViewHandler::HandleGetCookieDetails,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.getNumCookiesString",
base::BindRepeating(&CookiesViewHandler::HandleGetNumCookiesString,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.removeSite",
base::BindRepeating(&CookiesViewHandler::HandleRemoveSite,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.removeThirdPartyCookies",
base::BindRepeating(&CookiesViewHandler::HandleRemoveThirdParty,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"localData.reload",
base::BindRepeating(&CookiesViewHandler::HandleReloadCookies,
base::Unretained(this)));
}
void CookiesViewHandler::TreeNodesAdded(ui::TreeModel* model,
ui::TreeModelNode* parent,
size_t start,
size_t count) {}
void CookiesViewHandler::TreeNodesRemoved(ui::TreeModel* model,
ui::TreeModelNode* parent,
size_t start,
size_t count) {
// Skip if there is a batch update in progress.
if (batch_update_)
return;
FireWebUIListener("on-tree-item-removed");
}
void CookiesViewHandler::TreeModelBeginBatch(CookiesTreeModel* model) {
DCHECK(!batch_update_); // There should be no nested batch begin.
DCHECK(!pending_requests_.empty());
batch_update_ = true;
DCHECK_NE(Request::NO_BATCH, pending_requests_.front().batch_behavior);
}
void CookiesViewHandler::TreeModelEndBatch(CookiesTreeModel* model) {
DCHECK(batch_update_);
DCHECK(!pending_requests_.empty());
batch_update_ = false;
DCHECK_NE(Request::NO_BATCH, pending_requests_.front().batch_behavior);
if (pending_requests_.front().batch_behavior == Request::ASYNC_BATCH) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(pending_requests_.front().batch_end_task));
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&CookiesViewHandler::RequestComplete,
callback_weak_ptr_factory_.GetWeakPtr()));
}
}
void CookiesViewHandler::SetCookiesTreeModelForTesting(
std::unique_ptr<CookiesTreeModel> cookies_tree_model) {
cookies_tree_model_for_testing_ = std::move(cookies_tree_model);
}
void CookiesViewHandler::RecreateCookiesTreeModel() {
cookies_tree_model_.reset();
filter_.clear();
cookies_tree_model_ = cookies_tree_model_for_testing_.get()
? std::move(cookies_tree_model_for_testing_)
: CookiesTreeModel::CreateForProfileDeprecated(
Profile::FromWebUI(web_ui()));
cookies_tree_model_->AddCookiesTreeObserver(this);
}
void CookiesViewHandler::HandleGetCookieDetails(const base::Value::List& args) {
CHECK_EQ(2U, args.size());
std::string callback_id = args[0].GetString();
std::string site = args[1].GetString();
AllowJavascript();
pending_requests_.emplace(
Request::NO_BATCH, base::BindOnce(&CookiesViewHandler::GetCookieDetails,
callback_weak_ptr_factory_.GetWeakPtr(),
callback_id, site));
ProcessPendingRequests();
}
void CookiesViewHandler::GetCookieDetails(const std::string& callback_id,
const std::string& site) {
const CookieTreeNode* node = model_util_->GetTreeNodeFromTitle(
cookies_tree_model_->GetRoot(), base::UTF8ToUTF16(site));
if (!node) {
RejectJavascriptCallback(base::Value(callback_id), base::Value());
return;
}
base::Value::List children = model_util_->GetChildNodeDetailsDeprecated(node);
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(std::move(children)));
}
void CookiesViewHandler::HandleGetNumCookiesString(
const base::Value::List& args) {
CHECK_EQ(2U, args.size());
std::string callback_id;
callback_id = args[0].GetString();
int num_cookies = args[1].GetInt();
AllowJavascript();
const std::u16string string =
num_cookies > 0 ? l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SITE_SETTINGS_NUM_COOKIES, num_cookies)
: std::u16string();
ResolveJavascriptCallback(base::Value(callback_id), base::Value(string));
}
void CookiesViewHandler::HandleGetDisplayList(const base::Value::List& args) {
CHECK_EQ(2U, args.size());
std::string callback_id = args[0].GetString();
std::u16string filter = base::UTF8ToUTF16(args[1].GetString());
AllowJavascript();
pending_requests_.emplace(
Request::SYNC_BATCH,
base::BindOnce(&CookiesViewHandler::GetDisplayList,
callback_weak_ptr_factory_.GetWeakPtr(), callback_id,
filter));
ProcessPendingRequests();
}
void CookiesViewHandler::GetDisplayList(std::string callback_id,
const std::u16string& filter) {
if (filter != filter_) {
filter_ = filter;
cookies_tree_model_->UpdateSearchResults(filter_);
DCHECK(!batch_update_) << "Expected CookiesTreeModel::UpdateSearchResults "
<< "to execute synchronously.";
}
ReturnLocalDataList(callback_id);
}
void CookiesViewHandler::HandleReloadCookies(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
// Allowing Javascript for the first time will queue a task to create a new
// tree model. Thus the tree model only needs to be recreated if Javascript
// has already been allowed. Reload cookies is often the first call made by
// pages using this handler, so this avoids unnecessary work.
if (IsJavascriptAllowed()) {
pending_requests_.emplace(
base::BindOnce(&CookiesViewHandler::RecreateCookiesTreeModel,
callback_weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&CookiesViewHandler::ResolveJavascriptCallback,
callback_weak_ptr_factory_.GetWeakPtr(),
base::Value(callback_id), base::Value()));
} else {
AllowJavascript();
pending_requests_.emplace(
Request::NO_BATCH,
base::BindOnce(&CookiesViewHandler::ResolveJavascriptCallback,
callback_weak_ptr_factory_.GetWeakPtr(),
base::Value(callback_id), base::Value()));
}
ProcessPendingRequests();
}
void CookiesViewHandler::HandleRemoveAll(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
AllowJavascript();
std::string callback_id = args[0].GetString();
pending_requests_.emplace(
Request::SYNC_BATCH,
base::BindOnce(&CookiesViewHandler::RemoveAll,
callback_weak_ptr_factory_.GetWeakPtr(), callback_id));
ProcessPendingRequests();
}
void CookiesViewHandler::RemoveAll(const std::string& callback_id) {
cookies_tree_model_->DeleteAllStoredObjects();
ResolveJavascriptCallback(base::Value(callback_id), base::Value());
}
void CookiesViewHandler::HandleRemoveItem(const base::Value::List& args) {
std::string node_path = args[0].GetString();
AllowJavascript();
pending_requests_.emplace(
Request::NO_BATCH,
base::BindOnce(&CookiesViewHandler::RemoveItem,
callback_weak_ptr_factory_.GetWeakPtr(), node_path));
ProcessPendingRequests();
}
void CookiesViewHandler::RemoveItem(const std::string& path) {
const CookieTreeNode* node =
model_util_->GetTreeNodeFromPath(cookies_tree_model_->GetRoot(), path);
if (node) {
cookies_tree_model_->DeleteCookieNode(const_cast<CookieTreeNode*>(node));
}
}
void CookiesViewHandler::HandleRemoveThirdParty(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
std::string callback_id = args[0].GetString();
AllowJavascript();
Profile* profile = Profile::FromWebUI(web_ui());
pending_requests_.emplace(
base::BindOnce(
ClearThirdPartyData,
base::BindOnce(&CookiesViewHandler::RecreateCookiesTreeModel,
callback_weak_ptr_factory_.GetWeakPtr()),
profile),
base::BindOnce(&CookiesViewHandler::ResolveJavascriptCallback,
callback_weak_ptr_factory_.GetWeakPtr(),
base::Value(callback_id), base::Value()));
ProcessPendingRequests();
}
void CookiesViewHandler::HandleRemoveShownItems(const base::Value::List& args) {
CHECK_EQ(0U, args.size());
AllowJavascript();
pending_requests_.emplace(
Request::NO_BATCH,
base::BindOnce(&CookiesViewHandler::RemoveShownItems,
callback_weak_ptr_factory_.GetWeakPtr()));
ProcessPendingRequests();
}
void CookiesViewHandler::RemoveShownItems() {
CookieTreeNode* parent = cookies_tree_model_->GetRoot();
while (!parent->children().empty())
cookies_tree_model_->DeleteCookieNode(parent->children().front().get());
}
void CookiesViewHandler::HandleRemoveSite(const base::Value::List& args) {
CHECK_EQ(1U, args.size());
std::u16string site = base::UTF8ToUTF16(args[0].GetString());
AllowJavascript();
pending_requests_.emplace(
Request::NO_BATCH,
base::BindOnce(&CookiesViewHandler::RemoveSite,
callback_weak_ptr_factory_.GetWeakPtr(), site));
ProcessPendingRequests();
}
void CookiesViewHandler::RemoveSite(const std::u16string& site) {
CookieTreeNode* parent = cookies_tree_model_->GetRoot();
const auto i = std::find_if(
parent->children().cbegin(), parent->children().cend(),
[&site](const auto& node) { return node->GetTitle() == site; });
if (i != parent->children().cend()) {
cookies_tree_model_->DeleteCookieNode(i->get());
}
}
void CookiesViewHandler::ReturnLocalDataList(const std::string& callback_id) {
CHECK(cookies_tree_model_.get());
auto* parent = cookies_tree_model_->GetRoot();
// The layers in the CookieTree are:
// root - Top level.
// site - www.google.com, example.com, etc.
// category - Cookies, Local Storage, etc.
// item - Info on the actual thing.
// Gather list of sites with some highlights of the categories and items.
base::Value::List site_list;
for (const auto& site : parent->children()) {
std::u16string description;
for (const auto& category : site->children()) {
const auto node_type = category->GetDetailedInfo().node_type;
if (!description.empty())
description += u", ";
size_t item_count = category->children().size();
switch (node_type) {
case CookieTreeNode::DetailedInfo::TYPE_COOKIE:
DCHECK_EQ(0u, item_count);
item_count = 1;
[[fallthrough]];
case CookieTreeNode::DetailedInfo::TYPE_COOKIES:
description += l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SITE_SETTINGS_NUM_COOKIES,
static_cast<int>(item_count));
break;
default:
int ids_value = GetCategoryLabelID(node_type);
if (!ids_value) {
// If we don't have a label to call it by, don't show it. Please add
// a label ID if an expected category is not appearing in the UI.
continue;
}
description += l10n_util::GetStringUTF16(ids_value);
break;
}
}
base::Value::Dict list_info;
list_info.Set(kLocalData, description);
list_info.Set(kSite, site->GetTitle());
site_list.Append(std::move(list_info));
}
// Sort the list into alphabetical order based on site name.
std::sort(site_list.begin(), site_list.end(),
[=](const base::Value& a, const base::Value& b) {
return *a.FindStringKey(kSite) < *b.FindStringKey(kSite);
});
ResolveJavascriptCallback(base::Value(callback_id),
base::Value(std::move(site_list)));
}
void CookiesViewHandler::ProcessPendingRequests() {
if (pending_requests_.empty())
return;
// To ensure that multiple requests do not run during a tree model batch
// update, only tasks for a single request are queued at any one time.
if (request_in_progress_)
return;
request_in_progress_ = true;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(pending_requests_.front().initial_task));
if (pending_requests_.front().batch_behavior != Request::ASYNC_BATCH) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&CookiesViewHandler::RequestComplete,
callback_weak_ptr_factory_.GetWeakPtr()));
}
}
void CookiesViewHandler::RequestComplete() {
DCHECK(!pending_requests_.empty());
DCHECK(!batch_update_);
request_in_progress_ = false;
pending_requests_.pop();
ProcessPendingRequests();
}
} // namespace settings