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