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