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 "storage/common/database/database_identifier.h"
25 #include "third_party/zlib/google/zip.h"
26 #include "ui/base/text/bytes_formatting.h"
32 bool AllowWhitelistedPaths(const std::vector
<base::FilePath
>& allowed_paths
,
33 const base::FilePath
& candidate_path
) {
34 for (const base::FilePath
& allowed_path
: allowed_paths
) {
35 if (allowed_path
.IsParent(candidate_path
))
43 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI
* web_ui
)
44 : WebUIController(web_ui
) {
45 web_ui
->RegisterMessageCallback(
47 base::Bind(&IndexedDBInternalsUI::GetAllOrigins
, base::Unretained(this)));
49 web_ui
->RegisterMessageCallback(
51 base::Bind(&IndexedDBInternalsUI::DownloadOriginData
,
52 base::Unretained(this)));
53 web_ui
->RegisterMessageCallback(
55 base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin
,
56 base::Unretained(this)));
58 WebUIDataSource
* source
=
59 WebUIDataSource::Create(kChromeUIIndexedDBInternalsHost
);
60 source
->SetJsonPath("strings.js");
61 source
->AddResourcePath("indexeddb_internals.js",
62 IDR_INDEXED_DB_INTERNALS_JS
);
63 source
->AddResourcePath("indexeddb_internals.css",
64 IDR_INDEXED_DB_INTERNALS_CSS
);
65 source
->SetDefaultResource(IDR_INDEXED_DB_INTERNALS_HTML
);
67 BrowserContext
* browser_context
=
68 web_ui
->GetWebContents()->GetBrowserContext();
69 WebUIDataSource::Add(browser_context
, source
);
72 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
74 void IndexedDBInternalsUI::AddContextFromStoragePartition(
75 StoragePartition
* partition
) {
76 scoped_refptr
<IndexedDBContext
> context
= partition
->GetIndexedDBContext();
77 context
->TaskRunner()->PostTask(
79 base::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread
,
80 base::Unretained(this),
82 partition
->GetPath()));
85 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue
* args
) {
86 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
88 BrowserContext
* browser_context
=
89 web_ui()->GetWebContents()->GetBrowserContext();
91 BrowserContext::StoragePartitionCallback cb
=
92 base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition
,
93 base::Unretained(this));
94 BrowserContext::ForEachStoragePartition(browser_context
, cb
);
97 void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread(
98 scoped_refptr
<IndexedDBContext
> context
,
99 const base::FilePath
& context_path
) {
100 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
102 IndexedDBContextImpl
* context_impl
=
103 static_cast<IndexedDBContextImpl
*>(context
.get());
105 scoped_ptr
<base::ListValue
> info_list(context_impl
->GetAllOriginsDetails());
106 bool is_incognito
= context_impl
->is_incognito();
108 BrowserThread::PostTask(
111 base::Bind(&IndexedDBInternalsUI::OnOriginsReady
,
112 base::Unretained(this),
113 base::Passed(&info_list
),
114 is_incognito
? base::FilePath() : context_path
));
117 void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr
<base::ListValue
> origins
,
118 const base::FilePath
& path
) {
119 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
120 web_ui()->CallJavascriptFunction(
121 "indexeddb.onOriginsReady", *origins
, base::StringValue(path
.value()));
124 static void FindContext(const base::FilePath
& partition_path
,
125 StoragePartition
** result_partition
,
126 scoped_refptr
<IndexedDBContextImpl
>* result_context
,
127 StoragePartition
* storage_partition
) {
128 if (storage_partition
->GetPath() == partition_path
) {
129 *result_partition
= storage_partition
;
130 *result_context
= static_cast<IndexedDBContextImpl
*>(
131 storage_partition
->GetIndexedDBContext());
135 bool IndexedDBInternalsUI::GetOriginData(
136 const base::ListValue
* args
,
137 base::FilePath
* partition_path
,
139 scoped_refptr
<IndexedDBContextImpl
>* context
) {
140 base::FilePath::StringType path_string
;
141 if (!args
->GetString(0, &path_string
))
143 *partition_path
= base::FilePath(path_string
);
145 std::string url_string
;
146 if (!args
->GetString(1, &url_string
))
149 *origin_url
= GURL(url_string
);
151 return GetOriginContext(*partition_path
, *origin_url
, context
);
154 bool IndexedDBInternalsUI::GetOriginContext(
155 const base::FilePath
& path
,
156 const GURL
& origin_url
,
157 scoped_refptr
<IndexedDBContextImpl
>* context
) {
158 // search the origins to find the right context
159 BrowserContext
* browser_context
=
160 web_ui()->GetWebContents()->GetBrowserContext();
162 StoragePartition
* result_partition
;
163 BrowserContext::StoragePartitionCallback cb
=
164 base::Bind(&FindContext
, path
, &result_partition
, context
);
165 BrowserContext::ForEachStoragePartition(browser_context
, cb
);
167 if (!result_partition
|| !(context
->get()))
173 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue
* args
) {
174 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
176 base::FilePath partition_path
;
178 scoped_refptr
<IndexedDBContextImpl
> context
;
179 if (!GetOriginData(args
, &partition_path
, &origin_url
, &context
))
182 DCHECK(context
.get());
183 context
->TaskRunner()->PostTask(
185 base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread
,
186 base::Unretained(this),
192 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue
* args
) {
193 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
195 base::FilePath partition_path
;
197 scoped_refptr
<IndexedDBContextImpl
> context
;
198 if (!GetOriginData(args
, &partition_path
, &origin_url
, &context
))
201 context
->TaskRunner()->PostTask(
203 base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread
,
204 base::Unretained(this),
210 void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
211 const base::FilePath
& partition_path
,
212 const scoped_refptr
<IndexedDBContextImpl
> context
,
213 const GURL
& origin_url
) {
214 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
216 // Make sure the database hasn't been deleted since the page was loaded.
217 if (!context
->IsInOriginSet(origin_url
))
220 context
->ForceClose(origin_url
,
221 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE
);
222 size_t connection_count
= context
->GetConnectionCount(origin_url
);
224 base::ScopedTempDir temp_dir
;
225 if (!temp_dir
.CreateUniqueTempDir())
228 // This will get cleaned up on the File thread after the download
230 base::FilePath temp_path
= temp_dir
.Take();
232 std::string origin_id
= storage::GetIdentifierFromOrigin(origin_url
);
233 base::FilePath zip_path
=
234 temp_path
.AppendASCII(origin_id
).AddExtension(FILE_PATH_LITERAL("zip"));
236 // This happens on the "webkit" thread (which is really just the IndexedDB
237 // thread) as a simple way to avoid another script reopening the origin
238 // while we are zipping.
239 std::vector
<base::FilePath
> paths
= context
->GetStoragePaths(origin_url
);
240 zip::ZipWithFilterCallback(context
->data_path(), zip_path
,
241 base::Bind(AllowWhitelistedPaths
, paths
));
243 BrowserThread::PostTask(BrowserThread::UI
,
245 base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady
,
246 base::Unretained(this),
254 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
255 const base::FilePath
& partition_path
,
256 const scoped_refptr
<IndexedDBContextImpl
> context
,
257 const GURL
& origin_url
) {
258 DCHECK(context
->TaskRunner()->RunsTasksOnCurrentThread());
260 // Make sure the database hasn't been deleted since the page was loaded.
261 if (!context
->IsInOriginSet(origin_url
))
264 context
->ForceClose(origin_url
,
265 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE
);
266 size_t connection_count
= context
->GetConnectionCount(origin_url
);
268 BrowserThread::PostTask(BrowserThread::UI
,
270 base::Bind(&IndexedDBInternalsUI::OnForcedClose
,
271 base::Unretained(this),
277 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath
& partition_path
,
278 const GURL
& origin_url
,
279 size_t connection_count
) {
280 web_ui()->CallJavascriptFunction(
281 "indexeddb.onForcedClose",
282 base::StringValue(partition_path
.value()),
283 base::StringValue(origin_url
.spec()),
284 base::FundamentalValue(static_cast<double>(connection_count
)));
287 void IndexedDBInternalsUI::OnDownloadDataReady(
288 const base::FilePath
& partition_path
,
289 const GURL
& origin_url
,
290 const base::FilePath temp_path
,
291 const base::FilePath zip_path
,
292 size_t connection_count
) {
293 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
294 const GURL url
= GURL(FILE_PATH_LITERAL("file://") + zip_path
.value());
295 BrowserContext
* browser_context
=
296 web_ui()->GetWebContents()->GetBrowserContext();
297 scoped_ptr
<DownloadUrlParameters
> dl_params(
298 DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url
));
299 DownloadManager
* dlm
= BrowserContext::GetDownloadManager(browser_context
);
301 const GURL
referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
302 dl_params
->set_referrer(content::Referrer::SanitizeForRequest(
303 url
, content::Referrer(referrer
, blink::WebReferrerPolicyDefault
)));
305 // This is how to watch for the download to finish: first wait for it
306 // to start, then attach a DownloadItem::Observer to observe the
307 // state change to the finished state.
308 dl_params
->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted
,
309 base::Unretained(this),
314 dlm
->DownloadUrl(dl_params
.Pass());
317 // The entire purpose of this class is to delete the temp file after
318 // the download is complete.
319 class FileDeleter
: public DownloadItem::Observer
{
321 explicit FileDeleter(const base::FilePath
& temp_dir
) : temp_dir_(temp_dir
) {}
322 ~FileDeleter() override
;
324 void OnDownloadUpdated(DownloadItem
* download
) override
;
325 void OnDownloadOpened(DownloadItem
* item
) override
{}
326 void OnDownloadRemoved(DownloadItem
* item
) override
{}
327 void OnDownloadDestroyed(DownloadItem
* item
) override
{}
330 const base::FilePath temp_dir_
;
332 DISALLOW_COPY_AND_ASSIGN(FileDeleter
);
335 void FileDeleter::OnDownloadUpdated(DownloadItem
* item
) {
336 switch (item
->GetState()) {
337 case DownloadItem::IN_PROGRESS
:
339 case DownloadItem::COMPLETE
:
340 case DownloadItem::CANCELLED
:
341 case DownloadItem::INTERRUPTED
: {
342 item
->RemoveObserver(this);
343 BrowserThread::DeleteOnFileThread::Destruct(this);
351 FileDeleter::~FileDeleter() {
352 base::ScopedTempDir path
;
353 bool will_delete
= path
.Set(temp_dir_
);
357 void IndexedDBInternalsUI::OnDownloadStarted(
358 const base::FilePath
& partition_path
,
359 const GURL
& origin_url
,
360 const base::FilePath
& temp_path
,
361 size_t connection_count
,
363 DownloadInterruptReason interrupt_reason
) {
365 if (interrupt_reason
!= DOWNLOAD_INTERRUPT_REASON_NONE
) {
366 LOG(ERROR
) << "Error downloading database dump: "
367 << DownloadInterruptReasonToString(interrupt_reason
);
371 item
->AddObserver(new FileDeleter(temp_path
));
372 web_ui()->CallJavascriptFunction(
373 "indexeddb.onOriginDownloadReady",
374 base::StringValue(partition_path
.value()),
375 base::StringValue(origin_url
.spec()),
376 base::FundamentalValue(static_cast<double>(connection_count
)));
379 } // namespace content