Home | History | Annotate | Download | only in indexed_db
      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 
     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 
     60 IndexedDBInternalsUI::~IndexedDBInternalsUI() {}
     61 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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:
    307   explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
    308   virtual ~FileDeleter();
    309 
    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 {}
    314 
    315  private:
    316   const base::FilePath temp_dir_;
    317 
    318   DISALLOW_COPY_AND_ASSIGN(FileDeleter);
    319 };
    320 
    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 
    337 FileDeleter::~FileDeleter() {
    338   base::ScopedTempDir path;
    339   bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
    340   DCHECK(will_delete);
    341 }
    342 
    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