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.
4
5 #include "content/browser/indexed_db/indexed_db_internals_ui.h"
6
7 #include <string>
8
9 #include "base/bind.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/public/browser/browser_context.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/download_manager.h"
17 #include "content/public/browser/download_url_parameters.h"
18 #include "content/public/browser/storage_partition.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/browser/web_ui.h"
21 #include "content/public/browser/web_ui_data_source.h"
22 #include "content/public/common/url_constants.h"
23 #include "grit/content_resources.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"
27
28 namespace content {
29
IndexedDBInternalsUI(WebUI * web_ui)30 IndexedDBInternalsUI::IndexedDBInternalsUI(WebUI* web_ui)
31 : WebUIController(web_ui) {
32 web_ui->RegisterMessageCallback(
33 "getAllOrigins",
34 base::Bind(&IndexedDBInternalsUI::GetAllOrigins, base::Unretained(this)));
35
36 web_ui->RegisterMessageCallback(
37 "downloadOriginData",
38 base::Bind(&IndexedDBInternalsUI::DownloadOriginData,
39 base::Unretained(this)));
40 web_ui->RegisterMessageCallback(
41 "forceClose",
42 base::Bind(&IndexedDBInternalsUI::ForceCloseOrigin,
43 base::Unretained(this)));
44
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);
54
55 BrowserContext* browser_context =
56 web_ui->GetWebContents()->GetBrowserContext();
57 WebUIDataSource::Add(browser_context, source);
58 }
59
~IndexedDBInternalsUI()60 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
61
AddContextFromStoragePartition(StoragePartition * partition)62 void IndexedDBInternalsUI::AddContextFromStoragePartition(
63 StoragePartition* partition) {
64 scoped_refptr<IndexedDBContext> context = partition->GetIndexedDBContext();
65 context->TaskRunner()->PostTask(
66 FROM_HERE,
67 base::Bind(&IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread,
68 base::Unretained(this),
69 context,
70 partition->GetPath()));
71 }
72
GetAllOrigins(const base::ListValue * args)73 void IndexedDBInternalsUI::GetAllOrigins(const base::ListValue* args) {
74 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
75
76 BrowserContext* browser_context =
77 web_ui()->GetWebContents()->GetBrowserContext();
78
79 BrowserContext::StoragePartitionCallback cb =
80 base::Bind(&IndexedDBInternalsUI::AddContextFromStoragePartition,
81 base::Unretained(this));
82 BrowserContext::ForEachStoragePartition(browser_context, cb);
83 }
84
GetAllOriginsOnIndexedDBThread(scoped_refptr<IndexedDBContext> context,const base::FilePath & context_path)85 void IndexedDBInternalsUI::GetAllOriginsOnIndexedDBThread(
86 scoped_refptr<IndexedDBContext> context,
87 const base::FilePath& context_path) {
88 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
89
90 scoped_ptr<base::ListValue> info_list(static_cast<IndexedDBContextImpl*>(
91 context.get())->GetAllOriginsDetails());
92
93 BrowserThread::PostTask(BrowserThread::UI,
94 FROM_HERE,
95 base::Bind(&IndexedDBInternalsUI::OnOriginsReady,
96 base::Unretained(this),
97 base::Passed(&info_list),
98 context_path));
99 }
100
OnOriginsReady(scoped_ptr<base::ListValue> origins,const base::FilePath & path)101 void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr<base::ListValue> origins,
102 const base::FilePath& path) {
103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
104 web_ui()->CallJavascriptFunction(
105 "indexeddb.onOriginsReady", *origins, base::StringValue(path.value()));
106 }
107
FindContext(const base::FilePath & partition_path,StoragePartition ** result_partition,scoped_refptr<IndexedDBContextImpl> * result_context,StoragePartition * storage_partition)108 static void FindContext(const base::FilePath& partition_path,
109 StoragePartition** result_partition,
110 scoped_refptr<IndexedDBContextImpl>* result_context,
111 StoragePartition* storage_partition) {
112 if (storage_partition->GetPath() == partition_path) {
113 *result_partition = storage_partition;
114 *result_context = static_cast<IndexedDBContextImpl*>(
115 storage_partition->GetIndexedDBContext());
116 }
117 }
118
GetOriginData(const base::ListValue * args,base::FilePath * partition_path,GURL * origin_url,scoped_refptr<IndexedDBContextImpl> * context)119 bool IndexedDBInternalsUI::GetOriginData(
120 const base::ListValue* args,
121 base::FilePath* partition_path,
122 GURL* origin_url,
123 scoped_refptr<IndexedDBContextImpl>* context) {
124 base::FilePath::StringType path_string;
125 if (!args->GetString(0, &path_string))
126 return false;
127 *partition_path = base::FilePath(path_string);
128
129 std::string url_string;
130 if (!args->GetString(1, &url_string))
131 return false;
132
133 *origin_url = GURL(url_string);
134
135 return GetOriginContext(*partition_path, *origin_url, context);
136 }
137
GetOriginContext(const base::FilePath & path,const GURL & origin_url,scoped_refptr<IndexedDBContextImpl> * context)138 bool IndexedDBInternalsUI::GetOriginContext(
139 const base::FilePath& path,
140 const GURL& origin_url,
141 scoped_refptr<IndexedDBContextImpl>* context) {
142 // search the origins to find the right context
143 BrowserContext* browser_context =
144 web_ui()->GetWebContents()->GetBrowserContext();
145
146 StoragePartition* result_partition;
147 BrowserContext::StoragePartitionCallback cb =
148 base::Bind(&FindContext, path, &result_partition, context);
149 BrowserContext::ForEachStoragePartition(browser_context, cb);
150
151 if (!result_partition || !(*context))
152 return false;
153
154 return true;
155 }
156
DownloadOriginData(const base::ListValue * args)157 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
158 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159
160 base::FilePath partition_path;
161 GURL origin_url;
162 scoped_refptr<IndexedDBContextImpl> context;
163 if (!GetOriginData(args, &partition_path, &origin_url, &context))
164 return;
165
166 DCHECK(context);
167 context->TaskRunner()->PostTask(
168 FROM_HERE,
169 base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread,
170 base::Unretained(this),
171 partition_path,
172 context,
173 origin_url));
174 }
175
ForceCloseOrigin(const base::ListValue * args)176 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
178
179 base::FilePath partition_path;
180 GURL origin_url;
181 scoped_refptr<IndexedDBContextImpl> context;
182 if (!GetOriginData(args, &partition_path, &origin_url, &context))
183 return;
184
185 context->TaskRunner()->PostTask(
186 FROM_HERE,
187 base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread,
188 base::Unretained(this),
189 partition_path,
190 context,
191 origin_url));
192 }
193
DownloadOriginDataOnIndexedDBThread(const base::FilePath & partition_path,const scoped_refptr<IndexedDBContextImpl> context,const GURL & origin_url)194 void IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread(
195 const base::FilePath& partition_path,
196 const scoped_refptr<IndexedDBContextImpl> context,
197 const GURL& origin_url) {
198 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
199
200 // Make sure the database hasn't been deleted since the page was loaded.
201 if (!context->IsInOriginSet(origin_url))
202 return;
203
204 context->ForceClose(origin_url,
205 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
206 size_t connection_count = context->GetConnectionCount(origin_url);
207
208 base::ScopedTempDir temp_dir;
209 if (!temp_dir.CreateUniqueTempDir())
210 return;
211
212 // This will get cleaned up on the File thread after the download
213 // has completed.
214 base::FilePath temp_path = temp_dir.Take();
215
216 std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
217 base::FilePath zip_path =
218 temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip"));
219
220 // This happens on the "webkit" thread (which is really just the IndexedDB
221 // thread) as a simple way to avoid another script reopening the origin
222 // while we are zipping.
223 zip::Zip(context->GetFilePath(origin_url), zip_path, true);
224
225 BrowserThread::PostTask(BrowserThread::UI,
226 FROM_HERE,
227 base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady,
228 base::Unretained(this),
229 partition_path,
230 origin_url,
231 temp_path,
232 zip_path,
233 connection_count));
234 }
235
ForceCloseOriginOnIndexedDBThread(const base::FilePath & partition_path,const scoped_refptr<IndexedDBContextImpl> context,const GURL & origin_url)236 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
237 const base::FilePath& partition_path,
238 const scoped_refptr<IndexedDBContextImpl> context,
239 const GURL& origin_url) {
240 DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
241
242 // Make sure the database hasn't been deleted since the page was loaded.
243 if (!context->IsInOriginSet(origin_url))
244 return;
245
246 context->ForceClose(origin_url,
247 IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
248 size_t connection_count = context->GetConnectionCount(origin_url);
249
250 BrowserThread::PostTask(BrowserThread::UI,
251 FROM_HERE,
252 base::Bind(&IndexedDBInternalsUI::OnForcedClose,
253 base::Unretained(this),
254 partition_path,
255 origin_url,
256 connection_count));
257 }
258
OnForcedClose(const base::FilePath & partition_path,const GURL & origin_url,size_t connection_count)259 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
260 const GURL& origin_url,
261 size_t connection_count) {
262 web_ui()->CallJavascriptFunction(
263 "indexeddb.onForcedClose",
264 base::StringValue(partition_path.value()),
265 base::StringValue(origin_url.spec()),
266 base::FundamentalValue(static_cast<double>(connection_count)));
267 }
268
OnDownloadDataReady(const base::FilePath & partition_path,const GURL & origin_url,const base::FilePath temp_path,const base::FilePath zip_path,size_t connection_count)269 void IndexedDBInternalsUI::OnDownloadDataReady(
270 const base::FilePath& partition_path,
271 const GURL& origin_url,
272 const base::FilePath temp_path,
273 const base::FilePath zip_path,
274 size_t connection_count) {
275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
276 const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
277 BrowserContext* browser_context =
278 web_ui()->GetWebContents()->GetBrowserContext();
279 scoped_ptr<DownloadUrlParameters> dl_params(
280 DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url));
281 DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context);
282
283 const GURL referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
284 dl_params->set_referrer(
285 content::Referrer(referrer, blink::WebReferrerPolicyDefault));
286
287 // This is how to watch for the download to finish: first wait for it
288 // to start, then attach a DownloadItem::Observer to observe the
289 // state change to the finished state.
290 dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted,
291 base::Unretained(this),
292 partition_path,
293 origin_url,
294 temp_path,
295 connection_count));
296 dlm->DownloadUrl(dl_params.Pass());
297 }
298
299 // The entire purpose of this class is to delete the temp file after
300 // the download is complete.
301 class FileDeleter : public DownloadItem::Observer {
302 public:
FileDeleter(const base::FilePath & temp_dir)303 explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
304 virtual ~FileDeleter();
305
306 virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
OnDownloadOpened(DownloadItem * item)307 virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {}
OnDownloadRemoved(DownloadItem * item)308 virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {}
OnDownloadDestroyed(DownloadItem * item)309 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {}
310
311 private:
312 const base::FilePath temp_dir_;
313
314 DISALLOW_COPY_AND_ASSIGN(FileDeleter);
315 };
316
OnDownloadUpdated(DownloadItem * item)317 void FileDeleter::OnDownloadUpdated(DownloadItem* item) {
318 switch (item->GetState()) {
319 case DownloadItem::IN_PROGRESS:
320 break;
321 case DownloadItem::COMPLETE:
322 case DownloadItem::CANCELLED:
323 case DownloadItem::INTERRUPTED: {
324 item->RemoveObserver(this);
325 BrowserThread::DeleteOnFileThread::Destruct(this);
326 break;
327 }
328 default:
329 NOTREACHED();
330 }
331 }
332
~FileDeleter()333 FileDeleter::~FileDeleter() {
334 base::ScopedTempDir path;
335 bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
336 DCHECK(will_delete);
337 }
338
OnDownloadStarted(const base::FilePath & partition_path,const GURL & origin_url,const base::FilePath & temp_path,size_t connection_count,DownloadItem * item,DownloadInterruptReason interrupt_reason)339 void IndexedDBInternalsUI::OnDownloadStarted(
340 const base::FilePath& partition_path,
341 const GURL& origin_url,
342 const base::FilePath& temp_path,
343 size_t connection_count,
344 DownloadItem* item,
345 DownloadInterruptReason interrupt_reason) {
346
347 if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
348 LOG(ERROR) << "Error downloading database dump: "
349 << DownloadInterruptReasonToString(interrupt_reason);
350 return;
351 }
352
353 item->AddObserver(new FileDeleter(temp_path));
354 web_ui()->CallJavascriptFunction(
355 "indexeddb.onOriginDownloadReady",
356 base::StringValue(partition_path.value()),
357 base::StringValue(origin_url.spec()),
358 base::FundamentalValue(static_cast<double>(connection_count)));
359 }
360
361 } // namespace content
362