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