• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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"
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   IndexedDBContextImpl* context_impl =
91       static_cast<IndexedDBContextImpl*>(context.get());
92 
93   scoped_ptr<base::ListValue> info_list(context_impl->GetAllOriginsDetails());
94   bool is_incognito = context_impl->is_incognito();
95 
96   BrowserThread::PostTask(
97       BrowserThread::UI,
98       FROM_HERE,
99       base::Bind(&IndexedDBInternalsUI::OnOriginsReady,
100                  base::Unretained(this),
101                  base::Passed(&info_list),
102                  is_incognito ? base::FilePath() : context_path));
103 }
104 
OnOriginsReady(scoped_ptr<base::ListValue> origins,const base::FilePath & 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()));
110 }
111 
FindContext(const base::FilePath & partition_path,StoragePartition ** result_partition,scoped_refptr<IndexedDBContextImpl> * result_context,StoragePartition * storage_partition)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());
120   }
121 }
122 
GetOriginData(const base::ListValue * args,base::FilePath * partition_path,GURL * origin_url,scoped_refptr<IndexedDBContextImpl> * context)123 bool IndexedDBInternalsUI::GetOriginData(
124     const base::ListValue* args,
125     base::FilePath* partition_path,
126     GURL* origin_url,
127     scoped_refptr<IndexedDBContextImpl>* context) {
128   base::FilePath::StringType path_string;
129   if (!args->GetString(0, &path_string))
130     return false;
131   *partition_path = base::FilePath(path_string);
132 
133   std::string url_string;
134   if (!args->GetString(1, &url_string))
135     return false;
136 
137   *origin_url = GURL(url_string);
138 
139   return GetOriginContext(*partition_path, *origin_url, context);
140 }
141 
GetOriginContext(const base::FilePath & path,const GURL & origin_url,scoped_refptr<IndexedDBContextImpl> * 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();
149 
150   StoragePartition* result_partition;
151   BrowserContext::StoragePartitionCallback cb =
152       base::Bind(&FindContext, path, &result_partition, context);
153   BrowserContext::ForEachStoragePartition(browser_context, cb);
154 
155   if (!result_partition || !(context->get()))
156     return false;
157 
158   return true;
159 }
160 
DownloadOriginData(const base::ListValue * args)161 void IndexedDBInternalsUI::DownloadOriginData(const base::ListValue* args) {
162   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163 
164   base::FilePath partition_path;
165   GURL origin_url;
166   scoped_refptr<IndexedDBContextImpl> context;
167   if (!GetOriginData(args, &partition_path, &origin_url, &context))
168     return;
169 
170   DCHECK(context.get());
171   context->TaskRunner()->PostTask(
172       FROM_HERE,
173       base::Bind(&IndexedDBInternalsUI::DownloadOriginDataOnIndexedDBThread,
174                  base::Unretained(this),
175                  partition_path,
176                  context,
177                  origin_url));
178 }
179 
ForceCloseOrigin(const base::ListValue * args)180 void IndexedDBInternalsUI::ForceCloseOrigin(const base::ListValue* args) {
181   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
182 
183   base::FilePath partition_path;
184   GURL origin_url;
185   scoped_refptr<IndexedDBContextImpl> context;
186   if (!GetOriginData(args, &partition_path, &origin_url, &context))
187     return;
188 
189   context->TaskRunner()->PostTask(
190       FROM_HERE,
191       base::Bind(&IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread,
192                  base::Unretained(this),
193                  partition_path,
194                  context,
195                  origin_url));
196 }
197 
DownloadOriginDataOnIndexedDBThread(const base::FilePath & partition_path,const scoped_refptr<IndexedDBContextImpl> context,const GURL & origin_url)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());
203 
204   // Make sure the database hasn't been deleted since the page was loaded.
205   if (!context->IsInOriginSet(origin_url))
206     return;
207 
208   context->ForceClose(origin_url,
209                       IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
210   size_t connection_count = context->GetConnectionCount(origin_url);
211 
212   base::ScopedTempDir temp_dir;
213   if (!temp_dir.CreateUniqueTempDir())
214     return;
215 
216   // This will get cleaned up on the File thread after the download
217   // has completed.
218   base::FilePath temp_path = temp_dir.Take();
219 
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"));
223 
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);
228 
229   BrowserThread::PostTask(BrowserThread::UI,
230                           FROM_HERE,
231                           base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady,
232                                      base::Unretained(this),
233                                      partition_path,
234                                      origin_url,
235                                      temp_path,
236                                      zip_path,
237                                      connection_count));
238 }
239 
ForceCloseOriginOnIndexedDBThread(const base::FilePath & partition_path,const scoped_refptr<IndexedDBContextImpl> context,const GURL & origin_url)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());
245 
246   // Make sure the database hasn't been deleted since the page was loaded.
247   if (!context->IsInOriginSet(origin_url))
248     return;
249 
250   context->ForceClose(origin_url,
251                       IndexedDBContextImpl::FORCE_CLOSE_INTERNALS_PAGE);
252   size_t connection_count = context->GetConnectionCount(origin_url);
253 
254   BrowserThread::PostTask(BrowserThread::UI,
255                           FROM_HERE,
256                           base::Bind(&IndexedDBInternalsUI::OnForcedClose,
257                                      base::Unretained(this),
258                                      partition_path,
259                                      origin_url,
260                                      connection_count));
261 }
262 
OnForcedClose(const base::FilePath & partition_path,const GURL & origin_url,size_t connection_count)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)));
271 }
272 
OnDownloadDataReady(const base::FilePath & partition_path,const GURL & origin_url,const base::FilePath temp_path,const base::FilePath zip_path,size_t 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);
286 
287   const GURL referrer(web_ui()->GetWebContents()->GetLastCommittedURL());
288   dl_params->set_referrer(
289       content::Referrer(referrer, blink::WebReferrerPolicyDefault));
290 
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),
296                                      partition_path,
297                                      origin_url,
298                                      temp_path,
299                                      connection_count));
300   dlm->DownloadUrl(dl_params.Pass());
301 }
302 
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 {
306  public:
FileDeleter(const base::FilePath & temp_dir)307   explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
308   virtual ~FileDeleter();
309 
310   virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
OnDownloadOpened(DownloadItem * item)311   virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {}
OnDownloadRemoved(DownloadItem * item)312   virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {}
OnDownloadDestroyed(DownloadItem * item)313   virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {}
314 
315  private:
316   const base::FilePath temp_dir_;
317 
318   DISALLOW_COPY_AND_ASSIGN(FileDeleter);
319 };
320 
OnDownloadUpdated(DownloadItem * item)321 void FileDeleter::OnDownloadUpdated(DownloadItem* item) {
322   switch (item->GetState()) {
323     case DownloadItem::IN_PROGRESS:
324       break;
325     case DownloadItem::COMPLETE:
326     case DownloadItem::CANCELLED:
327     case DownloadItem::INTERRUPTED: {
328       item->RemoveObserver(this);
329       BrowserThread::DeleteOnFileThread::Destruct(this);
330       break;
331     }
332     default:
333       NOTREACHED();
334   }
335 }
336 
~FileDeleter()337 FileDeleter::~FileDeleter() {
338   base::ScopedTempDir path;
339   bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
340   DCHECK(will_delete);
341 }
342 
OnDownloadStarted(const base::FilePath & partition_path,const GURL & origin_url,const base::FilePath & temp_path,size_t connection_count,DownloadItem * item,DownloadInterruptReason interrupt_reason)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,
348     DownloadItem* item,
349     DownloadInterruptReason interrupt_reason) {
350 
351   if (interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
352     LOG(ERROR) << "Error downloading database dump: "
353                << DownloadInterruptReasonToString(interrupt_reason);
354     return;
355   }
356 
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)));
363 }
364 
365 }  // namespace content
366