1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/indexed_db/indexed_db_internals_ui.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/threading/platform_thread.h"
12 #include "base/values.h"
13 #include "content/browser/indexed_db/indexed_db_context_impl.h"
14 #include "content/grit/content_resources.h"
15 #include "content/public/browser/browser_context.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/download_manager.h"
18 #include "content/public/browser/download_url_parameters.h"
19 #include "content/public/browser/storage_partition.h"
20 #include "content/public/browser/web_contents.h"
21 #include "content/public/browser/web_ui.h"
22 #include "content/public/browser/web_ui_data_source.h"
23 #include "content/public/common/url_constants.h"
24 #include "third_party/zlib/google/zip.h"
25 #include "ui/base/text/bytes_formatting.h"
26 #include "webkit/common/database/database_identifier.h"
30 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI
* web_ui
)
31 : WebUIController(web_ui
) {
32 web_ui
->RegisterMessageCallback(
34 base::Bind(&IndexedDBInternalsUI::GetAllOrigins
, base::Unretained(this)));
36 web_ui
->RegisterMessageCallback(
38 base::Bind(&IndexedDBInternalsUI::DownloadOriginData
,
39 base::Unretained(this)));
40 web_ui
->RegisterMessageCallback(
42 base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin
,
43 base::Unretained(this)));
45 WebUIDataSource
* source
=
46 WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost
);
47 source
->SetUseJsonJSFormatV2();
48 source
->SetJsonPath("strings.js");
49 source
->AddResourcePath("indexeddb_internals.js",
50 IDR_INDEXED_DB_INTERNALS_JS
);
51 source
->AddResourcePath("indexeddb_internals.css",
52 IDR_INDEXED_DB_INTERNALS_CSS
);
53 source
->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML
);
55 BrowserContext
* browser_context
=
56 web_ui
->GetWebContents()->GetBrowserContext();
57 WebUIDataSource::Add(browser_context
, source
);
60 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
62 void IndexedDBInternalsUI::AddContextFromStoragePartition(
63 StoragePartition
* partition
) {
64 scoped_refptr
<IndexedDBContext
> context
= partition
->GetIndexedDBContext();
65 context
->TaskRunner()->PostTask(
67 base::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread
,
68 base::Unretained(this),
70 partition
->GetPath()));
73 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue
* args
) {
74 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
76 BrowserContext
* browser_context
=
77 web_ui()->GetWebContents()->GetBrowserContext();
79 BrowserContext::StoragePartitionCallback cb
=
80 base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition
,
81 base::Unretained(this));
82 BrowserContext::ForEachStoragePartition(browser_context
, cb
);
85 void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread(
86 scoped_refptr
<IndexedDBContext
> context
,
87 const base::FilePath
& context_path
) {
88 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
90 IndexedDBContextImpl
* context_impl
=
91 static_cast<IndexedDBContextImpl
*>(context
.get());
93 scoped_ptr
<base::ListValue
> info_list(context_impl
->GetAllOriginsDetails());
94 bool is_incognito
= context_impl
->is_incognito();
96 BrowserThread::PostTask(
99 base::Bind(&IndexedDBInternalsUI::OnOriginsReady
,
100 base::Unretained(this),
101 base::Passed(&info_list
),
102 is_incognito
? base::FilePath() : context_path
));
105 void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr
<base::ListValue
> origins
,
106 const base::FilePath
& path
) {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
108 web_ui()->CallJavascriptFunction(
109 "indexeddb.onOriginsReady", *origins
, base::StringValue(path
.value()));
112 static void FindContext(const base::FilePath
& partition_path
,
113 StoragePartition
** result_partition
,
114 scoped_refptr
<IndexedDBContextImpl
>* result_context
,
115 StoragePartition
* storage_partition
) {
116 if (storage_partition
->GetPath() == partition_path
) {
117 *result_partition
= storage_partition
;
118 *result_context
= static_cast<IndexedDBContextImpl
*>(
119 storage_partition
->GetIndexedDBContext());
123 bool IndexedDBInternalsUI::GetOriginData(
124 const base::ListValue
* args
,
125 base::FilePath
* partition_path
,
127 scoped_refptr
<IndexedDBContextImpl
>* context
) {
128 base::FilePath::StringType path_string
;
129 if (!args
->GetString(0, &path_string
))
131 *partition_path
= base::FilePath(path_string
);
133 std::string url_string
;
134 if (!args
->GetString(1, &url_string
))
137 *origin_url
= GURL(url_string
);
139 return GetOriginContext(*partition_path
, *origin_url
, context
);
142 bool IndexedDBInternalsUI::GetOriginContext(
143 const base::FilePath
& path
,
144 const GURL
& origin_url
,
145 scoped_refptr
<IndexedDBContextImpl
>* context
) {
146 // search the origins to find the right context
147 BrowserContext
* browser_context
=
148 web_ui()->GetWebContents()->GetBrowserContext();
150 StoragePartition
* result_partition
;
151 BrowserContext::StoragePartitionCallback cb
=
152 base::Bind(&FindContext
, path
, &result_partition
, context
);
153 BrowserContext::ForEachStoragePartition(browser_context
, cb
);
155 if (!result_partition
|| !(context
->get()))
161 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue
* args
) {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
164 base::FilePath partition_path
;
166 scoped_refptr
<IndexedDBContextImpl
> context
;
167 if (!GetOriginData(args
, &partition_path
, &origin_url
, &context
))
170 DCHECK(context
.get());
171 context
->TaskRunner()->PostTask(
173 base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread
,
174 base::Unretained(this),
180 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue
* args
) {
181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
183 base::FilePath partition_path
;
185 scoped_refptr
<IndexedDBContextImpl
> context
;
186 if (!GetOriginData(args
, &partition_path
, &origin_url
, &context
))
189 context
->TaskRunner()->PostTask(
191 base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread
,
192 base::Unretained(this),
198 void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
199 const base::FilePath
& partition_path
,
200 const scoped_refptr
<IndexedDBContextImpl
> context
,
201 const GURL
& origin_url
) {
202 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
204 // Make sure the database hasn't been deleted since the page was loaded.
205 if (!context
->IsInOriginSet(origin_url
))
208 context
->ForceClose(origin_url
,
209 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE
);
210 size_t connection_count
= context
->GetConnectionCount(origin_url
);
212 base::ScopedTempDir temp_dir
;
213 if (!temp_dir
.CreateUniqueTempDir())
216 // This will get cleaned up on the File thread after the download
218 base::FilePath temp_path
= temp_dir
.Take();
220 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
221 base::FilePath zip_path
=
222 temp_path
.AppendASCII(origin_id
).AddExtension(FILE_PATH_LITERAL("zip"));
224 // This happens on the "webkit" thread (which is really just the IndexedDB
225 // thread) as a simple way to avoid another script reopening the origin
226 // while we are zipping.
227 zip::Zip(context
->GetFilePath(origin_url
), zip_path
, true);
229 BrowserThread::PostTask(BrowserThread::UI
,
231 base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady
,
232 base::Unretained(this),
240 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
241 const base::FilePath
& partition_path
,
242 const scoped_refptr
<IndexedDBContextImpl
> context
,
243 const GURL
& origin_url
) {
244 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
246 // Make sure the database hasn't been deleted since the page was loaded.
247 if (!context
->IsInOriginSet(origin_url
))
250 context
->ForceClose(origin_url
,
251 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE
);
252 size_t connection_count
= context
->GetConnectionCount(origin_url
);
254 BrowserThread::PostTask(BrowserThread::UI
,
256 base::Bind(&IndexedDBInternalsUI::OnForcedClose
,
257 base::Unretained(this),
263 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath
& partition_path
,
264 const GURL
& origin_url
,
265 size_t connection_count
) {
266 web_ui()->CallJavascriptFunction(
267 "indexeddb.onForcedClose",
268 base::StringValue(partition_path
.value()),
269 base::StringValue(origin_url
.spec()),
270 base::FundamentalValue(static_cast<double>(connection_count
)));
273 void IndexedDBInternalsUI::OnDownloadDataReady(
274 const base::FilePath
& partition_path
,
275 const GURL
& origin_url
,
276 const base::FilePath temp_path
,
277 const base::FilePath zip_path
,
278 size_t connection_count
) {
279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
280 const GURL url
= GURL(FILE_PATH_LITERAL("file://") + zip_path
.value());
281 BrowserContext
* browser_context
=
282 web_ui()->GetWebContents()->GetBrowserContext();
283 scoped_ptr
<DownloadUrlParameters
> dl_params(
284 DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url
));
285 DownloadManager
* dlm
= BrowserContext::GetDownloadManager(browser_context
);
287 const GURL
referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
288 dl_params
->set_referrer(
289 content::Referrer(referrer
, blink::WebReferrerPolicyDefault
));
291 // This is how to watch for the download to finish: first wait for it
292 // to start, then attach a DownloadItem::Observer to observe the
293 // state change to the finished state.
294 dl_params
->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted
,
295 base::Unretained(this),
300 dlm
->DownloadUrl(dl_params
.Pass());
303 // The entire purpose of this class is to delete the temp file after
304 // the download is complete.
305 class FileDeleter
: public DownloadItem::Observer
{
307 explicit FileDeleter(const base::FilePath
& temp_dir
) : temp_dir_(temp_dir
) {}
308 virtual ~FileDeleter();
310 virtual void OnDownloadUpdated(DownloadItem
* download
) OVERRIDE
;
311 virtual void OnDownloadOpened(DownloadItem
* item
) OVERRIDE
{}
312 virtual void OnDownloadRemoved(DownloadItem
* item
) OVERRIDE
{}
313 virtual void OnDownloadDestroyed(DownloadItem
* item
) OVERRIDE
{}
316 const base::FilePath temp_dir_
;
318 DISALLOW_COPY_AND_ASSIGN(FileDeleter
);
321 void FileDeleter::OnDownloadUpdated(DownloadItem
* item
) {
322 switch (item
->GetState()) {
323 case DownloadItem::IN_PROGRESS
:
325 case DownloadItem::COMPLETE
:
326 case DownloadItem::CANCELLED
:
327 case DownloadItem::INTERRUPTED
: {
328 item
->RemoveObserver(this);
329 BrowserThread::DeleteOnFileThread::Destruct(this);
337 FileDeleter::~FileDeleter() {
338 base::ScopedTempDir path
;
339 bool will_delete ALLOW_UNUSED
= path
.Set(temp_dir_
);
343 void IndexedDBInternalsUI::OnDownloadStarted(
344 const base::FilePath
& partition_path
,
345 const GURL
& origin_url
,
346 const base::FilePath
& temp_path
,
347 size_t connection_count
,
349 DownloadInterruptReason interrupt_reason
) {
351 if (interrupt_reason
!= DOWNLOAD_INTERRUPT_REASON_NONE
) {
352 LOG(ERROR
) << "Error downloading database dump: "
353 << DownloadInterruptReasonToString(interrupt_reason
);
357 item
->AddObserver(new FileDeleter(temp_path
));
358 web_ui()->CallJavascriptFunction(
359 "indexeddb.onOriginDownloadReady",
360 base::StringValue(partition_path
.value()),
361 base::StringValue(origin_url
.spec()),
362 base::FundamentalValue(static_cast<double>(connection_count
)));
365 } // namespace content