blob: 64ff9c3deb6543e45fe1943a4ad0cd413d9f7a90 [file] [log] [blame]
// Copyright (c) 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/indexed_db/indexed_db_internals_ui.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/platform_thread.h"
#include "base/values.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/grit/content_resources.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/common/url_constants.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "storage/common/database/database_identifier.h"
#include "third_party/zlib/google/zip.h"
#include "ui/base/text/bytes_formatting.h"
#include "url/origin.h"
using url::Origin;
namespace content {
namespace {
bool AllowWhitelistedPaths(const std::vector<base::FilePath>& allowed_paths,
const base::FilePath& candidate_path) {
for (const base::FilePath& allowed_path : allowed_paths) {
if (allowed_path.IsParent(candidate_path))
return true;
}
return false;
}
} // namespace
IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
: WebUIController(web_ui) {
web_ui->RegisterMessageCallback(
"getAllOrigins",
base::Bind(&IndexedDBInternalsUI::GetAllOrigins, base::Unretained(this)));
web_ui->RegisterMessageCallback(
"downloadOriginData",
base::Bind(&IndexedDBInternalsUI::DownloadOriginData,
base::Unretained(this)));
web_ui->RegisterMessageCallback(
"forceClose",
base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin,
base::Unretained(this)));
WebUIDataSource* source =
WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost);
source->SetJsonPath("strings.js");
source->AddResourcePath("indexeddb_internals.js",
IDR_INDEXED_DB_INTERNALS_JS);
source->AddResourcePath("indexeddb_internals.css",
IDR_INDEXED_DB_INTERNALS_CSS);
source->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML);
source->UseGzip();
BrowserContext* browser_context =
web_ui->GetWebContents()->GetBrowserContext();
WebUIDataSource::Add(browser_context, source);
}
IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
void IndexedDBInternalsUI::AddContextFromStoragePartition(
StoragePartition* partition) {
scoped_refptr<IndexedDBContext> context = partition->GetIndexedDBContext();
context->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread,
base::Unretained(this), context, partition->GetPath()));
}
void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserContext* browser_context =
web_ui()->GetWebContents()->GetBrowserContext();
BrowserContext::StoragePartitionCallback cb =
base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition,
base::Unretained(this));
BrowserContext::ForEachStoragePartition(browser_context, cb);
}
void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread(
scoped_refptr<IndexedDBContext> context,
const base::FilePath& context_path) {
DCHECK(context->TaskRunner()->RunsTasksInCurrentSequence());
IndexedDBContextImpl* context_impl =
static_cast<IndexedDBContextImpl*>(context.get());
std::unique_ptr<base::ListValue> info_list(
context_impl->GetAllOriginsDetails());
bool is_incognito = context_impl->is_incognito();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnOriginsReady,
base::Unretained(this), base::Passed(&info_list),
is_incognito ? base::FilePath() : context_path));
}
void IndexedDBInternalsUI::OnOriginsReady(
std::unique_ptr<base::ListValue> origins,
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
web_ui()->CallJavascriptFunctionUnsafe("indexeddb.onOriginsReady", *origins,
base::Value(path.value()));
}
static void FindContext(const base::FilePath& partition_path,
StoragePartition** result_partition,
scoped_refptr<IndexedDBContextImpl>* result_context,
StoragePartition* storage_partition) {
if (storage_partition->GetPath() == partition_path) {
*result_partition = storage_partition;
*result_context = static_cast<IndexedDBContextImpl*>(
storage_partition->GetIndexedDBContext());
}
}
bool IndexedDBInternalsUI::GetOriginData(
const base::ListValue* args,
base::FilePath* partition_path,
Origin* origin,
scoped_refptr<IndexedDBContextImpl>* context) {
base::FilePath::StringType path_string;
if (!args->GetString(0, &path_string))
return false;
*partition_path = base::FilePath(path_string);
std::string url_string;
if (!args->GetString(1, &url_string))
return false;
*origin = Origin::Create(GURL(url_string));
return GetOriginContext(*partition_path, *origin, context);
}
bool IndexedDBInternalsUI::GetOriginContext(
const base::FilePath& path,
const Origin& origin,
scoped_refptr<IndexedDBContextImpl>* context) {
// search the origins to find the right context
BrowserContext* browser_context =
web_ui()->GetWebContents()->GetBrowserContext();
StoragePartition* result_partition;
BrowserContext::StoragePartitionCallback cb =
base::Bind(&FindContext, path, &result_partition, context);
BrowserContext::ForEachStoragePartition(browser_context, cb);
if (!result_partition || !(context->get()))
return false;
return true;
}
void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::FilePath partition_path;
Origin origin;
scoped_refptr<IndexedDBContextImpl> context;
if (!GetOriginData(args, &partition_path, &origin, &context))
return;
DCHECK(context.get());
context->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread,
base::Unretained(this), partition_path, context, origin));
}
void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::FilePath partition_path;
Origin origin;
scoped_refptr<IndexedDBContextImpl> context;
if (!GetOriginData(args, &partition_path, &origin, &context))
return;
context->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread,
base::Unretained(this), partition_path, context, origin));
}
void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const Origin& origin) {
DCHECK(context->TaskRunner()->RunsTasksInCurrentSequence());
// This runs on the IndexedDB task runner to prevent script from reopening
// the origin while we are zipping.
// Make sure the database hasn't been deleted since the page was loaded.
if (!context->HasOrigin(origin))
return;
context->ForceClose(origin, IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
size_t connection_count = context->GetConnectionCount(origin);
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir())
return;
// This will get cleaned up after the download has completed.
base::FilePath temp_path = temp_dir.Take();
std::string origin_id = storage::GetIdentifierFromOrigin(origin.GetURL());
base::FilePath zip_path =
temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip"));
std::vector<base::FilePath> paths = context->GetStoragePaths(origin);
zip::ZipWithFilterCallback(context->data_path(), zip_path,
base::Bind(AllowWhitelistedPaths, paths));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnDownloadDataReady,
base::Unretained(this), partition_path, origin, temp_path,
zip_path, connection_count));
}
void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const Origin& origin) {
DCHECK(context->TaskRunner()->RunsTasksInCurrentSequence());
// Make sure the database hasn't been deleted since the page was loaded.
if (!context->HasOrigin(origin))
return;
context->ForceClose(origin, IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
size_t connection_count = context->GetConnectionCount(origin);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(&IndexedDBInternalsUI::OnForcedClose,
base::Unretained(this), partition_path,
origin, connection_count));
}
void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
const Origin& origin,
size_t connection_count) {
web_ui()->CallJavascriptFunctionUnsafe(
"indexeddb.onForcedClose", base::Value(partition_path.value()),
base::Value(origin.Serialize()),
base::Value(static_cast<double>(connection_count)));
}
void IndexedDBInternalsUI::OnDownloadDataReady(
const base::FilePath& partition_path,
const Origin& origin,
const base::FilePath temp_path,
const base::FilePath zip_path,
size_t connection_count) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
WebContents* web_contents = web_ui()->GetWebContents();
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("indexed_db_internals_handler", R"(
semantics {
sender: "Indexed DB Internals"
description:
"This is an internal Chrome webpage that displays debug "
"information about IndexedDB usage and data, used by developers."
trigger: "When a user navigates to chrome://indexeddb-internals/."
data: "None."
destination: LOCAL
}
policy {
cookies_allowed: NO
setting:
"This feature cannot be disabled by settings, but it's only "
"triggered by navigating to the specified URL."
policy_exception_justification:
"Not implemented. Indexed DB is Chrome's internal local data "
"storage."
})");
std::unique_ptr<DownloadUrlParameters> dl_params(
DownloadUrlParameters::CreateForWebContentsMainFrame(web_contents, url,
traffic_annotation));
const GURL referrer(web_contents->GetLastCommittedURL());
dl_params->set_referrer(content::Referrer::SanitizeForRequest(
url, content::Referrer(referrer, blink::kWebReferrerPolicyDefault)));
// This is how to watch for the download to finish: first wait for it
// to start, then attach a DownloadItem::Observer to observe the
// state change to the finished state.
dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted,
base::Unretained(this), partition_path,
origin, temp_path, connection_count));
BrowserContext* context = web_contents->GetBrowserContext();
BrowserContext::GetDownloadManager(context)->DownloadUrl(
std::move(dl_params));
}
// The entire purpose of this class is to delete the temp file after
// the download is complete.
class FileDeleter : public DownloadItem::Observer {
public:
explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
~FileDeleter() override;
void OnDownloadUpdated(DownloadItem* download) override;
void OnDownloadOpened(DownloadItem* item) override {}
void OnDownloadRemoved(DownloadItem* item) override {}
void OnDownloadDestroyed(DownloadItem* item) override {}
private:
const base::FilePath temp_dir_;
DISALLOW_COPY_AND_ASSIGN(FileDeleter);
};
void FileDeleter::OnDownloadUpdated(DownloadItem* item) {
switch (item->GetState()) {
case DownloadItem::IN_PROGRESS:
break;
case DownloadItem::COMPLETE:
case DownloadItem::CANCELLED:
case DownloadItem::INTERRUPTED: {
item->RemoveObserver(this);
delete this;
break;
}
default:
NOTREACHED();
}
}
FileDeleter::~FileDeleter() {
base::PostTaskWithTraits(FROM_HERE,
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::Bind(base::IgnoreResult(&base::DeleteFile),
std::move(temp_dir_), true));
}
void IndexedDBInternalsUI::OnDownloadStarted(
const base::FilePath& partition_path,
const Origin& origin,
const base::FilePath& temp_path,
size_t connection_count,
DownloadItem* item,
DownloadInterruptReason interrupt_reason) {
if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
LOG(ERROR) << "Error downloading database dump: "
<< DownloadInterruptReasonToString(interrupt_reason);
return;
}
item->AddObserver(new FileDeleter(temp_path));
web_ui()->CallJavascriptFunctionUnsafe(
"indexeddb.onOriginDownloadReady", base::Value(partition_path.value()),
base::Value(origin.Serialize()),
base::Value(static_cast<double>(connection_count)));
}
} // namespace content