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/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 
     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   scoped_ptr<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 
    101 void IndexedDBInternalsUI::OnOriginsReady(scoped_ptr<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 
    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 
    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 
    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 
    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 
    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 
    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   size_t connection_count = context->GetConnectionCount(origin_url);
    206 
    207   base::ScopedTempDir temp_dir;
    208   if (!temp_dir.CreateUniqueTempDir())
    209     return;
    210 
    211   // This will get cleaned up on the File thread after the download
    212   // has completed.
    213   base::FilePath temp_path = temp_dir.Take();
    214 
    215   std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
    216   base::FilePath zip_path =
    217       temp_path.AppendASCII(origin_id).AddExtension(FILE_PATH_LITERAL("zip"));
    218 
    219   // This happens on the "webkit" thread (which is really just the IndexedDB
    220   // thread) as a simple way to avoid another script reopening the origin
    221   // while we are zipping.
    222   zip::Zip(context->GetFilePath(origin_url), zip_path, true);
    223 
    224   BrowserThread::PostTask(BrowserThread::UI,
    225                           FROM_HERE,
    226                           base::Bind(&IndexedDBInternalsUI::OnDownloadDataReady,
    227                                      base::Unretained(this),
    228                                      partition_path,
    229                                      origin_url,
    230                                      temp_path,
    231                                      zip_path,
    232                                      connection_count));
    233 }
    234 
    235 void IndexedDBInternalsUI::ForceCloseOriginOnIndexedDBThread(
    236     const base::FilePath& partition_path,
    237     const scoped_refptr<IndexedDBContextImpl> context,
    238     const GURL& origin_url) {
    239   DCHECK(context->TaskRunner()->RunsTasksOnCurrentThread());
    240 
    241   // Make sure the database hasn't been deleted since the page was loaded.
    242   if (!context->IsInOriginSet(origin_url))
    243     return;
    244 
    245   context->ForceClose(origin_url);
    246   size_t connection_count = context->GetConnectionCount(origin_url);
    247 
    248   BrowserThread::PostTask(BrowserThread::UI,
    249                           FROM_HERE,
    250                           base::Bind(&IndexedDBInternalsUI::OnForcedClose,
    251                                      base::Unretained(this),
    252                                      partition_path,
    253                                      origin_url,
    254                                      connection_count));
    255 }
    256 
    257 void IndexedDBInternalsUI::OnForcedClose(const base::FilePath& partition_path,
    258                                          const GURL& origin_url,
    259                                          size_t connection_count) {
    260 
    261   scoped_refptr<IndexedDBContextImpl> context;
    262 
    263   web_ui()->CallJavascriptFunction(
    264       "indexeddb.onForcedClose",
    265       base::StringValue(partition_path.value()),
    266       base::StringValue(origin_url.spec()),
    267       base::FundamentalValue(double(connection_count)));
    268 }
    269 
    270 void IndexedDBInternalsUI::OnDownloadDataReady(
    271     const base::FilePath& partition_path,
    272     const GURL& origin_url,
    273     const base::FilePath temp_path,
    274     const base::FilePath zip_path,
    275     size_t connection_count) {
    276   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    277   const GURL url = GURL(FILE_PATH_LITERAL("file://") + zip_path.value());
    278   BrowserContext* browser_context =
    279       web_ui()->GetWebContents()->GetBrowserContext();
    280   scoped_ptr<DownloadUrlParameters> dl_params(
    281       DownloadUrlParameters::FromWebContents(web_ui()->GetWebContents(), url));
    282   DownloadManager* dlm = BrowserContext::GetDownloadManager(browser_context);
    283 
    284   const GURL referrer(web_ui()->GetWebContents()->GetURL());
    285   dl_params->set_referrer(
    286       content::Referrer(referrer, WebKit::WebReferrerPolicyDefault));
    287 
    288   // This is how to watch for the download to finish: first wait for it
    289   // to start, then attach a DownloadItem::Observer to observe the
    290   // state change to the finished state.
    291   dl_params->set_callback(base::Bind(&IndexedDBInternalsUI::OnDownloadStarted,
    292                                      base::Unretained(this),
    293                                      partition_path,
    294                                      origin_url,
    295                                      temp_path,
    296                                      connection_count));
    297   dlm->DownloadUrl(dl_params.Pass());
    298 }
    299 
    300 // The entire purpose of this class is to delete the temp file after
    301 // the download is complete.
    302 class FileDeleter : public DownloadItem::Observer {
    303  public:
    304   explicit FileDeleter(const base::FilePath& temp_dir) : temp_dir_(temp_dir) {}
    305   virtual ~FileDeleter();
    306 
    307   virtual void OnDownloadUpdated(DownloadItem* download) OVERRIDE;
    308   virtual void OnDownloadOpened(DownloadItem* item) OVERRIDE {}
    309   virtual void OnDownloadRemoved(DownloadItem* item) OVERRIDE {}
    310   virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE {}
    311 
    312  private:
    313   const base::FilePath temp_dir_;
    314 };
    315 
    316 void FileDeleter::OnDownloadUpdated(DownloadItem* item) {
    317   switch (item->GetState()) {
    318     case DownloadItem::IN_PROGRESS:
    319       break;
    320     case DownloadItem::COMPLETE:
    321     case DownloadItem::CANCELLED:
    322     case DownloadItem::INTERRUPTED: {
    323       item->RemoveObserver(this);
    324       BrowserThread::DeleteOnFileThread::Destruct(this);
    325       break;
    326     }
    327     default:
    328       NOTREACHED();
    329   }
    330 }
    331 
    332 FileDeleter::~FileDeleter() {
    333   base::ScopedTempDir path;
    334   bool will_delete ALLOW_UNUSED = path.Set(temp_dir_);
    335   DCHECK(will_delete);
    336 }
    337 
    338 void IndexedDBInternalsUI::OnDownloadStarted(
    339     const base::FilePath& partition_path,
    340     const GURL& origin_url,
    341     const base::FilePath& temp_path,
    342     size_t connection_count,
    343     DownloadItem* item,
    344     net::Error error) {
    345 
    346   if (error != net::OK) {
    347     LOG(ERROR)
    348         << "Error downloading database dump: " << net::ErrorToString(error);
    349     return;
    350   }
    351 
    352   item->AddObserver(new FileDeleter(temp_path));
    353   web_ui()->CallJavascriptFunction(
    354       "indexeddb.onOriginDownloadReady",
    355       base::StringValue(partition_path.value()),
    356       base::StringValue(origin_url.spec()),
    357       base::FundamentalValue(double(connection_count)));
    358 }
    359 
    360 }  // namespace content
    361