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 "webkit/browser/appcache/appcache_storage_impl.h" 6 7 #include <algorithm> 8 #include <functional> 9 #include <set> 10 #include <vector> 11 12 #include "base/bind.h" 13 #include "base/bind_helpers.h" 14 #include "base/file_util.h" 15 #include "base/logging.h" 16 #include "base/message_loop/message_loop.h" 17 #include "base/stl_util.h" 18 #include "base/strings/string_util.h" 19 #include "net/base/cache_type.h" 20 #include "net/base/net_errors.h" 21 #include "sql/connection.h" 22 #include "sql/transaction.h" 23 #include "webkit/browser/appcache/appcache.h" 24 #include "webkit/browser/appcache/appcache_database.h" 25 #include "webkit/browser/appcache/appcache_entry.h" 26 #include "webkit/browser/appcache/appcache_group.h" 27 #include "webkit/browser/appcache/appcache_histograms.h" 28 #include "webkit/browser/appcache/appcache_quota_client.h" 29 #include "webkit/browser/appcache/appcache_response.h" 30 #include "webkit/browser/appcache/appcache_service.h" 31 #include "webkit/browser/quota/quota_client.h" 32 #include "webkit/browser/quota/quota_manager.h" 33 #include "webkit/browser/quota/special_storage_policy.h" 34 35 namespace appcache { 36 37 // Hard coded default when not using quota management. 38 static const int kDefaultQuota = 5 * 1024 * 1024; 39 40 static const int kMaxDiskCacheSize = 250 * 1024 * 1024; 41 static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024; 42 static const base::FilePath::CharType kDiskCacheDirectoryName[] = 43 FILE_PATH_LITERAL("Cache"); 44 45 namespace { 46 47 // Helpers for clearing data from the AppCacheDatabase. 48 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database, 49 int64 group_id, 50 std::vector<int64>* deletable_response_ids) { 51 AppCacheDatabase::CacheRecord cache_record; 52 bool success = false; 53 if (database->FindCacheForGroup(group_id, &cache_record)) { 54 database->FindResponseIdsForCacheAsVector(cache_record.cache_id, 55 deletable_response_ids); 56 success = 57 database->DeleteGroup(group_id) && 58 database->DeleteCache(cache_record.cache_id) && 59 database->DeleteEntriesForCache(cache_record.cache_id) && 60 database->DeleteNamespacesForCache(cache_record.cache_id) && 61 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) && 62 database->InsertDeletableResponseIds(*deletable_response_ids); 63 } else { 64 NOTREACHED() << "A existing group without a cache is unexpected"; 65 success = database->DeleteGroup(group_id); 66 } 67 return success; 68 } 69 70 // Destroys |database|. If there is appcache data to be deleted 71 // (|force_keep_session_state| is false), deletes session-only appcache data. 72 void ClearSessionOnlyOrigins( 73 AppCacheDatabase* database, 74 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy, 75 bool force_keep_session_state) { 76 scoped_ptr<AppCacheDatabase> database_to_delete(database); 77 78 // If saving session state, only delete the database. 79 if (force_keep_session_state) 80 return; 81 82 bool has_session_only_appcaches = 83 special_storage_policy.get() && 84 special_storage_policy->HasSessionOnlyOrigins(); 85 86 // Clearning only session-only databases, and there are none. 87 if (!has_session_only_appcaches) 88 return; 89 90 std::set<GURL> origins; 91 database->FindOriginsWithGroups(&origins); 92 if (origins.empty()) 93 return; // nothing to delete 94 95 sql::Connection* connection = database->db_connection(); 96 if (!connection) { 97 NOTREACHED() << "Missing database connection."; 98 return; 99 } 100 101 std::set<GURL>::const_iterator origin; 102 for (origin = origins.begin(); origin != origins.end(); ++origin) { 103 if (!special_storage_policy->IsStorageSessionOnly(*origin)) 104 continue; 105 if (special_storage_policy.get() && 106 special_storage_policy->IsStorageProtected(*origin)) 107 continue; 108 109 std::vector<AppCacheDatabase::GroupRecord> groups; 110 database->FindGroupsForOrigin(*origin, &groups); 111 std::vector<AppCacheDatabase::GroupRecord>::const_iterator group; 112 for (group = groups.begin(); group != groups.end(); ++group) { 113 sql::Transaction transaction(connection); 114 if (!transaction.Begin()) { 115 NOTREACHED() << "Failed to start transaction"; 116 return; 117 } 118 std::vector<int64> deletable_response_ids; 119 bool success = DeleteGroupAndRelatedRecords(database, 120 group->group_id, 121 &deletable_response_ids); 122 success = success && transaction.Commit(); 123 DCHECK(success); 124 } // for each group 125 } // for each origin 126 } 127 128 } // namespace 129 130 // DatabaseTask ----------------------------------------- 131 132 class AppCacheStorageImpl::DatabaseTask 133 : public base::RefCountedThreadSafe<DatabaseTask> { 134 public: 135 explicit DatabaseTask(AppCacheStorageImpl* storage) 136 : storage_(storage), database_(storage->database_), 137 io_thread_(base::MessageLoopProxy::current()) { 138 DCHECK(io_thread_.get()); 139 } 140 141 void AddDelegate(DelegateReference* delegate_reference) { 142 delegates_.push_back(make_scoped_refptr(delegate_reference)); 143 } 144 145 // Schedules a task to be Run() on the DB thread. Tasks 146 // are run in the order in which they are scheduled. 147 void Schedule(); 148 149 // Called on the DB thread. 150 virtual void Run() = 0; 151 152 // Called on the IO thread after Run() has completed. 153 virtual void RunCompleted() {} 154 155 // Once scheduled a task cannot be cancelled, but the 156 // call to RunCompleted may be. This method should only be 157 // called on the IO thread. This is used by AppCacheStorageImpl 158 // to cancel the completion calls when AppCacheStorageImpl is 159 // destructed. This method may be overriden to release or delete 160 // additional data associated with the task that is not DB thread 161 // safe. If overriden, this base class method must be called from 162 // within the override. 163 virtual void CancelCompletion(); 164 165 protected: 166 friend class base::RefCountedThreadSafe<DatabaseTask>; 167 virtual ~DatabaseTask() {} 168 169 AppCacheStorageImpl* storage_; 170 AppCacheDatabase* database_; 171 DelegateReferenceVector delegates_; 172 173 private: 174 void CallRun(base::TimeTicks schedule_time); 175 void CallRunCompleted(base::TimeTicks schedule_time); 176 void CallDisableStorage(); 177 178 scoped_refptr<base::MessageLoopProxy> io_thread_; 179 }; 180 181 void AppCacheStorageImpl::DatabaseTask::Schedule() { 182 DCHECK(storage_); 183 DCHECK(io_thread_->BelongsToCurrentThread()); 184 if (storage_->db_thread_->PostTask( 185 FROM_HERE, 186 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) { 187 storage_->scheduled_database_tasks_.push_back(this); 188 } else { 189 NOTREACHED() << "Thread for database tasks is not running. " 190 << "This is not always the DB thread."; 191 } 192 } 193 194 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() { 195 DCHECK(io_thread_->BelongsToCurrentThread()); 196 delegates_.clear(); 197 storage_ = NULL; 198 } 199 200 void AppCacheStorageImpl::DatabaseTask::CallRun( 201 base::TimeTicks schedule_time) { 202 AppCacheHistograms::AddTaskQueueTimeSample( 203 base::TimeTicks::Now() - schedule_time); 204 if (!database_->is_disabled()) { 205 base::TimeTicks run_time = base::TimeTicks::Now(); 206 Run(); 207 AppCacheHistograms::AddTaskRunTimeSample( 208 base::TimeTicks::Now() - run_time); 209 if (database_->is_disabled()) { 210 io_thread_->PostTask( 211 FROM_HERE, 212 base::Bind(&DatabaseTask::CallDisableStorage, this)); 213 } 214 } 215 io_thread_->PostTask( 216 FROM_HERE, 217 base::Bind(&DatabaseTask::CallRunCompleted, this, 218 base::TimeTicks::Now())); 219 } 220 221 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted( 222 base::TimeTicks schedule_time) { 223 AppCacheHistograms::AddCompletionQueueTimeSample( 224 base::TimeTicks::Now() - schedule_time); 225 if (storage_) { 226 DCHECK(io_thread_->BelongsToCurrentThread()); 227 DCHECK(storage_->scheduled_database_tasks_.front() == this); 228 storage_->scheduled_database_tasks_.pop_front(); 229 base::TimeTicks run_time = base::TimeTicks::Now(); 230 RunCompleted(); 231 AppCacheHistograms::AddCompletionRunTimeSample( 232 base::TimeTicks::Now() - run_time); 233 delegates_.clear(); 234 } 235 } 236 237 void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() { 238 if (storage_) { 239 DCHECK(io_thread_->BelongsToCurrentThread()); 240 storage_->Disable(); 241 } 242 } 243 244 // InitTask ------- 245 246 class AppCacheStorageImpl::InitTask : public DatabaseTask { 247 public: 248 explicit InitTask(AppCacheStorageImpl* storage) 249 : DatabaseTask(storage), last_group_id_(0), 250 last_cache_id_(0), last_response_id_(0), 251 last_deletable_response_rowid_(0) {} 252 253 // DatabaseTask: 254 virtual void Run() OVERRIDE; 255 virtual void RunCompleted() OVERRIDE; 256 257 protected: 258 virtual ~InitTask() {} 259 260 private: 261 int64 last_group_id_; 262 int64 last_cache_id_; 263 int64 last_response_id_; 264 int64 last_deletable_response_rowid_; 265 std::map<GURL, int64> usage_map_; 266 }; 267 268 void AppCacheStorageImpl::InitTask::Run() { 269 database_->FindLastStorageIds( 270 &last_group_id_, &last_cache_id_, &last_response_id_, 271 &last_deletable_response_rowid_); 272 database_->GetAllOriginUsage(&usage_map_); 273 } 274 275 void AppCacheStorageImpl::InitTask::RunCompleted() { 276 storage_->last_group_id_ = last_group_id_; 277 storage_->last_cache_id_ = last_cache_id_; 278 storage_->last_response_id_ = last_response_id_; 279 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_; 280 281 if (!storage_->is_disabled()) { 282 storage_->usage_map_.swap(usage_map_); 283 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5); 284 base::MessageLoop::current()->PostDelayedTask( 285 FROM_HERE, 286 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses, 287 storage_->weak_factory_.GetWeakPtr()), 288 kDelay); 289 } 290 291 if (storage_->service()->quota_client()) 292 storage_->service()->quota_client()->NotifyAppCacheReady(); 293 } 294 295 // CloseConnectionTask ------- 296 297 class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask { 298 public: 299 explicit CloseConnectionTask(AppCacheStorageImpl* storage) 300 : DatabaseTask(storage) {} 301 302 // DatabaseTask: 303 virtual void Run() OVERRIDE { database_->CloseConnection(); } 304 305 protected: 306 virtual ~CloseConnectionTask() {} 307 }; 308 309 // DisableDatabaseTask ------- 310 311 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask { 312 public: 313 explicit DisableDatabaseTask(AppCacheStorageImpl* storage) 314 : DatabaseTask(storage) {} 315 316 // DatabaseTask: 317 virtual void Run() OVERRIDE { database_->Disable(); } 318 319 protected: 320 virtual ~DisableDatabaseTask() {} 321 }; 322 323 // GetAllInfoTask ------- 324 325 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask { 326 public: 327 explicit GetAllInfoTask(AppCacheStorageImpl* storage) 328 : DatabaseTask(storage), 329 info_collection_(new AppCacheInfoCollection()) { 330 } 331 332 // DatabaseTask: 333 virtual void Run() OVERRIDE; 334 virtual void RunCompleted() OVERRIDE; 335 336 protected: 337 virtual ~GetAllInfoTask() {} 338 339 private: 340 scoped_refptr<AppCacheInfoCollection> info_collection_; 341 }; 342 343 void AppCacheStorageImpl::GetAllInfoTask::Run() { 344 std::set<GURL> origins; 345 database_->FindOriginsWithGroups(&origins); 346 for (std::set<GURL>::const_iterator origin = origins.begin(); 347 origin != origins.end(); ++origin) { 348 AppCacheInfoVector& infos = 349 info_collection_->infos_by_origin[*origin]; 350 std::vector<AppCacheDatabase::GroupRecord> groups; 351 database_->FindGroupsForOrigin(*origin, &groups); 352 for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator 353 group = groups.begin(); 354 group != groups.end(); ++group) { 355 AppCacheDatabase::CacheRecord cache_record; 356 database_->FindCacheForGroup(group->group_id, &cache_record); 357 AppCacheInfo info; 358 info.manifest_url = group->manifest_url; 359 info.creation_time = group->creation_time; 360 info.size = cache_record.cache_size; 361 info.last_access_time = group->last_access_time; 362 info.last_update_time = cache_record.update_time; 363 info.cache_id = cache_record.cache_id; 364 info.group_id = group->group_id; 365 info.is_complete = true; 366 infos.push_back(info); 367 } 368 } 369 } 370 371 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() { 372 DCHECK(delegates_.size() == 1); 373 FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get())); 374 } 375 376 // StoreOrLoadTask ------- 377 378 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask { 379 protected: 380 explicit StoreOrLoadTask(AppCacheStorageImpl* storage) 381 : DatabaseTask(storage) {} 382 virtual ~StoreOrLoadTask() {} 383 384 bool FindRelatedCacheRecords(int64 cache_id); 385 void CreateCacheAndGroupFromRecords( 386 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group); 387 388 AppCacheDatabase::GroupRecord group_record_; 389 AppCacheDatabase::CacheRecord cache_record_; 390 std::vector<AppCacheDatabase::EntryRecord> entry_records_; 391 std::vector<AppCacheDatabase::NamespaceRecord> 392 intercept_namespace_records_; 393 std::vector<AppCacheDatabase::NamespaceRecord> 394 fallback_namespace_records_; 395 std::vector<AppCacheDatabase::OnlineWhiteListRecord> 396 online_whitelist_records_; 397 }; 398 399 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords( 400 int64 cache_id) { 401 return database_->FindEntriesForCache(cache_id, &entry_records_) && 402 database_->FindNamespacesForCache( 403 cache_id, &intercept_namespace_records_, 404 &fallback_namespace_records_) && 405 database_->FindOnlineWhiteListForCache( 406 cache_id, &online_whitelist_records_); 407 } 408 409 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords( 410 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) { 411 DCHECK(storage_ && cache && group); 412 413 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id); 414 if (cache->get()) { 415 (*group) = cache->get()->owning_group(); 416 DCHECK(group->get()); 417 DCHECK_EQ(group_record_.group_id, group->get()->group_id()); 418 419 // TODO(michaeln): histogram is fishing for clues to crbug/95101 420 if (!cache->get()->GetEntry(group_record_.manifest_url)) { 421 AppCacheHistograms::AddMissingManifestDetectedAtCallsite( 422 AppCacheHistograms::CALLSITE_0); 423 } 424 425 storage_->NotifyStorageAccessed(group_record_.origin); 426 return; 427 } 428 429 (*cache) = new AppCache(storage_, cache_record_.cache_id); 430 cache->get()->InitializeWithDatabaseRecords( 431 cache_record_, entry_records_, 432 intercept_namespace_records_, 433 fallback_namespace_records_, 434 online_whitelist_records_); 435 cache->get()->set_complete(true); 436 437 (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url); 438 if (group->get()) { 439 DCHECK(group_record_.group_id == group->get()->group_id()); 440 group->get()->AddCache(cache->get()); 441 442 // TODO(michaeln): histogram is fishing for clues to crbug/95101 443 if (!cache->get()->GetEntry(group_record_.manifest_url)) { 444 AppCacheHistograms::AddMissingManifestDetectedAtCallsite( 445 AppCacheHistograms::CALLSITE_1); 446 } 447 } else { 448 (*group) = new AppCacheGroup( 449 storage_, group_record_.manifest_url, 450 group_record_.group_id); 451 group->get()->set_creation_time(group_record_.creation_time); 452 group->get()->AddCache(cache->get()); 453 454 // TODO(michaeln): histogram is fishing for clues to crbug/95101 455 if (!cache->get()->GetEntry(group_record_.manifest_url)) { 456 AppCacheHistograms::AddMissingManifestDetectedAtCallsite( 457 AppCacheHistograms::CALLSITE_2); 458 } 459 } 460 DCHECK(group->get()->newest_complete_cache() == cache->get()); 461 462 // We have to update foriegn entries if MarkEntryAsForeignTasks 463 // are in flight. 464 std::vector<GURL> urls; 465 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls); 466 for (std::vector<GURL>::iterator iter = urls.begin(); 467 iter != urls.end(); ++iter) { 468 DCHECK(cache->get()->GetEntry(*iter)); 469 cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN); 470 } 471 472 storage_->NotifyStorageAccessed(group_record_.origin); 473 474 // TODO(michaeln): Maybe verify that the responses we expect to exist 475 // do actually exist in the disk_cache (and if not then what?) 476 } 477 478 // CacheLoadTask ------- 479 480 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask { 481 public: 482 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage) 483 : StoreOrLoadTask(storage), cache_id_(cache_id), 484 success_(false) {} 485 486 // DatabaseTask: 487 virtual void Run() OVERRIDE; 488 virtual void RunCompleted() OVERRIDE; 489 490 protected: 491 virtual ~CacheLoadTask() {} 492 493 private: 494 int64 cache_id_; 495 bool success_; 496 }; 497 498 void AppCacheStorageImpl::CacheLoadTask::Run() { 499 success_ = 500 database_->FindCache(cache_id_, &cache_record_) && 501 database_->FindGroup(cache_record_.group_id, &group_record_) && 502 FindRelatedCacheRecords(cache_id_); 503 504 if (success_) 505 database_->UpdateGroupLastAccessTime(group_record_.group_id, 506 base::Time::Now()); 507 } 508 509 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() { 510 storage_->pending_cache_loads_.erase(cache_id_); 511 scoped_refptr<AppCache> cache; 512 scoped_refptr<AppCacheGroup> group; 513 if (success_ && !storage_->is_disabled()) { 514 DCHECK(cache_record_.cache_id == cache_id_); 515 CreateCacheAndGroupFromRecords(&cache, &group); 516 } 517 FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_)); 518 } 519 520 // GroupLoadTask ------- 521 522 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask { 523 public: 524 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage) 525 : StoreOrLoadTask(storage), manifest_url_(manifest_url), 526 success_(false) {} 527 528 // DatabaseTask: 529 virtual void Run() OVERRIDE; 530 virtual void RunCompleted() OVERRIDE; 531 532 protected: 533 virtual ~GroupLoadTask() {} 534 535 private: 536 GURL manifest_url_; 537 bool success_; 538 }; 539 540 void AppCacheStorageImpl::GroupLoadTask::Run() { 541 success_ = 542 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) && 543 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) && 544 FindRelatedCacheRecords(cache_record_.cache_id); 545 546 if (success_) 547 database_->UpdateGroupLastAccessTime(group_record_.group_id, 548 base::Time::Now()); 549 } 550 551 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() { 552 storage_->pending_group_loads_.erase(manifest_url_); 553 scoped_refptr<AppCacheGroup> group; 554 scoped_refptr<AppCache> cache; 555 if (!storage_->is_disabled()) { 556 if (success_) { 557 DCHECK(group_record_.manifest_url == manifest_url_); 558 CreateCacheAndGroupFromRecords(&cache, &group); 559 } else { 560 group = storage_->working_set_.GetGroup(manifest_url_); 561 if (!group.get()) { 562 group = 563 new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId()); 564 } 565 } 566 } 567 FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_)); 568 } 569 570 // StoreGroupAndCacheTask ------- 571 572 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask { 573 public: 574 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group, 575 AppCache* newest_cache); 576 577 void GetQuotaThenSchedule(); 578 void OnQuotaCallback( 579 quota::QuotaStatusCode status, int64 usage, int64 quota); 580 581 // DatabaseTask: 582 virtual void Run() OVERRIDE; 583 virtual void RunCompleted() OVERRIDE; 584 virtual void CancelCompletion() OVERRIDE; 585 586 protected: 587 virtual ~StoreGroupAndCacheTask() {} 588 589 private: 590 scoped_refptr<AppCacheGroup> group_; 591 scoped_refptr<AppCache> cache_; 592 bool success_; 593 bool would_exceed_quota_; 594 int64 space_available_; 595 int64 new_origin_usage_; 596 std::vector<int64> newly_deletable_response_ids_; 597 }; 598 599 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask( 600 AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache) 601 : StoreOrLoadTask(storage), group_(group), cache_(newest_cache), 602 success_(false), would_exceed_quota_(false), 603 space_available_(-1), new_origin_usage_(-1) { 604 group_record_.group_id = group->group_id(); 605 group_record_.manifest_url = group->manifest_url(); 606 group_record_.origin = group_record_.manifest_url.GetOrigin(); 607 newest_cache->ToDatabaseRecords( 608 group, 609 &cache_record_, &entry_records_, 610 &intercept_namespace_records_, 611 &fallback_namespace_records_, 612 &online_whitelist_records_); 613 } 614 615 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() { 616 quota::QuotaManager* quota_manager = NULL; 617 if (storage_->service()->quota_manager_proxy()) { 618 quota_manager = 619 storage_->service()->quota_manager_proxy()->quota_manager(); 620 } 621 622 if (!quota_manager) { 623 if (storage_->service()->special_storage_policy() && 624 storage_->service()->special_storage_policy()->IsStorageUnlimited( 625 group_record_.origin)) 626 space_available_ = kint64max; 627 Schedule(); 628 return; 629 } 630 631 // We have to ask the quota manager for the value. 632 storage_->pending_quota_queries_.insert(this); 633 quota_manager->GetUsageAndQuota( 634 group_record_.origin, quota::kStorageTypeTemporary, 635 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this)); 636 } 637 638 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback( 639 quota::QuotaStatusCode status, int64 usage, int64 quota) { 640 if (storage_) { 641 if (status == quota::kQuotaStatusOk) 642 space_available_ = std::max(static_cast<int64>(0), quota - usage); 643 else 644 space_available_ = 0; 645 storage_->pending_quota_queries_.erase(this); 646 Schedule(); 647 } 648 } 649 650 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() { 651 DCHECK(!success_); 652 sql::Connection* connection = database_->db_connection(); 653 if (!connection) 654 return; 655 656 sql::Transaction transaction(connection); 657 if (!transaction.Begin()) 658 return; 659 660 int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin); 661 662 AppCacheDatabase::GroupRecord existing_group; 663 success_ = database_->FindGroup(group_record_.group_id, &existing_group); 664 if (!success_) { 665 group_record_.creation_time = base::Time::Now(); 666 group_record_.last_access_time = base::Time::Now(); 667 success_ = database_->InsertGroup(&group_record_); 668 } else { 669 DCHECK(group_record_.group_id == existing_group.group_id); 670 DCHECK(group_record_.manifest_url == existing_group.manifest_url); 671 DCHECK(group_record_.origin == existing_group.origin); 672 673 database_->UpdateGroupLastAccessTime(group_record_.group_id, 674 base::Time::Now()); 675 676 AppCacheDatabase::CacheRecord cache; 677 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) { 678 // Get the set of response ids in the old cache. 679 std::set<int64> existing_response_ids; 680 database_->FindResponseIdsForCacheAsSet(cache.cache_id, 681 &existing_response_ids); 682 683 // Remove those that remain in the new cache. 684 std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter = 685 entry_records_.begin(); 686 while (entry_iter != entry_records_.end()) { 687 existing_response_ids.erase(entry_iter->response_id); 688 ++entry_iter; 689 } 690 691 // The rest are deletable. 692 std::set<int64>::const_iterator id_iter = existing_response_ids.begin(); 693 while (id_iter != existing_response_ids.end()) { 694 newly_deletable_response_ids_.push_back(*id_iter); 695 ++id_iter; 696 } 697 698 success_ = 699 database_->DeleteCache(cache.cache_id) && 700 database_->DeleteEntriesForCache(cache.cache_id) && 701 database_->DeleteNamespacesForCache(cache.cache_id) && 702 database_->DeleteOnlineWhiteListForCache(cache.cache_id) && 703 database_->InsertDeletableResponseIds(newly_deletable_response_ids_); 704 // TODO(michaeln): store group_id too with deletable ids 705 } else { 706 NOTREACHED() << "A existing group without a cache is unexpected"; 707 } 708 } 709 710 success_ = 711 success_ && 712 database_->InsertCache(&cache_record_) && 713 database_->InsertEntryRecords(entry_records_) && 714 database_->InsertNamespaceRecords(intercept_namespace_records_) && 715 database_->InsertNamespaceRecords(fallback_namespace_records_) && 716 database_->InsertOnlineWhiteListRecords(online_whitelist_records_); 717 718 if (!success_) 719 return; 720 721 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin); 722 723 // Only check quota when the new usage exceeds the old usage. 724 if (new_origin_usage_ <= old_origin_usage) { 725 success_ = transaction.Commit(); 726 return; 727 } 728 729 // Use a simple hard-coded value when not using quota management. 730 if (space_available_ == -1) { 731 if (new_origin_usage_ > kDefaultQuota) { 732 would_exceed_quota_ = true; 733 success_ = false; 734 return; 735 } 736 success_ = transaction.Commit(); 737 return; 738 } 739 740 // Check limits based on the space availbable given to us via the 741 // quota system. 742 int64 delta = new_origin_usage_ - old_origin_usage; 743 if (delta > space_available_) { 744 would_exceed_quota_ = true; 745 success_ = false; 746 return; 747 } 748 749 success_ = transaction.Commit(); 750 } 751 752 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() { 753 if (success_) { 754 storage_->UpdateUsageMapAndNotify( 755 group_->manifest_url().GetOrigin(), new_origin_usage_); 756 if (cache_.get() != group_->newest_complete_cache()) { 757 cache_->set_complete(true); 758 group_->AddCache(cache_.get()); 759 } 760 if (group_->creation_time().is_null()) 761 group_->set_creation_time(group_record_.creation_time); 762 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); 763 } 764 FOR_EACH_DELEGATE( 765 delegates_, 766 OnGroupAndNewestCacheStored( 767 group_.get(), cache_.get(), success_, would_exceed_quota_)); 768 group_ = NULL; 769 cache_ = NULL; 770 771 // TODO(michaeln): if (would_exceed_quota_) what if the current usage 772 // also exceeds the quota? http://crbug.com/83968 773 } 774 775 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() { 776 // Overriden to safely drop our reference to the group and cache 777 // which are not thread safe refcounted. 778 DatabaseTask::CancelCompletion(); 779 group_ = NULL; 780 cache_ = NULL; 781 } 782 783 // FindMainResponseTask ------- 784 785 // Helpers for FindMainResponseTask::Run() 786 namespace { 787 class SortByCachePreference 788 : public std::binary_function< 789 AppCacheDatabase::EntryRecord, 790 AppCacheDatabase::EntryRecord, 791 bool> { 792 public: 793 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids) 794 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) { 795 } 796 bool operator()( 797 const AppCacheDatabase::EntryRecord& lhs, 798 const AppCacheDatabase::EntryRecord& rhs) { 799 return compute_value(lhs) > compute_value(rhs); 800 } 801 private: 802 int compute_value(const AppCacheDatabase::EntryRecord& entry) { 803 if (entry.cache_id == preferred_id_) 804 return 100; 805 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end()) 806 return 50; 807 return 0; 808 } 809 int64 preferred_id_; 810 const std::set<int64>& in_use_ids_; 811 }; 812 813 bool SortByLength( 814 const AppCacheDatabase::NamespaceRecord& lhs, 815 const AppCacheDatabase::NamespaceRecord& rhs) { 816 return lhs.namespace_.namespace_url.spec().length() > 817 rhs.namespace_.namespace_url.spec().length(); 818 } 819 820 class NetworkNamespaceHelper { 821 public: 822 explicit NetworkNamespaceHelper(AppCacheDatabase* database) 823 : database_(database) { 824 } 825 826 bool IsInNetworkNamespace(const GURL& url, int64 cache_id) { 827 typedef std::pair<WhiteListMap::iterator, bool> InsertResult; 828 InsertResult result = namespaces_map_.insert( 829 WhiteListMap::value_type(cache_id, NamespaceVector())); 830 if (result.second) 831 GetOnlineWhiteListForCache(cache_id, &result.first->second); 832 return AppCache::FindNamespace(result.first->second, url) != NULL; 833 } 834 835 private: 836 void GetOnlineWhiteListForCache( 837 int64 cache_id, NamespaceVector* namespaces) { 838 DCHECK(namespaces && namespaces->empty()); 839 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord> 840 WhiteListVector; 841 WhiteListVector records; 842 if (!database_->FindOnlineWhiteListForCache(cache_id, &records)) 843 return; 844 WhiteListVector::const_iterator iter = records.begin(); 845 while (iter != records.end()) { 846 namespaces->push_back( 847 Namespace(NETWORK_NAMESPACE, iter->namespace_url, GURL(), 848 iter->is_pattern)); 849 ++iter; 850 } 851 } 852 853 // Key is cache id 854 typedef std::map<int64, NamespaceVector> WhiteListMap; 855 WhiteListMap namespaces_map_; 856 AppCacheDatabase* database_; 857 }; 858 859 } // namespace 860 861 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask { 862 public: 863 FindMainResponseTask(AppCacheStorageImpl* storage, 864 const GURL& url, 865 const GURL& preferred_manifest_url, 866 const AppCacheWorkingSet::GroupMap* groups_in_use) 867 : DatabaseTask(storage), url_(url), 868 preferred_manifest_url_(preferred_manifest_url), 869 cache_id_(kNoCacheId), group_id_(0) { 870 if (groups_in_use) { 871 for (AppCacheWorkingSet::GroupMap::const_iterator it = 872 groups_in_use->begin(); 873 it != groups_in_use->end(); ++it) { 874 AppCacheGroup* group = it->second; 875 AppCache* cache = group->newest_complete_cache(); 876 if (group->is_obsolete() || !cache) 877 continue; 878 cache_ids_in_use_.insert(cache->cache_id()); 879 } 880 } 881 } 882 883 // DatabaseTask: 884 virtual void Run() OVERRIDE; 885 virtual void RunCompleted() OVERRIDE; 886 887 protected: 888 virtual ~FindMainResponseTask() {} 889 890 private: 891 typedef std::vector<AppCacheDatabase::NamespaceRecord*> 892 NamespaceRecordPtrVector; 893 894 bool FindExactMatch(int64 preferred_id); 895 bool FindNamespaceMatch(int64 preferred_id); 896 bool FindNamespaceHelper( 897 int64 preferred_cache_id, 898 AppCacheDatabase::NamespaceRecordVector* namespaces, 899 NetworkNamespaceHelper* network_namespace_helper); 900 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces); 901 902 GURL url_; 903 GURL preferred_manifest_url_; 904 std::set<int64> cache_ids_in_use_; 905 AppCacheEntry entry_; 906 AppCacheEntry fallback_entry_; 907 GURL namespace_entry_url_; 908 int64 cache_id_; 909 int64 group_id_; 910 GURL manifest_url_; 911 }; 912 913 914 915 void AppCacheStorageImpl::FindMainResponseTask::Run() { 916 // NOTE: The heuristics around choosing amoungst multiple candidates 917 // is underspecified, and just plain not fully understood. This needs 918 // to be refined. 919 920 // The 'preferred_manifest_url' is the url of the manifest associated 921 // with the page that opened or embedded the page being loaded now. 922 // We have a strong preference to use resources from that cache. 923 // We also have a lesser bias to use resources from caches that are currently 924 // being used by other unrelated pages. 925 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases 926 // - when navigating a frame whose current contents are from an appcache 927 // - when clicking an href in a frame that is appcached 928 int64 preferred_cache_id = kNoCacheId; 929 if (!preferred_manifest_url_.is_empty()) { 930 AppCacheDatabase::GroupRecord preferred_group; 931 AppCacheDatabase::CacheRecord preferred_cache; 932 if (database_->FindGroupForManifestUrl( 933 preferred_manifest_url_, &preferred_group) && 934 database_->FindCacheForGroup( 935 preferred_group.group_id, &preferred_cache)) { 936 preferred_cache_id = preferred_cache.cache_id; 937 } 938 } 939 940 if (FindExactMatch(preferred_cache_id) || 941 FindNamespaceMatch(preferred_cache_id)) { 942 // We found something. 943 DCHECK(cache_id_ != kNoCacheId && !manifest_url_.is_empty() && 944 group_id_ != 0); 945 return; 946 } 947 948 // We didn't find anything. 949 DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() && 950 group_id_ == 0); 951 } 952 953 bool AppCacheStorageImpl:: 954 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) { 955 std::vector<AppCacheDatabase::EntryRecord> entries; 956 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) { 957 // Sort them in order of preference, from the preferred_cache first, 958 // followed by hits from caches that are 'in use', then the rest. 959 std::sort(entries.begin(), entries.end(), 960 SortByCachePreference(preferred_cache_id, cache_ids_in_use_)); 961 962 // Take the first with a valid, non-foreign entry. 963 std::vector<AppCacheDatabase::EntryRecord>::iterator iter; 964 for (iter = entries.begin(); iter < entries.end(); ++iter) { 965 AppCacheDatabase::GroupRecord group_record; 966 if ((iter->flags & AppCacheEntry::FOREIGN) || 967 !database_->FindGroupForCache(iter->cache_id, &group_record)) { 968 continue; 969 } 970 manifest_url_ = group_record.manifest_url; 971 group_id_ = group_record.group_id; 972 entry_ = AppCacheEntry(iter->flags, iter->response_id); 973 cache_id_ = iter->cache_id; 974 return true; // We found an exact match. 975 } 976 } 977 return false; 978 } 979 980 bool AppCacheStorageImpl:: 981 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) { 982 AppCacheDatabase::NamespaceRecordVector all_intercepts; 983 AppCacheDatabase::NamespaceRecordVector all_fallbacks; 984 if (!database_->FindNamespacesForOrigin( 985 url_.GetOrigin(), &all_intercepts, &all_fallbacks) 986 || (all_intercepts.empty() && all_fallbacks.empty())) { 987 return false; 988 } 989 990 NetworkNamespaceHelper network_namespace_helper(database_); 991 if (FindNamespaceHelper(preferred_cache_id, 992 &all_intercepts, 993 &network_namespace_helper) || 994 FindNamespaceHelper(preferred_cache_id, 995 &all_fallbacks, 996 &network_namespace_helper)) { 997 return true; 998 } 999 return false; 1000 } 1001 1002 bool AppCacheStorageImpl:: 1003 FindMainResponseTask::FindNamespaceHelper( 1004 int64 preferred_cache_id, 1005 AppCacheDatabase::NamespaceRecordVector* namespaces, 1006 NetworkNamespaceHelper* network_namespace_helper) { 1007 // Sort them by length, longer matches within the same cache/bucket take 1008 // precedence. 1009 std::sort(namespaces->begin(), namespaces->end(), SortByLength); 1010 1011 NamespaceRecordPtrVector preferred_namespaces; 1012 NamespaceRecordPtrVector inuse_namespaces; 1013 NamespaceRecordPtrVector other_namespaces; 1014 std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter; 1015 for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) { 1016 // Skip those that aren't a match. 1017 if (!iter->namespace_.IsMatch(url_)) 1018 continue; 1019 1020 // Skip namespaces where the requested url falls into a network 1021 // namespace of its containing appcache. 1022 if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id)) 1023 continue; 1024 1025 // Bin them into one of our three buckets. 1026 if (iter->cache_id == preferred_cache_id) 1027 preferred_namespaces.push_back(&(*iter)); 1028 else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end()) 1029 inuse_namespaces.push_back(&(*iter)); 1030 else 1031 other_namespaces.push_back(&(*iter)); 1032 } 1033 1034 if (FindFirstValidNamespace(preferred_namespaces) || 1035 FindFirstValidNamespace(inuse_namespaces) || 1036 FindFirstValidNamespace(other_namespaces)) 1037 return true; // We found one. 1038 1039 // We didn't find anything. 1040 return false; 1041 } 1042 1043 bool AppCacheStorageImpl:: 1044 FindMainResponseTask::FindFirstValidNamespace( 1045 const NamespaceRecordPtrVector& namespaces) { 1046 // Take the first with a valid, non-foreign entry. 1047 NamespaceRecordPtrVector::const_iterator iter; 1048 for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) { 1049 AppCacheDatabase::EntryRecord entry_record; 1050 if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url, 1051 &entry_record)) { 1052 AppCacheDatabase::GroupRecord group_record; 1053 if ((entry_record.flags & AppCacheEntry::FOREIGN) || 1054 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) { 1055 continue; 1056 } 1057 manifest_url_ = group_record.manifest_url; 1058 group_id_ = group_record.group_id; 1059 cache_id_ = (*iter)->cache_id; 1060 namespace_entry_url_ = (*iter)->namespace_.target_url; 1061 if ((*iter)->namespace_.type == FALLBACK_NAMESPACE) 1062 fallback_entry_ = AppCacheEntry(entry_record.flags, 1063 entry_record.response_id); 1064 else 1065 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id); 1066 return true; // We found one. 1067 } 1068 } 1069 return false; // We didn't find a match. 1070 } 1071 1072 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() { 1073 storage_->CallOnMainResponseFound( 1074 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_, 1075 cache_id_, group_id_, manifest_url_); 1076 } 1077 1078 // MarkEntryAsForeignTask ------- 1079 1080 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask { 1081 public: 1082 MarkEntryAsForeignTask( 1083 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id) 1084 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {} 1085 1086 // DatabaseTask: 1087 virtual void Run() OVERRIDE; 1088 virtual void RunCompleted() OVERRIDE; 1089 1090 protected: 1091 virtual ~MarkEntryAsForeignTask() {} 1092 1093 private: 1094 int64 cache_id_; 1095 GURL entry_url_; 1096 }; 1097 1098 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() { 1099 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN); 1100 } 1101 1102 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() { 1103 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ && 1104 storage_->pending_foreign_markings_.front().second == cache_id_); 1105 storage_->pending_foreign_markings_.pop_front(); 1106 } 1107 1108 // MakeGroupObsoleteTask ------- 1109 1110 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask { 1111 public: 1112 MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group); 1113 1114 // DatabaseTask: 1115 virtual void Run() OVERRIDE; 1116 virtual void RunCompleted() OVERRIDE; 1117 virtual void CancelCompletion() OVERRIDE; 1118 1119 protected: 1120 virtual ~MakeGroupObsoleteTask() {} 1121 1122 private: 1123 scoped_refptr<AppCacheGroup> group_; 1124 int64 group_id_; 1125 GURL origin_; 1126 bool success_; 1127 int64 new_origin_usage_; 1128 std::vector<int64> newly_deletable_response_ids_; 1129 }; 1130 1131 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask( 1132 AppCacheStorageImpl* storage, AppCacheGroup* group) 1133 : DatabaseTask(storage), group_(group), group_id_(group->group_id()), 1134 origin_(group->manifest_url().GetOrigin()), 1135 success_(false), new_origin_usage_(-1) { 1136 } 1137 1138 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() { 1139 DCHECK(!success_); 1140 sql::Connection* connection = database_->db_connection(); 1141 if (!connection) 1142 return; 1143 1144 sql::Transaction transaction(connection); 1145 if (!transaction.Begin()) 1146 return; 1147 1148 AppCacheDatabase::GroupRecord group_record; 1149 if (!database_->FindGroup(group_id_, &group_record)) { 1150 // This group doesn't exists in the database, nothing todo here. 1151 new_origin_usage_ = database_->GetOriginUsage(origin_); 1152 success_ = true; 1153 return; 1154 } 1155 1156 DCHECK_EQ(group_record.origin, origin_); 1157 success_ = DeleteGroupAndRelatedRecords(database_, 1158 group_id_, 1159 &newly_deletable_response_ids_); 1160 1161 new_origin_usage_ = database_->GetOriginUsage(origin_); 1162 success_ = success_ && transaction.Commit(); 1163 } 1164 1165 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() { 1166 if (success_) { 1167 group_->set_obsolete(true); 1168 if (!storage_->is_disabled()) { 1169 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_); 1170 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); 1171 1172 // Also remove from the working set, caches for an 'obsolete' group 1173 // may linger in use, but the group itself cannot be looked up by 1174 // 'manifest_url' in the working set any longer. 1175 storage_->working_set()->RemoveGroup(group_.get()); 1176 } 1177 } 1178 FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_.get(), success_)); 1179 group_ = NULL; 1180 } 1181 1182 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() { 1183 // Overriden to safely drop our reference to the group 1184 // which is not thread safe refcounted. 1185 DatabaseTask::CancelCompletion(); 1186 group_ = NULL; 1187 } 1188 1189 // GetDeletableResponseIdsTask ------- 1190 1191 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask { 1192 public: 1193 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid) 1194 : DatabaseTask(storage), max_rowid_(max_rowid) {} 1195 1196 // DatabaseTask: 1197 virtual void Run() OVERRIDE; 1198 virtual void RunCompleted() OVERRIDE; 1199 1200 protected: 1201 virtual ~GetDeletableResponseIdsTask() {} 1202 1203 private: 1204 int64 max_rowid_; 1205 std::vector<int64> response_ids_; 1206 }; 1207 1208 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() { 1209 const int kSqlLimit = 1000; 1210 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit); 1211 // TODO(michaeln): retrieve group_ids too 1212 } 1213 1214 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() { 1215 if (!response_ids_.empty()) 1216 storage_->StartDeletingResponses(response_ids_); 1217 } 1218 1219 // InsertDeletableResponseIdsTask ------- 1220 1221 class AppCacheStorageImpl::InsertDeletableResponseIdsTask 1222 : public DatabaseTask { 1223 public: 1224 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage) 1225 : DatabaseTask(storage) {} 1226 1227 // DatabaseTask: 1228 virtual void Run() OVERRIDE; 1229 1230 std::vector<int64> response_ids_; 1231 1232 protected: 1233 virtual ~InsertDeletableResponseIdsTask() {} 1234 }; 1235 1236 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() { 1237 database_->InsertDeletableResponseIds(response_ids_); 1238 // TODO(michaeln): store group_ids too 1239 } 1240 1241 // DeleteDeletableResponseIdsTask ------- 1242 1243 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask 1244 : public DatabaseTask { 1245 public: 1246 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage) 1247 : DatabaseTask(storage) {} 1248 1249 // DatabaseTask: 1250 virtual void Run() OVERRIDE; 1251 1252 std::vector<int64> response_ids_; 1253 1254 protected: 1255 virtual ~DeleteDeletableResponseIdsTask() {} 1256 }; 1257 1258 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() { 1259 database_->DeleteDeletableResponseIds(response_ids_); 1260 } 1261 1262 // UpdateGroupLastAccessTimeTask ------- 1263 1264 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask 1265 : public DatabaseTask { 1266 public: 1267 UpdateGroupLastAccessTimeTask( 1268 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time) 1269 : DatabaseTask(storage), group_id_(group->group_id()), 1270 last_access_time_(time) { 1271 storage->NotifyStorageAccessed(group->manifest_url().GetOrigin()); 1272 } 1273 1274 // DatabaseTask: 1275 virtual void Run() OVERRIDE; 1276 1277 protected: 1278 virtual ~UpdateGroupLastAccessTimeTask() {} 1279 1280 private: 1281 int64 group_id_; 1282 base::Time last_access_time_; 1283 }; 1284 1285 void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() { 1286 database_->UpdateGroupLastAccessTime(group_id_, last_access_time_); 1287 } 1288 1289 1290 // AppCacheStorageImpl --------------------------------------------------- 1291 1292 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service) 1293 : AppCacheStorage(service), 1294 is_incognito_(false), 1295 is_response_deletion_scheduled_(false), 1296 did_start_deleting_responses_(false), 1297 last_deletable_response_rowid_(0), 1298 database_(NULL), 1299 is_disabled_(false), 1300 weak_factory_(this) { 1301 } 1302 1303 AppCacheStorageImpl::~AppCacheStorageImpl() { 1304 std::for_each(pending_quota_queries_.begin(), 1305 pending_quota_queries_.end(), 1306 std::mem_fun(&DatabaseTask::CancelCompletion)); 1307 std::for_each(scheduled_database_tasks_.begin(), 1308 scheduled_database_tasks_.end(), 1309 std::mem_fun(&DatabaseTask::CancelCompletion)); 1310 1311 if (database_ && 1312 !db_thread_->PostTask( 1313 FROM_HERE, 1314 base::Bind(&ClearSessionOnlyOrigins, database_, 1315 make_scoped_refptr(service_->special_storage_policy()), 1316 service()->force_keep_session_state()))) { 1317 delete database_; 1318 } 1319 } 1320 1321 void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory, 1322 base::MessageLoopProxy* db_thread, 1323 base::MessageLoopProxy* cache_thread) { 1324 DCHECK(db_thread); 1325 1326 cache_directory_ = cache_directory; 1327 is_incognito_ = cache_directory_.empty(); 1328 1329 base::FilePath db_file_path; 1330 if (!is_incognito_) 1331 db_file_path = cache_directory_.Append(kAppCacheDatabaseName); 1332 database_ = new AppCacheDatabase(db_file_path); 1333 1334 db_thread_ = db_thread; 1335 cache_thread_ = cache_thread; 1336 1337 scoped_refptr<InitTask> task(new InitTask(this)); 1338 task->Schedule(); 1339 } 1340 1341 void AppCacheStorageImpl::Disable() { 1342 if (is_disabled_) 1343 return; 1344 VLOG(1) << "Disabling appcache storage."; 1345 is_disabled_ = true; 1346 ClearUsageMapAndNotify(); 1347 working_set()->Disable(); 1348 if (disk_cache_) 1349 disk_cache_->Disable(); 1350 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this)); 1351 task->Schedule(); 1352 } 1353 1354 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) { 1355 DCHECK(delegate); 1356 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this)); 1357 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1358 task->Schedule(); 1359 } 1360 1361 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) { 1362 DCHECK(delegate); 1363 if (is_disabled_) { 1364 delegate->OnCacheLoaded(NULL, id); 1365 return; 1366 } 1367 1368 AppCache* cache = working_set_.GetCache(id); 1369 if (cache) { 1370 delegate->OnCacheLoaded(cache, id); 1371 if (cache->owning_group()) { 1372 scoped_refptr<DatabaseTask> update_task( 1373 new UpdateGroupLastAccessTimeTask( 1374 this, cache->owning_group(), base::Time::Now())); 1375 update_task->Schedule(); 1376 } 1377 return; 1378 } 1379 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id)); 1380 if (task.get()) { 1381 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1382 return; 1383 } 1384 task = new CacheLoadTask(id, this); 1385 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1386 task->Schedule(); 1387 pending_cache_loads_[id] = task.get(); 1388 } 1389 1390 void AppCacheStorageImpl::LoadOrCreateGroup( 1391 const GURL& manifest_url, Delegate* delegate) { 1392 DCHECK(delegate); 1393 if (is_disabled_) { 1394 delegate->OnGroupLoaded(NULL, manifest_url); 1395 return; 1396 } 1397 1398 AppCacheGroup* group = working_set_.GetGroup(manifest_url); 1399 if (group) { 1400 delegate->OnGroupLoaded(group, manifest_url); 1401 scoped_refptr<DatabaseTask> update_task( 1402 new UpdateGroupLastAccessTimeTask( 1403 this, group, base::Time::Now())); 1404 update_task->Schedule(); 1405 return; 1406 } 1407 1408 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url)); 1409 if (task.get()) { 1410 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1411 return; 1412 } 1413 1414 if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) { 1415 // No need to query the database, return a new group immediately. 1416 scoped_refptr<AppCacheGroup> group(new AppCacheGroup( 1417 service_->storage(), manifest_url, NewGroupId())); 1418 delegate->OnGroupLoaded(group.get(), manifest_url); 1419 return; 1420 } 1421 1422 task = new GroupLoadTask(manifest_url, this); 1423 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1424 task->Schedule(); 1425 pending_group_loads_[manifest_url] = task.get(); 1426 } 1427 1428 void AppCacheStorageImpl::StoreGroupAndNewestCache( 1429 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) { 1430 // TODO(michaeln): distinguish between a simple update of an existing 1431 // cache that just adds new master entry(s), and the insertion of a 1432 // whole new cache. The StoreGroupAndCacheTask as written will handle 1433 // the simple update case in a very heavy weight way (delete all and 1434 // the reinsert all over again). 1435 DCHECK(group && delegate && newest_cache); 1436 scoped_refptr<StoreGroupAndCacheTask> task( 1437 new StoreGroupAndCacheTask(this, group, newest_cache)); 1438 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1439 task->GetQuotaThenSchedule(); 1440 1441 // TODO(michaeln): histogram is fishing for clues to crbug/95101 1442 if (!newest_cache->GetEntry(group->manifest_url())) { 1443 AppCacheHistograms::AddMissingManifestDetectedAtCallsite( 1444 AppCacheHistograms::CALLSITE_3); 1445 } 1446 } 1447 1448 void AppCacheStorageImpl::FindResponseForMainRequest( 1449 const GURL& url, const GURL& preferred_manifest_url, 1450 Delegate* delegate) { 1451 DCHECK(delegate); 1452 1453 const GURL* url_ptr = &url; 1454 GURL url_no_ref; 1455 if (url.has_ref()) { 1456 GURL::Replacements replacements; 1457 replacements.ClearRef(); 1458 url_no_ref = url.ReplaceComponents(replacements); 1459 url_ptr = &url_no_ref; 1460 } 1461 1462 const GURL origin = url.GetOrigin(); 1463 1464 // First look in our working set for a direct hit without having to query 1465 // the database. 1466 const AppCacheWorkingSet::GroupMap* groups_in_use = 1467 working_set()->GetGroupsInOrigin(origin); 1468 if (groups_in_use) { 1469 if (!preferred_manifest_url.is_empty()) { 1470 AppCacheWorkingSet::GroupMap::const_iterator found = 1471 groups_in_use->find(preferred_manifest_url); 1472 if (found != groups_in_use->end() && 1473 FindResponseForMainRequestInGroup( 1474 found->second, *url_ptr, delegate)) { 1475 return; 1476 } 1477 } else { 1478 for (AppCacheWorkingSet::GroupMap::const_iterator it = 1479 groups_in_use->begin(); 1480 it != groups_in_use->end(); ++it) { 1481 if (FindResponseForMainRequestInGroup( 1482 it->second, *url_ptr, delegate)) { 1483 return; 1484 } 1485 } 1486 } 1487 } 1488 1489 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) { 1490 // No need to query the database, return async'ly but without going thru 1491 // the DB thread. 1492 scoped_refptr<AppCacheGroup> no_group; 1493 scoped_refptr<AppCache> no_cache; 1494 ScheduleSimpleTask( 1495 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, 1496 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group, 1497 no_cache, 1498 make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); 1499 return; 1500 } 1501 1502 // We have to query the database, schedule a database task to do so. 1503 scoped_refptr<FindMainResponseTask> task( 1504 new FindMainResponseTask(this, *url_ptr, preferred_manifest_url, 1505 groups_in_use)); 1506 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1507 task->Schedule(); 1508 } 1509 1510 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup( 1511 AppCacheGroup* group, const GURL& url, Delegate* delegate) { 1512 AppCache* cache = group->newest_complete_cache(); 1513 if (group->is_obsolete() || !cache) 1514 return false; 1515 1516 AppCacheEntry* entry = cache->GetEntry(url); 1517 if (!entry || entry->IsForeign()) 1518 return false; 1519 1520 ScheduleSimpleTask( 1521 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, 1522 weak_factory_.GetWeakPtr(), url, *entry, 1523 make_scoped_refptr(group), make_scoped_refptr(cache), 1524 make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); 1525 return true; 1526 } 1527 1528 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse( 1529 const GURL& url, 1530 const AppCacheEntry& found_entry, 1531 scoped_refptr<AppCacheGroup> group, 1532 scoped_refptr<AppCache> cache, 1533 scoped_refptr<DelegateReference> delegate_ref) { 1534 if (delegate_ref->delegate) { 1535 DelegateReferenceVector delegates(1, delegate_ref); 1536 CallOnMainResponseFound( 1537 &delegates, url, found_entry, 1538 GURL(), AppCacheEntry(), 1539 cache.get() ? cache->cache_id() : kNoCacheId, 1540 group.get() ? group->group_id() : kNoCacheId, 1541 group.get() ? group->manifest_url() : GURL()); 1542 } 1543 } 1544 1545 void AppCacheStorageImpl::CallOnMainResponseFound( 1546 DelegateReferenceVector* delegates, 1547 const GURL& url, const AppCacheEntry& entry, 1548 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, 1549 int64 cache_id, int64 group_id, const GURL& manifest_url) { 1550 FOR_EACH_DELEGATE( 1551 (*delegates), 1552 OnMainResponseFound(url, entry, 1553 namespace_entry_url, fallback_entry, 1554 cache_id, group_id, manifest_url)); 1555 } 1556 1557 void AppCacheStorageImpl::FindResponseForSubRequest( 1558 AppCache* cache, const GURL& url, 1559 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, 1560 bool* found_network_namespace) { 1561 DCHECK(cache && cache->is_complete()); 1562 1563 // When a group is forcibly deleted, all subresource loads for pages 1564 // using caches in the group will result in a synthesized network errors. 1565 // Forcible deletion is not a function that is covered by the HTML5 spec. 1566 if (cache->owning_group()->is_being_deleted()) { 1567 *found_entry = AppCacheEntry(); 1568 *found_fallback_entry = AppCacheEntry(); 1569 *found_network_namespace = false; 1570 return; 1571 } 1572 1573 GURL fallback_namespace_not_used; 1574 GURL intercept_namespace_not_used; 1575 cache->FindResponseForRequest( 1576 url, found_entry, &intercept_namespace_not_used, 1577 found_fallback_entry, &fallback_namespace_not_used, 1578 found_network_namespace); 1579 } 1580 1581 void AppCacheStorageImpl::MarkEntryAsForeign( 1582 const GURL& entry_url, int64 cache_id) { 1583 AppCache* cache = working_set_.GetCache(cache_id); 1584 if (cache) { 1585 AppCacheEntry* entry = cache->GetEntry(entry_url); 1586 DCHECK(entry); 1587 if (entry) 1588 entry->add_types(AppCacheEntry::FOREIGN); 1589 } 1590 scoped_refptr<MarkEntryAsForeignTask> task( 1591 new MarkEntryAsForeignTask(this, entry_url, cache_id)); 1592 task->Schedule(); 1593 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id)); 1594 } 1595 1596 void AppCacheStorageImpl::MakeGroupObsolete( 1597 AppCacheGroup* group, Delegate* delegate) { 1598 DCHECK(group && delegate); 1599 scoped_refptr<MakeGroupObsoleteTask> task( 1600 new MakeGroupObsoleteTask(this, group)); 1601 task->AddDelegate(GetOrCreateDelegateReference(delegate)); 1602 task->Schedule(); 1603 } 1604 1605 AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader( 1606 const GURL& manifest_url, int64 group_id, int64 response_id) { 1607 return new AppCacheResponseReader(response_id, group_id, disk_cache()); 1608 } 1609 1610 AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter( 1611 const GURL& manifest_url, int64 group_id) { 1612 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache()); 1613 } 1614 1615 void AppCacheStorageImpl::DoomResponses( 1616 const GURL& manifest_url, const std::vector<int64>& response_ids) { 1617 if (response_ids.empty()) 1618 return; 1619 1620 // Start deleting them from the disk cache lazily. 1621 StartDeletingResponses(response_ids); 1622 1623 // Also schedule a database task to record these ids in the 1624 // deletable responses table. 1625 // TODO(michaeln): There is a race here. If the browser crashes 1626 // prior to committing these rows to the database and prior to us 1627 // having deleted them from the disk cache, we'll never delete them. 1628 scoped_refptr<InsertDeletableResponseIdsTask> task( 1629 new InsertDeletableResponseIdsTask(this)); 1630 task->response_ids_ = response_ids; 1631 task->Schedule(); 1632 } 1633 1634 void AppCacheStorageImpl::DeleteResponses( 1635 const GURL& manifest_url, const std::vector<int64>& response_ids) { 1636 if (response_ids.empty()) 1637 return; 1638 StartDeletingResponses(response_ids); 1639 } 1640 1641 void AppCacheStorageImpl::PurgeMemory() { 1642 scoped_refptr<CloseConnectionTask> task(new CloseConnectionTask(this)); 1643 task->Schedule(); 1644 } 1645 1646 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() { 1647 // Only if we haven't already begun. 1648 if (!did_start_deleting_responses_) { 1649 scoped_refptr<GetDeletableResponseIdsTask> task( 1650 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_)); 1651 task->Schedule(); 1652 } 1653 } 1654 1655 void AppCacheStorageImpl::StartDeletingResponses( 1656 const std::vector<int64>& response_ids) { 1657 DCHECK(!response_ids.empty()); 1658 did_start_deleting_responses_ = true; 1659 deletable_response_ids_.insert( 1660 deletable_response_ids_.end(), 1661 response_ids.begin(), response_ids.end()); 1662 if (!is_response_deletion_scheduled_) 1663 ScheduleDeleteOneResponse(); 1664 } 1665 1666 void AppCacheStorageImpl::ScheduleDeleteOneResponse() { 1667 DCHECK(!is_response_deletion_scheduled_); 1668 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10); 1669 base::MessageLoop::current()->PostDelayedTask( 1670 FROM_HERE, 1671 base::Bind(&AppCacheStorageImpl::DeleteOneResponse, 1672 weak_factory_.GetWeakPtr()), 1673 kDelay); 1674 is_response_deletion_scheduled_ = true; 1675 } 1676 1677 void AppCacheStorageImpl::DeleteOneResponse() { 1678 DCHECK(is_response_deletion_scheduled_); 1679 DCHECK(!deletable_response_ids_.empty()); 1680 1681 if (!disk_cache()) { 1682 DCHECK(is_disabled_); 1683 deletable_response_ids_.clear(); 1684 deleted_response_ids_.clear(); 1685 is_response_deletion_scheduled_ = false; 1686 return; 1687 } 1688 1689 // TODO(michaeln): add group_id to DoomEntry args 1690 int64 id = deletable_response_ids_.front(); 1691 int rv = disk_cache_->DoomEntry( 1692 id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse, 1693 base::Unretained(this))); 1694 if (rv != net::ERR_IO_PENDING) 1695 OnDeletedOneResponse(rv); 1696 } 1697 1698 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) { 1699 is_response_deletion_scheduled_ = false; 1700 if (is_disabled_) 1701 return; 1702 1703 int64 id = deletable_response_ids_.front(); 1704 deletable_response_ids_.pop_front(); 1705 if (rv != net::ERR_ABORTED) 1706 deleted_response_ids_.push_back(id); 1707 1708 const size_t kBatchSize = 50U; 1709 if (deleted_response_ids_.size() >= kBatchSize || 1710 deletable_response_ids_.empty()) { 1711 scoped_refptr<DeleteDeletableResponseIdsTask> task( 1712 new DeleteDeletableResponseIdsTask(this)); 1713 task->response_ids_.swap(deleted_response_ids_); 1714 task->Schedule(); 1715 } 1716 1717 if (deletable_response_ids_.empty()) { 1718 scoped_refptr<GetDeletableResponseIdsTask> task( 1719 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_)); 1720 task->Schedule(); 1721 return; 1722 } 1723 1724 ScheduleDeleteOneResponse(); 1725 } 1726 1727 AppCacheStorageImpl::CacheLoadTask* 1728 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) { 1729 PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id); 1730 if (found != pending_cache_loads_.end()) 1731 return found->second; 1732 return NULL; 1733 } 1734 1735 AppCacheStorageImpl::GroupLoadTask* 1736 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) { 1737 PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url); 1738 if (found != pending_group_loads_.end()) 1739 return found->second; 1740 return NULL; 1741 } 1742 1743 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache( 1744 int64 cache_id, std::vector<GURL>* urls) { 1745 PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin(); 1746 while (iter != pending_foreign_markings_.end()) { 1747 if (iter->second == cache_id) 1748 urls->push_back(iter->first); 1749 ++iter; 1750 } 1751 } 1752 1753 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) { 1754 pending_simple_tasks_.push_back(task); 1755 base::MessageLoop::current()->PostTask( 1756 FROM_HERE, 1757 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask, 1758 weak_factory_.GetWeakPtr())); 1759 } 1760 1761 void AppCacheStorageImpl::RunOnePendingSimpleTask() { 1762 DCHECK(!pending_simple_tasks_.empty()); 1763 base::Closure task = pending_simple_tasks_.front(); 1764 pending_simple_tasks_.pop_front(); 1765 task.Run(); 1766 } 1767 1768 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() { 1769 DCHECK(IsInitTaskComplete()); 1770 1771 if (is_disabled_) 1772 return NULL; 1773 1774 if (!disk_cache_) { 1775 int rv = net::OK; 1776 disk_cache_.reset(new AppCacheDiskCache); 1777 if (is_incognito_) { 1778 rv = disk_cache_->InitWithMemBackend( 1779 kMaxMemDiskCacheSize, 1780 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, 1781 base::Unretained(this))); 1782 } else { 1783 rv = disk_cache_->InitWithDiskBackend( 1784 cache_directory_.Append(kDiskCacheDirectoryName), 1785 kMaxDiskCacheSize, 1786 false, 1787 cache_thread_.get(), 1788 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, 1789 base::Unretained(this))); 1790 } 1791 1792 // We should not keep this reference around. 1793 cache_thread_ = NULL; 1794 1795 if (rv != net::ERR_IO_PENDING) 1796 OnDiskCacheInitialized(rv); 1797 } 1798 return disk_cache_.get(); 1799 } 1800 1801 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) { 1802 if (rv != net::OK) { 1803 LOG(ERROR) << "Failed to open the appcache diskcache."; 1804 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR); 1805 1806 // We're unable to open the disk cache, this is a fatal error that we can't 1807 // really recover from. We handle it by disabling the appcache for this 1808 // browser session and deleting the directory on disk. The next browser 1809 // session should start with a clean slate. 1810 Disable(); 1811 if (!is_incognito_) { 1812 VLOG(1) << "Deleting existing appcache data and starting over."; 1813 db_thread_->PostTask( 1814 FROM_HERE, base::Bind(base::IgnoreResult(&base::DeleteFile), 1815 cache_directory_, true)); 1816 } 1817 } 1818 } 1819 1820 } // namespace appcache 1821