blob: 230b7887552c8b10007d0f8474792678a9fc7e51 [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 "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/threading/platform_thread.h"
#include "base/values.h"
#include "content/browser/indexed_db/indexed_db_context_impl.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 "grit/content_resources.h"
#include "third_party/zlib/google/zip.h"
#include "ui/base/text/bytes_formatting.h"
#include "webkit/common/database/database_identifier.h"
namespace content {
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->SetUseJsonJSFormatV2();
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);
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::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread,
base::Unretained(this),
context,
partition->GetPath()));
}
void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(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()->RunsTasksOnCurrentThread());
scoped_ptr<ListValue> info_list(static_cast<IndexedDBContextImpl*>(
context.get())->GetAllOriginsDetails());
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&IndexedDBInternalsUI::OnOriginsReady,
base::Unretained(this),
base::Passed(&info_list),
context_path));
}
void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr<ListValue> origins,
const base::FilePath& path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
web_ui()->CallJavascriptFunction(
"indexeddb.onOriginsReady", *origins, base::StringValue(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,
GURL* origin_url,
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_url = GURL(url_string);
return GetOriginContext(*partition_path, *origin_url, context);
}
bool IndexedDBInternalsUI::GetOriginContext(
const base::FilePath& path,
const GURL& origin_url,
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))
return false;
return true;
}
void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::FilePath partition_path;
GURL origin_url;
scoped_refptr<IndexedDBContextImpl> context;
if (!GetOriginData(args, &partition_path, &origin_url, &context))
return;
DCHECK(context);
context->TaskRunner()->PostTask(
FROM_HERE,
base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread,
base::Unretained(this),
partition_path,
context,
origin_url));
}
void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::FilePath partition_path;
GURL origin_url;
scoped_refptr<IndexedDBContextImpl> context;
if (!GetOriginData(args, &partition_path, &origin_url, &context))
return;
context->TaskRunner()->PostTask(
FROM_HERE,
base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread,
base::Unretained(this),
partition_path,
context,
origin_url));
}
void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const GURL& origin_url) {
DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
// Make sure the database hasn't been deleted since the page was loaded.
if (!context->IsInOriginSet(origin_url))
return;
context->ForceClose(origin_url);
size_t connection_count = context->GetConnectionCount(origin_url);
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir())
return;
// This will get cleaned up on the File thread after the download
// has completed.
base::FilePath temp_path = temp_dir.Take();
std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
base::FilePath zip_path =
temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip"));
// This happens on the "webkit" thread (which is really just the IndexedDB
// thread) as a simple way to avoid another script reopening the origin
// while we are zipping.
zip::Zip(context->GetFilePath(origin_url), zip_path, true);
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady,
base::Unretained(this),
partition_path,
origin_url,
temp_path,
zip_path,
connection_count));
}
void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
const base::FilePath& partition_path,
const scoped_refptr<IndexedDBContextImpl> context,
const GURL& origin_url) {
DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
// Make sure the database hasn't been deleted since the page was loaded.
if (!context->IsInOriginSet(origin_url))
return;
context->ForceClose(origin_url);
size_t connection_count = context->GetConnectionCount(origin_url);
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&IndexedDBInternalsUI::OnForcedClose,
base::Unretained(this),
partition_path,
origin_url,
connection_count));
}
void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
const GURL& origin_url,
size_t connection_count) {
web_ui()->CallJavascriptFunction(
"indexeddb.onForcedClose",
base::StringValue(partition_path.value()),
base::StringValue(origin_url.spec()),
base::FundamentalValue(double(connection_count)));
}
void IndexedDBInternalsUI::OnDownloadDataReady(
const base::FilePath& partition_path,
const GURL& origin_url,
const base::FilePath temp_path,
const base::FilePath zip_path,
size_t connection_count) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
BrowserContext* browser_context =
web_ui()->GetWebContents()->GetBrowserContext();
scoped_ptr<DownloadUrlParameters> dl_params(
DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url));
DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context);
const GURL referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
dl_params->set_referrer(
content::Referrer(referrer, WebKit::WebReferrerPolicyDefault));
// 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_url,
temp_path,
connection_count));
dlm->DownloadUrl(dl_params.Pass());
}
// 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) {}
virtual ~FileDeleter();
virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {}
virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {}
virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {}
private:
const base::FilePath temp_dir_;
};
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);
BrowserThread::DeleteOnFileThread::Destruct(this);
break;
}
default:
NOTREACHED();
}
}
FileDeleter::~FileDeleter() {
base::ScopedTempDir path;
bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
DCHECK(will_delete);
}
void IndexedDBInternalsUI::OnDownloadStarted(
const base::FilePath& partition_path,
const GURL& origin_url,
const base::FilePath& temp_path,
size_t connection_count,
DownloadItem* item,
net::Error error) {
if (error != net::OK) {
LOG(ERROR)
<< "Error downloading database dump: " << net::ErrorToString(error);
return;
}
item->AddObserver(new FileDeleter(temp_path));
web_ui()->CallJavascriptFunction(
"indexeddb.onOriginDownloadReady",
base::StringValue(partition_path.value()),
base::StringValue(origin_url.spec()),
base::FundamentalValue(double(connection_count)));
}
} // namespace content