Home | History | Annotate | Download | only in indexed_db
      1 // Copyright (c) 2012 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_context_impl.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/bind.h"
     10 #include "base/command_line.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_enumerator.h"
     13 #include "base/logging.h"
     14 #include "base/sequenced_task_runner.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/threading/thread_restrictions.h"
     18 #include "base/values.h"
     19 #include "content/browser/browser_main_loop.h"
     20 #include "content/browser/indexed_db/indexed_db_connection.h"
     21 #include "content/browser/indexed_db/indexed_db_database.h"
     22 #include "content/browser/indexed_db/indexed_db_factory.h"
     23 #include "content/browser/indexed_db/indexed_db_quota_client.h"
     24 #include "content/browser/indexed_db/indexed_db_transaction.h"
     25 #include "content/public/browser/browser_thread.h"
     26 #include "content/public/browser/indexed_db_info.h"
     27 #include "content/public/common/content_switches.h"
     28 #include "ui/base/text/bytes_formatting.h"
     29 #include "webkit/browser/database/database_util.h"
     30 #include "webkit/browser/quota/quota_manager.h"
     31 #include "webkit/browser/quota/special_storage_policy.h"
     32 #include "webkit/common/database/database_identifier.h"
     33 
     34 using base::DictionaryValue;
     35 using base::ListValue;
     36 using webkit_database::DatabaseUtil;
     37 
     38 namespace content {
     39 const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
     40     FILE_PATH_LITERAL("IndexedDB");
     41 
     42 static const base::FilePath::CharType kIndexedDBExtension[] =
     43     FILE_PATH_LITERAL(".indexeddb");
     44 
     45 static const base::FilePath::CharType kLevelDBExtension[] =
     46     FILE_PATH_LITERAL(".leveldb");
     47 
     48 namespace {
     49 
     50 // This may be called after the IndexedDBContext is destroyed.
     51 void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path,
     52                            std::vector<GURL>* origins,
     53                            std::vector<base::FilePath>* file_paths) {
     54   // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
     55   // if a global handle to it is ever available.
     56   if (indexeddb_path.empty())
     57     return;
     58   base::FileEnumerator file_enumerator(
     59       indexeddb_path, false, base::FileEnumerator::DIRECTORIES);
     60   for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
     61        file_path = file_enumerator.Next()) {
     62     if (file_path.Extension() == kLevelDBExtension &&
     63         file_path.RemoveExtension().Extension() == kIndexedDBExtension) {
     64       std::string origin_id = file_path.BaseName().RemoveExtension()
     65           .RemoveExtension().MaybeAsASCII();
     66       origins->push_back(webkit_database::GetOriginFromIdentifier(origin_id));
     67       if (file_paths)
     68         file_paths->push_back(file_path);
     69     }
     70   }
     71 }
     72 
     73 // This will be called after the IndexedDBContext is destroyed.
     74 void ClearSessionOnlyOrigins(
     75     const base::FilePath& indexeddb_path,
     76     scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
     77   // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
     78   // if a global handle to it is ever available.
     79   std::vector<GURL> origins;
     80   std::vector<base::FilePath> file_paths;
     81   GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths);
     82   DCHECK_EQ(origins.size(), file_paths.size());
     83   std::vector<base::FilePath>::const_iterator file_path_iter =
     84       file_paths.begin();
     85   for (std::vector<GURL>::const_iterator iter = origins.begin();
     86        iter != origins.end();
     87        ++iter, ++file_path_iter) {
     88     if (!special_storage_policy->IsStorageSessionOnly(*iter))
     89       continue;
     90     if (special_storage_policy->IsStorageProtected(*iter))
     91       continue;
     92     base::DeleteFile(*file_path_iter, true);
     93   }
     94 }
     95 
     96 }  // namespace
     97 
     98 IndexedDBContextImpl::IndexedDBContextImpl(
     99     const base::FilePath& data_path,
    100     quota::SpecialStoragePolicy* special_storage_policy,
    101     quota::QuotaManagerProxy* quota_manager_proxy,
    102     base::SequencedTaskRunner* task_runner)
    103     : force_keep_session_state_(false),
    104       special_storage_policy_(special_storage_policy),
    105       quota_manager_proxy_(quota_manager_proxy),
    106       task_runner_(task_runner) {
    107   if (!data_path.empty())
    108     data_path_ = data_path.Append(kIndexedDBDirectory);
    109   if (quota_manager_proxy) {
    110     quota_manager_proxy->RegisterClient(new IndexedDBQuotaClient(this));
    111   }
    112 }
    113 
    114 IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
    115   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    116   if (!factory_) {
    117     // Prime our cache of origins with existing databases so we can
    118     // detect when dbs are newly created.
    119     GetOriginSet();
    120     factory_ = new IndexedDBFactory();
    121   }
    122   return factory_;
    123 }
    124 
    125 std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
    126   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    127   std::vector<GURL> origins;
    128   std::set<GURL>* origins_set = GetOriginSet();
    129   for (std::set<GURL>::const_iterator iter = origins_set->begin();
    130        iter != origins_set->end();
    131        ++iter) {
    132     origins.push_back(*iter);
    133   }
    134   return origins;
    135 }
    136 
    137 std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() {
    138   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    139   std::vector<GURL> origins = GetAllOrigins();
    140   std::vector<IndexedDBInfo> result;
    141   for (std::vector<GURL>::const_iterator iter = origins.begin();
    142        iter != origins.end();
    143        ++iter) {
    144     const GURL& origin_url = *iter;
    145 
    146     base::FilePath idb_directory = GetFilePath(origin_url);
    147     size_t connection_count = GetConnectionCount(origin_url);
    148     result.push_back(IndexedDBInfo(origin_url,
    149                                    GetOriginDiskUsage(origin_url),
    150                                    GetOriginLastModified(origin_url),
    151                                    idb_directory,
    152                                    connection_count));
    153   }
    154   return result;
    155 }
    156 
    157 static bool HostNameComparator(const GURL& i, const GURL& j) {
    158   return i.host() < j.host();
    159 }
    160 
    161 ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
    162   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    163   std::vector<GURL> origins = GetAllOrigins();
    164 
    165   std::sort(origins.begin(), origins.end(), HostNameComparator);
    166 
    167   scoped_ptr<ListValue> list(new ListValue());
    168   for (std::vector<GURL>::const_iterator iter = origins.begin();
    169        iter != origins.end();
    170        ++iter) {
    171     const GURL& origin_url = *iter;
    172 
    173     scoped_ptr<DictionaryValue> info(new DictionaryValue());
    174     info->SetString("url", origin_url.spec());
    175     info->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url)));
    176     info->SetDouble("last_modified",
    177                     GetOriginLastModified(origin_url).ToJsTime());
    178     info->SetString("path", GetFilePath(origin_url).value());
    179     info->SetDouble("connection_count", GetConnectionCount(origin_url));
    180 
    181     // This ends up being O(n^2) since we iterate over all open databases
    182     // to extract just those in the origin, and we're iterating over all
    183     // origins in the outer loop.
    184 
    185     if (factory_) {
    186       std::vector<IndexedDBDatabase*> databases =
    187           factory_->GetOpenDatabasesForOrigin(
    188               webkit_database::GetIdentifierFromOrigin(origin_url));
    189       // TODO(jsbell): Sort by name?
    190       scoped_ptr<ListValue> database_list(new ListValue());
    191 
    192       for (std::vector<IndexedDBDatabase*>::iterator it = databases.begin();
    193            it != databases.end();
    194            ++it) {
    195 
    196         const IndexedDBDatabase* db = *it;
    197         scoped_ptr<DictionaryValue> db_info(new DictionaryValue());
    198 
    199         db_info->SetString("name", db->name());
    200         db_info->SetDouble("pending_opens", db->PendingOpenCount());
    201         db_info->SetDouble("pending_upgrades", db->PendingUpgradeCount());
    202         db_info->SetDouble("running_upgrades", db->RunningUpgradeCount());
    203         db_info->SetDouble("pending_deletes", db->PendingDeleteCount());
    204         db_info->SetDouble("connection_count",
    205                            db->ConnectionCount() - db->PendingUpgradeCount() -
    206                                db->RunningUpgradeCount());
    207 
    208         scoped_ptr<ListValue> transaction_list(new ListValue());
    209         std::vector<const IndexedDBTransaction*> transactions =
    210             db->transaction_coordinator().GetTransactions();
    211         for (std::vector<const IndexedDBTransaction*>::iterator trans_it =
    212                  transactions.begin();
    213              trans_it != transactions.end();
    214              ++trans_it) {
    215 
    216           const IndexedDBTransaction* transaction = *trans_it;
    217           scoped_ptr<DictionaryValue> transaction_info(new DictionaryValue());
    218 
    219           const char* kModes[] = { "readonly", "readwrite", "versionchange" };
    220           transaction_info->SetString("mode", kModes[transaction->mode()]);
    221           transaction_info->SetBoolean("running", transaction->IsRunning());
    222 
    223           scoped_ptr<ListValue> scope(new ListValue());
    224           for (std::set<int64>::const_iterator scope_it =
    225                    transaction->scope().begin();
    226                scope_it != transaction->scope().end();
    227                ++scope_it) {
    228             IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it =
    229                 db->metadata().object_stores.find(*scope_it);
    230             if (it != db->metadata().object_stores.end())
    231               scope->AppendString(it->second.name);
    232           }
    233 
    234           transaction_info->Set("scope", scope.release());
    235           transaction_list->Append(transaction_info.release());
    236         }
    237         db_info->Set("transactions", transaction_list.release());
    238 
    239         database_list->Append(db_info.release());
    240       }
    241       info->Set("databases", database_list.release());
    242     }
    243 
    244     list->Append(info.release());
    245   }
    246   return list.release();
    247 }
    248 
    249 int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
    250   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    251   if (data_path_.empty() || !IsInOriginSet(origin_url))
    252     return 0;
    253   EnsureDiskUsageCacheInitialized(origin_url);
    254   return origin_size_map_[origin_url];
    255 }
    256 
    257 base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
    258   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    259   if (data_path_.empty() || !IsInOriginSet(origin_url))
    260     return base::Time();
    261   base::FilePath idb_directory = GetFilePath(origin_url);
    262   base::PlatformFileInfo file_info;
    263   if (!file_util::GetFileInfo(idb_directory, &file_info))
    264     return base::Time();
    265   return file_info.last_modified;
    266 }
    267 
    268 void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
    269   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    270   ForceClose(origin_url);
    271   if (data_path_.empty() || !IsInOriginSet(origin_url))
    272     return;
    273 
    274   base::FilePath idb_directory = GetFilePath(origin_url);
    275   EnsureDiskUsageCacheInitialized(origin_url);
    276   const bool recursive = true;
    277   bool deleted = base::DeleteFile(idb_directory, recursive);
    278 
    279   QueryDiskAndUpdateQuotaUsage(origin_url);
    280   if (deleted) {
    281     RemoveFromOriginSet(origin_url);
    282     origin_size_map_.erase(origin_url);
    283     space_available_map_.erase(origin_url);
    284   }
    285 }
    286 
    287 void IndexedDBContextImpl::ForceClose(const GURL& origin_url) {
    288   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    289   if (data_path_.empty() || !IsInOriginSet(origin_url))
    290     return;
    291 
    292   if (connections_.find(origin_url) != connections_.end()) {
    293     ConnectionSet& connections = connections_[origin_url];
    294     ConnectionSet::iterator it = connections.begin();
    295     while (it != connections.end()) {
    296       // Remove before closing so callbacks don't double-erase
    297       IndexedDBConnection* connection = *it;
    298       connections.erase(it++);
    299       connection->ForceClose();
    300     }
    301     DCHECK_EQ(connections_[origin_url].size(), 0UL);
    302     connections_.erase(origin_url);
    303   }
    304 }
    305 
    306 size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
    307   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    308   if (data_path_.empty() || !IsInOriginSet(origin_url))
    309     return 0;
    310 
    311   if (connections_.find(origin_url) == connections_.end())
    312     return 0;
    313 
    314   return connections_[origin_url].size();
    315 }
    316 
    317 base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) {
    318   std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
    319   return GetIndexedDBFilePath(origin_id);
    320 }
    321 
    322 base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
    323     const std::string& origin_id) const {
    324   return GetIndexedDBFilePath(origin_id);
    325 }
    326 
    327 void IndexedDBContextImpl::SetTaskRunnerForTesting(
    328     base::SequencedTaskRunner* task_runner) {
    329   DCHECK(!task_runner_);
    330   task_runner_ = task_runner;
    331 }
    332 
    333 void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
    334                                             IndexedDBConnection* connection) {
    335   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    336   DCHECK_EQ(connections_[origin_url].count(connection), 0UL);
    337   if (quota_manager_proxy()) {
    338     quota_manager_proxy()->NotifyStorageAccessed(
    339         quota::QuotaClient::kIndexedDatabase,
    340         origin_url,
    341         quota::kStorageTypeTemporary);
    342   }
    343   connections_[origin_url].insert(connection);
    344   if (AddToOriginSet(origin_url)) {
    345     // A newly created db, notify the quota system.
    346     QueryDiskAndUpdateQuotaUsage(origin_url);
    347   } else {
    348     EnsureDiskUsageCacheInitialized(origin_url);
    349   }
    350   QueryAvailableQuota(origin_url);
    351 }
    352 
    353 void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
    354                                             IndexedDBConnection* connection) {
    355   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    356   // May not be in the map if connection was forced to close
    357   if (connections_.find(origin_url) == connections_.end() ||
    358       connections_[origin_url].count(connection) != 1)
    359     return;
    360   if (quota_manager_proxy()) {
    361     quota_manager_proxy()->NotifyStorageAccessed(
    362         quota::QuotaClient::kIndexedDatabase,
    363         origin_url,
    364         quota::kStorageTypeTemporary);
    365   }
    366   connections_[origin_url].erase(connection);
    367   if (connections_[origin_url].size() == 0) {
    368     QueryDiskAndUpdateQuotaUsage(origin_url);
    369     connections_.erase(origin_url);
    370   }
    371 }
    372 
    373 void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
    374   DCHECK(connections_.find(origin_url) != connections_.end() &&
    375          connections_[origin_url].size() > 0);
    376   QueryDiskAndUpdateQuotaUsage(origin_url);
    377   QueryAvailableQuota(origin_url);
    378 }
    379 
    380 bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
    381                                             int64 additional_bytes) {
    382   if (space_available_map_.find(origin_url) == space_available_map_.end()) {
    383     // We haven't heard back from the QuotaManager yet, just let it through.
    384     return false;
    385   }
    386   bool over_quota = additional_bytes > space_available_map_[origin_url];
    387   return over_quota;
    388 }
    389 
    390 bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
    391   const int kOneAdditionalByte = 1;
    392   return WouldBeOverQuota(origin_url, kOneAdditionalByte);
    393 }
    394 
    395 quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
    396   return quota_manager_proxy_;
    397 }
    398 
    399 IndexedDBContextImpl::~IndexedDBContextImpl() {
    400   if (factory_) {
    401     IndexedDBFactory* factory = factory_;
    402     factory->AddRef();
    403     factory_ = NULL;
    404     if (!task_runner_->ReleaseSoon(FROM_HERE, factory)) {
    405       factory->Release();
    406     }
    407   }
    408 
    409   if (data_path_.empty())
    410     return;
    411 
    412   if (force_keep_session_state_)
    413     return;
    414 
    415   bool has_session_only_databases =
    416       special_storage_policy_ &&
    417       special_storage_policy_->HasSessionOnlyOrigins();
    418 
    419   // Clearning only session-only databases, and there are none.
    420   if (!has_session_only_databases)
    421     return;
    422 
    423   TaskRunner()->PostTask(
    424       FROM_HERE,
    425       base::Bind(
    426           &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
    427 }
    428 
    429 base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
    430     const std::string& origin_id) const {
    431   DCHECK(!data_path_.empty());
    432   return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension)
    433       .AddExtension(kLevelDBExtension);
    434 }
    435 
    436 int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
    437   if (data_path_.empty())
    438     return 0;
    439   std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
    440   base::FilePath file_path = GetIndexedDBFilePath(origin_id);
    441   return base::ComputeDirectorySize(file_path);
    442 }
    443 
    444 void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
    445     const GURL& origin_url) {
    446   if (origin_size_map_.find(origin_url) == origin_size_map_.end())
    447     origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
    448 }
    449 
    450 void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
    451     const GURL& origin_url) {
    452   int64 former_disk_usage = origin_size_map_[origin_url];
    453   int64 current_disk_usage = ReadUsageFromDisk(origin_url);
    454   int64 difference = current_disk_usage - former_disk_usage;
    455   if (difference) {
    456     origin_size_map_[origin_url] = current_disk_usage;
    457     // quota_manager_proxy() is NULL in unit tests.
    458     if (quota_manager_proxy()) {
    459       quota_manager_proxy()->NotifyStorageModified(
    460           quota::QuotaClient::kIndexedDatabase,
    461           origin_url,
    462           quota::kStorageTypeTemporary,
    463           difference);
    464     }
    465   }
    466 }
    467 
    468 void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
    469                                             quota::QuotaStatusCode status,
    470                                             int64 usage,
    471                                             int64 quota) {
    472   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    473   DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort)
    474       << "status was " << status;
    475   if (status == quota::kQuotaErrorAbort) {
    476     // We seem to no longer care to wait around for the answer.
    477     return;
    478   }
    479   TaskRunner()->PostTask(FROM_HERE,
    480                          base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
    481                                     this,
    482                                     origin_url,
    483                                     usage,
    484                                     quota));
    485 }
    486 
    487 void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
    488                                            int64 usage,
    489                                            int64 quota) {
    490   DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    491   space_available_map_[origin_url] = quota - usage;
    492 }
    493 
    494 void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
    495   if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    496     DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
    497     if (quota_manager_proxy()) {
    498       BrowserThread::PostTask(
    499           BrowserThread::IO,
    500           FROM_HERE,
    501           base::Bind(
    502               &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
    503     }
    504     return;
    505   }
    506   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    507   if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
    508     return;
    509   quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
    510       origin_url,
    511       quota::kStorageTypeTemporary,
    512       base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
    513 }
    514 
    515 std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
    516   if (!origin_set_) {
    517     origin_set_.reset(new std::set<GURL>);
    518     std::vector<GURL> origins;
    519     GetAllOriginsAndPaths(data_path_, &origins, NULL);
    520     for (std::vector<GURL>::const_iterator iter = origins.begin();
    521          iter != origins.end();
    522          ++iter) {
    523       origin_set_->insert(*iter);
    524     }
    525   }
    526   return origin_set_.get();
    527 }
    528 
    529 void IndexedDBContextImpl::ResetCaches() {
    530   origin_set_.reset();
    531   origin_size_map_.clear();
    532   space_available_map_.clear();
    533 }
    534 
    535 base::TaskRunner* IndexedDBContextImpl::TaskRunner() const {
    536   return task_runner_;
    537 }
    538 
    539 }  // namespace content
    540