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