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.
5 #include "webkit/browser/appcache/appcache_update_job.h"
8 #include "base/bind_helpers.h"
9 #include "base/compiler_specific.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "net/base/io_buffer.h"
14 #include "net/base/load_flags.h"
15 #include "net/base/net_errors.h"
16 #include "net/base/request_priority.h"
17 #include "net/http/http_request_headers.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/url_request/url_request_context.h"
20 #include "webkit/browser/appcache/appcache_group.h"
21 #include "webkit/browser/appcache/appcache_histograms.h"
25 static const int kBufferSize = 32768;
26 static const size_t kMaxConcurrentUrlFetches = 2;
27 static const int kMax503Retries = 3;
29 static std::string FormatUrlErrorMessage(
30 const char* format, const GURL& url,
31 AppCacheUpdateJob::ResultType error,
33 // Show the net response code if we have one.
34 int code = response_code;
35 if (error != AppCacheUpdateJob::SERVER_ERROR)
36 code = static_cast<int>(error);
37 return base::StringPrintf(format, code, url.spec().c_str());
40 // Helper class for collecting hosts per frontend when sending notifications
41 // so that only one notification is sent for all hosts using the same frontend.
44 typedef std::vector<int> HostIds;
45 typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap;
47 // Caller is responsible for ensuring there will be no duplicate hosts.
48 void AddHost(AppCacheHost* host) {
49 std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert(
50 NotifyHostMap::value_type(host->frontend(), HostIds()));
51 ret.first->second.push_back(host->host_id());
54 void AddHosts(const std::set<AppCacheHost*>& hosts) {
55 for (std::set<AppCacheHost*>::const_iterator it = hosts.begin();
56 it != hosts.end(); ++it) {
61 void SendNotifications(EventID event_id) {
62 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
63 it != hosts_to_notify.end(); ++it) {
64 AppCacheFrontend* frontend = it->first;
65 frontend->OnEventRaised(it->second, event_id);
69 void SendProgressNotifications(
70 const GURL& url, int num_total, int num_complete) {
71 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
72 it != hosts_to_notify.end(); ++it) {
73 AppCacheFrontend* frontend = it->first;
74 frontend->OnProgressEventRaised(it->second, url,
75 num_total, num_complete);
79 void SendErrorNotifications(const std::string& error_message) {
80 DCHECK(!error_message.empty());
81 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
82 it != hosts_to_notify.end(); ++it) {
83 AppCacheFrontend* frontend = it->first;
84 frontend->OnErrorEventRaised(it->second, error_message);
89 NotifyHostMap hosts_to_notify;
92 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
94 AppCacheResponseInfo* info)
96 storage_checked(checked),
97 existing_response_info(info) {
100 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
103 // Helper class to fetch resources. Depending on the fetch type,
104 // can either fetch to an in-memory string or write the response
105 // data out to the disk cache.
106 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
107 FetchType fetch_type,
108 AppCacheUpdateJob* job)
111 fetch_type_(fetch_type),
112 retry_503_attempts_(0),
113 buffer_(new net::IOBuffer(kBufferSize)),
114 request_(job->service_->request_context()
115 ->CreateRequest(url, net::DEFAULT_PRIORITY, this)),
116 result_(UPDATE_OK) {}
118 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
121 void AppCacheUpdateJob::URLFetcher::Start() {
122 request_->set_first_party_for_cookies(job_->manifest_url_);
123 request_->SetLoadFlags(request_->load_flags() |
124 net::LOAD_DISABLE_INTERCEPT);
125 if (existing_response_headers_.get())
126 AddConditionalHeaders(existing_response_headers_.get());
130 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
131 net::URLRequest* request, const GURL& new_url, bool* defer_redirect) {
132 DCHECK(request_ == request);
133 // Redirect is not allowed by the update process.
135 result_ = REDIRECT_ERROR;
136 OnResponseCompleted();
139 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
140 net::URLRequest *request) {
141 DCHECK(request == request_);
142 int response_code = -1;
143 if (request->status().is_success())
144 response_code = request->GetResponseCode();
145 if ((response_code / 100) == 2) {
147 // See http://code.google.com/p/chromium/issues/detail?id=69594
148 // We willfully violate the HTML5 spec at this point in order
149 // to support the appcaching of cross-origin HTTPS resources.
150 // We've opted for a milder constraint and allow caching unless
151 // the resource has a "no-store" header. A spec change has been
152 // requested on the whatwg list.
153 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
154 if (url_.SchemeIsSecure() &&
155 url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
156 if (request->response_headers()->
157 HasHeaderValue("cache-control", "no-store")) {
159 result_ = SERVER_ERROR; // Not the best match?
160 OnResponseCompleted();
165 // Write response info to storage for URL fetches. Wait for async write
166 // completion before reading any response data.
167 if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
168 response_writer_.reset(job_->CreateResponseWriter());
169 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
170 new HttpResponseInfoIOBuffer(
171 new net::HttpResponseInfo(request->response_info())));
172 response_writer_->WriteInfo(
174 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
179 if (response_code > 0)
180 result_ = SERVER_ERROR;
182 result_ = NETWORK_ERROR;
183 OnResponseCompleted();
187 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
188 net::URLRequest* request, int bytes_read) {
189 DCHECK(request_ == request);
190 bool data_consumed = true;
191 if (request->status().is_success() && bytes_read > 0) {
192 data_consumed = ConsumeResponseData(bytes_read);
195 while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
196 if (bytes_read > 0) {
197 data_consumed = ConsumeResponseData(bytes_read);
199 break; // wait for async data processing, then read more
206 if (data_consumed && !request->status().is_io_pending())
207 OnResponseCompleted();
210 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
211 const net::HttpResponseHeaders* headers) {
212 DCHECK(request_.get() && headers);
213 net::HttpRequestHeaders extra_headers;
215 // Add If-Modified-Since header if response info has Last-Modified header.
216 const std::string last_modified = "Last-Modified";
217 std::string last_modified_value;
218 headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
219 if (!last_modified_value.empty()) {
220 extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
221 last_modified_value);
224 // Add If-None-Match header if response info has ETag header.
225 const std::string etag = "ETag";
226 std::string etag_value;
227 headers->EnumerateHeader(NULL, etag, &etag_value);
228 if (!etag_value.empty()) {
229 extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
232 if (!extra_headers.IsEmpty())
233 request_->SetExtraRequestHeaders(extra_headers);
236 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
239 result_ = DISKCACHE_ERROR;
240 OnResponseCompleted();
246 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
247 InternalUpdateState state = job_->internal_state_;
248 if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
251 request_->Read(buffer_.get(), kBufferSize, &bytes_read);
252 OnReadCompleted(request_.get(), bytes_read);
255 // Returns false if response data is processed asynchronously, in which
256 // case ReadResponseData will be invoked when it is safe to continue
257 // reading more response data from the request.
258 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
259 DCHECK_GT(bytes_read, 0);
260 switch (fetch_type_) {
262 case MANIFEST_REFETCH:
263 manifest_data_.append(buffer_->data(), bytes_read);
266 case MASTER_ENTRY_FETCH:
267 DCHECK(response_writer_.get());
268 response_writer_->WriteData(
271 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
272 return false; // wait for async write completion to continue reading
279 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
280 // Retry for 503s where retry-after is 0.
281 if (request_->status().is_success() &&
282 request_->GetResponseCode() == 503 &&
283 MaybeRetryRequest()) {
287 switch (fetch_type_) {
289 job_->HandleManifestFetchCompleted(this);
292 job_->HandleUrlFetchCompleted(this);
294 case MASTER_ENTRY_FETCH:
295 job_->HandleMasterEntryFetchCompleted(this);
297 case MANIFEST_REFETCH:
298 job_->HandleManifestRefetchCompleted(this);
307 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
308 if (retry_503_attempts_ >= kMax503Retries ||
309 !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
312 ++retry_503_attempts_;
313 request_ = job_->service_->request_context()->CreateRequest(
314 url_, net::DEFAULT_PRIORITY, this);
319 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service,
320 AppCacheGroup* group)
322 manifest_url_(group->manifest_url()),
324 update_type_(UNKNOWN_TYPE),
325 internal_state_(FETCH_MANIFEST),
326 master_entries_completed_(0),
327 url_fetches_completed_(0),
328 manifest_fetcher_(NULL),
329 stored_state_(UNSTORED),
330 storage_(service->storage()) {
331 service_->AddObserver(this);
334 AppCacheUpdateJob::~AppCacheUpdateJob() {
336 service_->RemoveObserver(this);
337 if (internal_state_ != COMPLETED)
340 DCHECK(!manifest_fetcher_);
341 DCHECK(pending_url_fetches_.empty());
342 DCHECK(!inprogress_cache_.get());
343 DCHECK(pending_master_entries_.empty());
344 DCHECK(master_entry_fetches_.empty());
347 group_->SetUpdateStatus(AppCacheGroup::IDLE);
350 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
351 const GURL& new_master_resource) {
352 DCHECK(group_->update_job() == this);
353 DCHECK(!group_->is_obsolete());
355 bool is_new_pending_master_entry = false;
356 if (!new_master_resource.is_empty()) {
357 DCHECK(new_master_resource == host->pending_master_entry_url());
358 DCHECK(!new_master_resource.has_ref());
359 DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
361 // Cannot add more to this update if already terminating.
362 if (IsTerminating()) {
363 group_->QueueUpdate(host, new_master_resource);
367 std::pair<PendingMasters::iterator, bool> ret =
368 pending_master_entries_.insert(
369 PendingMasters::value_type(new_master_resource, PendingHosts()));
370 is_new_pending_master_entry = ret.second;
371 ret.first->second.push_back(host);
372 host->AddObserver(this);
375 // Notify host (if any) if already checking or downloading.
376 AppCacheGroup::UpdateStatus update_status = group_->update_status();
377 if (update_status == AppCacheGroup::CHECKING ||
378 update_status == AppCacheGroup::DOWNLOADING) {
380 NotifySingleHost(host, CHECKING_EVENT);
381 if (update_status == AppCacheGroup::DOWNLOADING)
382 NotifySingleHost(host, DOWNLOADING_EVENT);
384 // Add to fetch list or an existing entry if already fetched.
385 if (!new_master_resource.is_empty()) {
386 AddMasterEntryToFetchList(host, new_master_resource,
387 is_new_pending_master_entry);
393 // Begin update process for the group.
394 group_->SetUpdateStatus(AppCacheGroup::CHECKING);
395 if (group_->HasCache()) {
396 update_type_ = UPGRADE_ATTEMPT;
397 NotifyAllAssociatedHosts(CHECKING_EVENT);
399 update_type_ = CACHE_ATTEMPT;
401 NotifySingleHost(host, CHECKING_EVENT);
404 if (!new_master_resource.is_empty()) {
405 AddMasterEntryToFetchList(host, new_master_resource,
406 is_new_pending_master_entry);
412 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
413 AppCacheResponseWriter* writer =
414 storage_->CreateResponseWriter(manifest_url_,
416 stored_response_ids_.push_back(writer->response_id());
420 void AppCacheUpdateJob::HandleCacheFailure(
421 const std::string& error_message,
423 // 6.9.4 cache failure steps 2-8.
424 DCHECK(internal_state_ != CACHE_FAILURE);
425 DCHECK(!error_message.empty());
426 DCHECK(result != UPDATE_OK);
427 internal_state_ = CACHE_FAILURE;
428 CancelAllUrlFetches();
429 CancelAllMasterEntryFetches(error_message);
430 NotifyAllError(error_message);
431 DiscardInprogressCache();
432 internal_state_ = COMPLETED;
433 AppCacheHistograms::CountUpdateJobResult(
434 result, manifest_url_.GetOrigin());
435 DeleteSoon(); // To unwind the stack prior to deletion.
438 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
439 DCHECK(!manifest_fetcher_);
440 manifest_fetcher_ = new URLFetcher(
442 is_first_fetch ? URLFetcher::MANIFEST_FETCH :
443 URLFetcher::MANIFEST_REFETCH,
446 // Add any necessary Http headers before sending fetch request.
447 if (is_first_fetch) {
448 AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
449 group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
451 // Asynchronously load response info for manifest from newest cache.
452 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
453 entry->response_id(), this);
455 manifest_fetcher_->Start();
458 DCHECK(internal_state_ == REFETCH_MANIFEST);
459 DCHECK(manifest_response_info_.get());
460 manifest_fetcher_->set_existing_response_headers(
461 manifest_response_info_->headers.get());
462 manifest_fetcher_->Start();
467 void AppCacheUpdateJob::HandleManifestFetchCompleted(
468 URLFetcher* fetcher) {
469 DCHECK_EQ(internal_state_, FETCH_MANIFEST);
470 DCHECK_EQ(manifest_fetcher_, fetcher);
471 manifest_fetcher_ = NULL;
473 net::URLRequest* request = fetcher->request();
474 int response_code = -1;
475 bool is_valid_response_code = false;
476 if (request->status().is_success()) {
477 response_code = request->GetResponseCode();
478 is_valid_response_code = (response_code / 100 == 2);
481 if (is_valid_response_code) {
482 manifest_data_ = fetcher->manifest_data();
483 manifest_response_info_.reset(
484 new net::HttpResponseInfo(request->response_info()));
485 if (update_type_ == UPGRADE_ATTEMPT)
486 CheckIfManifestChanged(); // continues asynchronously
488 ContinueHandleManifestFetchCompleted(true);
489 } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
490 ContinueHandleManifestFetchCompleted(false);
491 } else if ((response_code == 404 || response_code == 410) &&
492 update_type_ == UPGRADE_ATTEMPT) {
493 storage_->MakeGroupObsolete(group_, this); // async
495 const char* kFormatString = "Manifest fetch failed (%d) %s";
496 std::string message = FormatUrlErrorMessage(
497 kFormatString, manifest_url_, fetcher->result(), response_code);
498 HandleCacheFailure(message, fetcher->result());
502 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
504 DCHECK(master_entry_fetches_.empty());
505 CancelAllMasterEntryFetches("The cache has been made obsolete, "
506 "the manifest file returned 404 or 410");
508 DCHECK(group->is_obsolete());
509 NotifyAllAssociatedHosts(OBSOLETE_EVENT);
510 internal_state_ = COMPLETED;
511 MaybeCompleteUpdate();
513 // Treat failure to mark group obsolete as a cache failure.
514 HandleCacheFailure("Failed to mark the cache as obsolete", DB_ERROR);
518 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
519 DCHECK(internal_state_ == FETCH_MANIFEST);
522 DCHECK(update_type_ == UPGRADE_ATTEMPT);
523 internal_state_ = NO_UPDATE;
525 // Wait for pending master entries to download.
526 FetchMasterEntries();
527 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
532 if (!ParseManifest(manifest_url_, manifest_data_.data(),
533 manifest_data_.length(), manifest)) {
534 const char* kFormatString = "Failed to parse manifest %s";
535 const std::string message = base::StringPrintf(kFormatString,
536 manifest_url_.spec().c_str());
537 HandleCacheFailure(message, MANIFEST_ERROR);
542 // Proceed with update process. Section 6.9.4 steps 8-20.
543 internal_state_ = DOWNLOADING;
544 inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
545 BuildUrlFileList(manifest);
546 inprogress_cache_->InitializeWithManifest(&manifest);
548 // Associate all pending master hosts with the newly created cache.
549 for (PendingMasters::iterator it = pending_master_entries_.begin();
550 it != pending_master_entries_.end(); ++it) {
551 PendingHosts& hosts = it->second;
552 for (PendingHosts::iterator host_it = hosts.begin();
553 host_it != hosts.end(); ++host_it) {
555 ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
559 group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING);
560 NotifyAllAssociatedHosts(DOWNLOADING_EVENT);
562 FetchMasterEntries();
563 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
566 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
567 DCHECK(internal_state_ == DOWNLOADING);
569 net::URLRequest* request = fetcher->request();
570 const GURL& url = request->original_url();
571 pending_url_fetches_.erase(url);
572 NotifyAllProgress(url);
573 ++url_fetches_completed_;
575 int response_code = request->status().is_success()
576 ? request->GetResponseCode() : -1;
577 AppCacheEntry& entry = url_file_list_.find(url)->second;
579 if (response_code / 100 == 2) {
580 // Associate storage with the new entry.
581 DCHECK(fetcher->response_writer());
582 entry.set_response_id(fetcher->response_writer()->response_id());
583 entry.set_response_size(fetcher->response_writer()->amount_written());
584 if (!inprogress_cache_->AddOrModifyEntry(url, entry))
585 duplicate_response_ids_.push_back(entry.response_id());
587 // TODO(michaeln): Check for <html manifest=xxx>
588 // See http://code.google.com/p/chromium/issues/detail?id=97930
589 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
590 // if (!manifestAttribute) skip it
592 // Foreign entries will be detected during cache selection.
593 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
594 // file whose root element is an html element with a manifest attribute
595 // whose value doesn't match the manifest url of the application cache
596 // being processed, mark the entry as being foreign.
598 VLOG(1) << "Request status: " << request->status().status()
599 << " error: " << request->status().error()
600 << " response code: " << response_code;
601 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
602 if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
603 // Keep the existing response.
604 entry.set_response_id(fetcher->existing_entry().response_id());
605 entry.set_response_size(fetcher->existing_entry().response_size());
606 inprogress_cache_->AddOrModifyEntry(url, entry);
608 const char* kFormatString = "Resource fetch failed (%d) %s";
609 std::string message = FormatUrlErrorMessage(
610 kFormatString, url, fetcher->result(), response_code);
611 HandleCacheFailure(message, fetcher->result());
614 } else if (response_code == 404 || response_code == 410) {
615 // Entry is skipped. They are dropped from the cache.
616 } else if (update_type_ == UPGRADE_ATTEMPT &&
617 fetcher->existing_entry().has_response_id()) {
618 // Keep the existing response.
619 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
620 // but the old resource may or may not be compatible with the new contents
621 // of the cache. Impossible to know one way or the other.
622 entry.set_response_id(fetcher->existing_entry().response_id());
623 entry.set_response_size(fetcher->existing_entry().response_size());
624 inprogress_cache_->AddOrModifyEntry(url, entry);
628 // Fetch another URL now that one request has completed.
629 DCHECK(internal_state_ != CACHE_FAILURE);
631 MaybeCompleteUpdate();
634 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
635 URLFetcher* fetcher) {
636 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
638 // TODO(jennb): Handle downloads completing during cache failure when update
639 // no longer fetches master entries directly. For now, we cancel all pending
640 // master entry fetches when entering cache failure state so this will never
641 // be called in CACHE_FAILURE state.
643 net::URLRequest* request = fetcher->request();
644 const GURL& url = request->original_url();
645 master_entry_fetches_.erase(url);
646 ++master_entries_completed_;
648 int response_code = request->status().is_success()
649 ? request->GetResponseCode() : -1;
651 PendingMasters::iterator found = pending_master_entries_.find(url);
652 DCHECK(found != pending_master_entries_.end());
653 PendingHosts& hosts = found->second;
655 // Section 6.9.4. No update case: step 7.3, else step 22.
656 if (response_code / 100 == 2) {
657 // Add fetched master entry to the appropriate cache.
658 AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
659 : group_->newest_complete_cache();
660 DCHECK(fetcher->response_writer());
661 AppCacheEntry master_entry(AppCacheEntry::MASTER,
662 fetcher->response_writer()->response_id(),
663 fetcher->response_writer()->amount_written());
664 if (cache->AddOrModifyEntry(url, master_entry))
665 added_master_entries_.push_back(url);
667 duplicate_response_ids_.push_back(master_entry.response_id());
669 // In no-update case, associate host with the newest cache.
670 if (!inprogress_cache_.get()) {
671 // TODO(michaeln): defer until the updated cache has been stored
672 DCHECK(cache == group_->newest_complete_cache());
673 for (PendingHosts::iterator host_it = hosts.begin();
674 host_it != hosts.end(); ++host_it) {
675 (*host_it)->AssociateCompleteCache(cache);
679 HostNotifier host_notifier;
680 for (PendingHosts::iterator host_it = hosts.begin();
681 host_it != hosts.end(); ++host_it) {
682 AppCacheHost* host = *host_it;
683 host_notifier.AddHost(host);
685 // In downloading case, disassociate host from inprogress cache.
686 if (inprogress_cache_.get())
687 host->AssociateNoCache(GURL());
689 host->RemoveObserver(this);
693 const char* kFormatString = "Manifest fetch failed (%d) %s";
694 std::string message = FormatUrlErrorMessage(
695 kFormatString, request->url(), fetcher->result(), response_code);
696 host_notifier.SendErrorNotifications(message);
698 // In downloading case, update result is different if all master entries
699 // failed vs. only some failing.
700 if (inprogress_cache_.get()) {
701 // Only count successful downloads to know if all master entries failed.
702 pending_master_entries_.erase(found);
703 --master_entries_completed_;
705 // Section 6.9.4, step 22.3.
706 if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
707 HandleCacheFailure(message, fetcher->result());
713 DCHECK(internal_state_ != CACHE_FAILURE);
714 FetchMasterEntries();
715 MaybeCompleteUpdate();
718 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
719 URLFetcher* fetcher) {
720 DCHECK(internal_state_ == REFETCH_MANIFEST);
721 DCHECK(manifest_fetcher_ == fetcher);
722 manifest_fetcher_ = NULL;
724 net::URLRequest* request = fetcher->request();
725 int response_code = request->status().is_success()
726 ? request->GetResponseCode() : -1;
727 if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
728 // Only need to store response in storage if manifest is not already
729 // an entry in the cache.
730 AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
732 entry->add_types(AppCacheEntry::MANIFEST);
733 StoreGroupAndCache();
735 manifest_response_writer_.reset(CreateResponseWriter());
736 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
737 new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
738 manifest_response_writer_->WriteInfo(
740 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
741 base::Unretained(this)));
744 VLOG(1) << "Request status: " << request->status().status()
745 << " error: " << request->status().error()
746 << " response code: " << response_code;
747 ScheduleUpdateRetry(kRerunDelayMs);
748 if (response_code == 200) {
749 HandleCacheFailure("Manifest changed during update", MANIFEST_ERROR);
751 const char* kFormatString = "Manifest re-fetch failed (%d) %s";
752 std::string message = FormatUrlErrorMessage(
753 kFormatString, manifest_url_, fetcher->result(), response_code);
754 HandleCacheFailure(message, fetcher->result());
759 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
761 scoped_refptr<net::StringIOBuffer> io_buffer(
762 new net::StringIOBuffer(manifest_data_));
763 manifest_response_writer_->WriteData(
765 manifest_data_.length(),
766 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
767 base::Unretained(this)));
769 HandleCacheFailure("Failed to write the manifest headers to storage",
774 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
776 AppCacheEntry entry(AppCacheEntry::MANIFEST,
777 manifest_response_writer_->response_id(),
778 manifest_response_writer_->amount_written());
779 if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
780 duplicate_response_ids_.push_back(entry.response_id());
781 StoreGroupAndCache();
783 HandleCacheFailure("Failed to write the manifest data to storage",
788 void AppCacheUpdateJob::StoreGroupAndCache() {
789 DCHECK(stored_state_ == UNSTORED);
790 stored_state_ = STORING;
791 scoped_refptr<AppCache> newest_cache;
792 if (inprogress_cache_.get())
793 newest_cache.swap(inprogress_cache_);
795 newest_cache = group_->newest_complete_cache();
796 newest_cache->set_update_time(base::Time::Now());
798 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
799 DCHECK_EQ(manifest_url_, group_->manifest_url());
800 storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
803 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
804 AppCache* newest_cache,
806 bool would_exceed_quota) {
807 DCHECK(stored_state_ == STORING);
809 stored_state_ = STORED;
810 MaybeCompleteUpdate(); // will definitely complete
812 stored_state_ = UNSTORED;
814 // Restore inprogress_cache_ to get the proper events delivered
815 // and the proper cleanup to occur.
816 if (newest_cache != group->newest_complete_cache())
817 inprogress_cache_ = newest_cache;
819 ResultType result = DB_ERROR;
820 std::string message("Failed to commit new cache to storage");
821 if (would_exceed_quota) {
822 message.append(", would exceed quota");
823 result = QUOTA_ERROR;
825 HandleCacheFailure(message, result);
829 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
831 std::vector<int> ids(1, host->host_id());
832 host->frontend()->OnEventRaised(ids, event_id);
835 void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) {
836 HostNotifier host_notifier;
837 AddAllAssociatedHostsToNotifier(&host_notifier);
838 host_notifier.SendNotifications(event_id);
841 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
842 HostNotifier host_notifier;
843 AddAllAssociatedHostsToNotifier(&host_notifier);
844 host_notifier.SendProgressNotifications(
845 url, url_file_list_.size(), url_fetches_completed_);
848 void AppCacheUpdateJob::NotifyAllFinalProgress() {
849 DCHECK(url_file_list_.size() == url_fetches_completed_);
850 NotifyAllProgress(GURL());
853 void AppCacheUpdateJob::NotifyAllError(const std::string& error_message) {
854 HostNotifier host_notifier;
855 AddAllAssociatedHostsToNotifier(&host_notifier);
856 host_notifier.SendErrorNotifications(error_message);
859 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
860 HostNotifier* host_notifier) {
861 // Collect hosts so we only send one notification per frontend.
862 // A host can only be associated with a single cache so no need to worry
863 // about duplicate hosts being added to the notifier.
864 if (inprogress_cache_.get()) {
865 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
866 host_notifier->AddHosts(inprogress_cache_->associated_hosts());
869 AppCacheGroup::Caches old_caches = group_->old_caches();
870 for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
871 it != old_caches.end(); ++it) {
872 host_notifier->AddHosts((*it)->associated_hosts());
875 AppCache* newest_cache = group_->newest_complete_cache();
877 host_notifier->AddHosts(newest_cache->associated_hosts());
880 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
881 // The host is about to be deleted; remove from our collection.
882 PendingMasters::iterator found =
883 pending_master_entries_.find(host->pending_master_entry_url());
884 DCHECK(found != pending_master_entries_.end());
885 PendingHosts& hosts = found->second;
886 PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
887 DCHECK(it != hosts.end());
891 void AppCacheUpdateJob::OnServiceReinitialized(
892 AppCacheStorageReference* old_storage_ref) {
893 // We continue to use the disabled instance, but arrange for its
894 // deletion when its no longer needed.
895 if (old_storage_ref->storage() == storage_)
896 disabled_storage_reference_ = old_storage_ref;
899 void AppCacheUpdateJob::CheckIfManifestChanged() {
900 DCHECK(update_type_ == UPGRADE_ATTEMPT);
901 AppCacheEntry* entry = NULL;
902 if (group_->newest_complete_cache())
903 entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
905 // TODO(michaeln): This is just a bandaid to avoid a crash.
906 // http://code.google.com/p/chromium/issues/detail?id=95101
907 if (service_->storage() == storage_) {
908 // Use a local variable because service_ is reset in HandleCacheFailure.
909 AppCacheService* service = service_;
910 HandleCacheFailure("Manifest entry not found in existing cache",
912 AppCacheHistograms::AddMissingManifestEntrySample();
913 service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
918 // Load manifest data from storage to compare against fetched manifest.
919 manifest_response_reader_.reset(
920 storage_->CreateResponseReader(manifest_url_,
922 entry->response_id()));
923 read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
924 manifest_response_reader_->ReadData(
925 read_manifest_buffer_.get(),
927 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
928 base::Unretained(this))); // async read
931 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
933 loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
934 manifest_response_reader_->ReadData(
935 read_manifest_buffer_.get(),
937 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
938 base::Unretained(this))); // read more
940 read_manifest_buffer_ = NULL;
941 manifest_response_reader_.reset();
942 ContinueHandleManifestFetchCompleted(
943 result < 0 || manifest_data_ != loaded_manifest_data_);
947 void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) {
948 for (base::hash_set<std::string>::const_iterator it =
949 manifest.explicit_urls.begin();
950 it != manifest.explicit_urls.end(); ++it) {
951 AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
954 const std::vector<Namespace>& intercepts =
955 manifest.intercept_namespaces;
956 for (std::vector<Namespace>::const_iterator it = intercepts.begin();
957 it != intercepts.end(); ++it) {
958 int flags = AppCacheEntry::INTERCEPT;
959 if (it->is_executable)
960 flags |= AppCacheEntry::EXECUTABLE;
961 AddUrlToFileList(it->target_url, flags);
964 const std::vector<Namespace>& fallbacks =
965 manifest.fallback_namespaces;
966 for (std::vector<Namespace>::const_iterator it = fallbacks.begin();
967 it != fallbacks.end(); ++it) {
968 AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
971 // Add all master entries from newest complete cache.
972 if (update_type_ == UPGRADE_ATTEMPT) {
973 const AppCache::EntryMap& entries =
974 group_->newest_complete_cache()->entries();
975 for (AppCache::EntryMap::const_iterator it = entries.begin();
976 it != entries.end(); ++it) {
977 const AppCacheEntry& entry = it->second;
978 if (entry.IsMaster())
979 AddUrlToFileList(it->first, AppCacheEntry::MASTER);
984 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
985 std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
986 AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
989 urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
991 ret.first->second.add_types(type); // URL already exists. Merge types.
994 void AppCacheUpdateJob::FetchUrls() {
995 DCHECK(internal_state_ == DOWNLOADING);
997 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
998 // Fetch up to the concurrent limit. Other fetches will be triggered as each
999 // each fetch completes.
1000 while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
1001 !urls_to_fetch_.empty()) {
1002 UrlToFetch url_to_fetch = urls_to_fetch_.front();
1003 urls_to_fetch_.pop_front();
1005 AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
1006 DCHECK(it != url_file_list_.end());
1007 AppCacheEntry& entry = it->second;
1008 if (ShouldSkipUrlFetch(entry)) {
1009 NotifyAllProgress(url_to_fetch.url);
1010 ++url_fetches_completed_;
1011 } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
1012 NotifyAllProgress(url_to_fetch.url);
1013 ++url_fetches_completed_; // saved a URL request
1014 } else if (!url_to_fetch.storage_checked &&
1015 MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
1016 // Continues asynchronously after data is loaded from newest cache.
1018 URLFetcher* fetcher = new URLFetcher(
1019 url_to_fetch.url, URLFetcher::URL_FETCH, this);
1020 if (url_to_fetch.existing_response_info.get()) {
1021 DCHECK(group_->newest_complete_cache());
1022 AppCacheEntry* existing_entry =
1023 group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
1024 DCHECK(existing_entry);
1025 DCHECK(existing_entry->response_id() ==
1026 url_to_fetch.existing_response_info->response_id());
1027 fetcher->set_existing_response_headers(
1028 url_to_fetch.existing_response_info->http_response_info()->headers
1030 fetcher->set_existing_entry(*existing_entry);
1033 pending_url_fetches_.insert(
1034 PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
1039 void AppCacheUpdateJob::CancelAllUrlFetches() {
1040 // Cancel any pending URL requests.
1041 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1042 it != pending_url_fetches_.end(); ++it) {
1046 url_fetches_completed_ +=
1047 pending_url_fetches_.size() + urls_to_fetch_.size();
1048 pending_url_fetches_.clear();
1049 urls_to_fetch_.clear();
1052 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
1054 // If the resource URL being processed was flagged as neither an
1055 // "explicit entry" nor or a "fallback entry", then the user agent
1056 // may skip this URL.
1057 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
1060 // TODO(jennb): decide if entry should be skipped to expire it from cache
1064 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
1066 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
1067 AppCacheEntry* existing =
1068 inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
1069 : group_->newest_complete_cache()->GetEntry(url);
1071 existing->add_types(entry_type);
1077 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
1080 DCHECK(!IsTerminating());
1082 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
1084 if (inprogress_cache_.get()) {
1086 host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
1087 cache = inprogress_cache_.get();
1089 cache = group_->newest_complete_cache();
1092 // Update existing entry if it has already been fetched.
1093 AppCacheEntry* entry = cache->GetEntry(url);
1095 entry->add_types(AppCacheEntry::MASTER);
1096 if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
1097 // only associate if have entry
1098 host->AssociateCompleteCache(cache);
1101 ++master_entries_completed_; // pretend fetching completed
1106 // Add to fetch list if not already fetching.
1107 if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
1108 master_entries_to_fetch_.insert(url);
1109 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
1110 FetchMasterEntries();
1114 void AppCacheUpdateJob::FetchMasterEntries() {
1115 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
1117 // Fetch each master entry in the list, up to the concurrent limit.
1118 // Additional fetches will be triggered as each fetch completes.
1119 while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
1120 !master_entries_to_fetch_.empty()) {
1121 const GURL& url = *master_entries_to_fetch_.begin();
1123 if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
1124 ++master_entries_completed_; // saved a URL request
1126 // In no update case, associate hosts to newest cache in group
1127 // now that master entry has been "successfully downloaded".
1128 if (internal_state_ == NO_UPDATE) {
1129 // TODO(michaeln): defer until the updated cache has been stored.
1130 DCHECK(!inprogress_cache_.get());
1131 AppCache* cache = group_->newest_complete_cache();
1132 PendingMasters::iterator found = pending_master_entries_.find(url);
1133 DCHECK(found != pending_master_entries_.end());
1134 PendingHosts& hosts = found->second;
1135 for (PendingHosts::iterator host_it = hosts.begin();
1136 host_it != hosts.end(); ++host_it) {
1137 (*host_it)->AssociateCompleteCache(cache);
1141 URLFetcher* fetcher = new URLFetcher(
1142 url, URLFetcher::MASTER_ENTRY_FETCH, this);
1144 master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
1147 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1151 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1152 const std::string& error_message) {
1153 // For now, cancel all in-progress fetches for master entries and pretend
1154 // all master entries fetches have completed.
1155 // TODO(jennb): Delete this when update no longer fetches master entries
1158 // Cancel all in-progress fetches.
1159 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1160 it != master_entry_fetches_.end(); ++it) {
1162 master_entries_to_fetch_.insert(it->first); // back in unfetched list
1164 master_entry_fetches_.clear();
1166 master_entries_completed_ += master_entries_to_fetch_.size();
1168 // Cache failure steps, step 2.
1169 // Pretend all master entries that have not yet been fetched have completed
1170 // downloading. Unassociate hosts from any appcache and send ERROR event.
1171 HostNotifier host_notifier;
1172 while (!master_entries_to_fetch_.empty()) {
1173 const GURL& url = *master_entries_to_fetch_.begin();
1174 PendingMasters::iterator found = pending_master_entries_.find(url);
1175 DCHECK(found != pending_master_entries_.end());
1176 PendingHosts& hosts = found->second;
1177 for (PendingHosts::iterator host_it = hosts.begin();
1178 host_it != hosts.end(); ++host_it) {
1179 AppCacheHost* host = *host_it;
1180 host->AssociateNoCache(GURL());
1181 host_notifier.AddHost(host);
1182 host->RemoveObserver(this);
1186 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1188 host_notifier.SendErrorNotifications(error_message);
1191 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
1192 AppCacheEntry& entry) {
1193 if (update_type_ != UPGRADE_ATTEMPT)
1196 AppCache* newest = group_->newest_complete_cache();
1197 AppCacheEntry* copy_me = newest->GetEntry(url);
1198 if (!copy_me || !copy_me->has_response_id())
1201 // Load HTTP headers for entry from newest cache.
1202 loading_responses_.insert(
1203 LoadingResponses::value_type(copy_me->response_id(), url));
1204 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
1205 copy_me->response_id(),
1207 // Async: wait for OnResponseInfoLoaded to complete.
1211 void AppCacheUpdateJob::OnResponseInfoLoaded(
1212 AppCacheResponseInfo* response_info, int64 response_id) {
1213 const net::HttpResponseInfo* http_info = response_info ?
1214 response_info->http_response_info() : NULL;
1216 // Needed response info for a manifest fetch request.
1217 if (internal_state_ == FETCH_MANIFEST) {
1219 manifest_fetcher_->set_existing_response_headers(
1220 http_info->headers.get());
1221 manifest_fetcher_->Start();
1225 LoadingResponses::iterator found = loading_responses_.find(response_id);
1226 DCHECK(found != loading_responses_.end());
1227 const GURL& url = found->second;
1230 LoadFromNewestCacheFailed(url, NULL); // no response found
1232 // Check if response can be re-used according to HTTP caching semantics.
1233 // Responses with a "vary" header get treated as expired.
1234 const std::string name = "vary";
1237 if (!http_info->headers.get() ||
1238 http_info->headers->RequiresValidation(http_info->request_time,
1239 http_info->response_time,
1240 base::Time::Now()) ||
1241 http_info->headers->EnumerateHeader(&iter, name, &value)) {
1242 LoadFromNewestCacheFailed(url, response_info);
1244 DCHECK(group_->newest_complete_cache());
1245 AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
1247 DCHECK(copy_me->response_id() == response_id);
1249 AppCache::EntryMap::iterator it = url_file_list_.find(url);
1250 DCHECK(it != url_file_list_.end());
1251 AppCacheEntry& entry = it->second;
1252 entry.set_response_id(response_id);
1253 entry.set_response_size(copy_me->response_size());
1254 inprogress_cache_->AddOrModifyEntry(url, entry);
1255 NotifyAllProgress(url);
1256 ++url_fetches_completed_;
1259 loading_responses_.erase(found);
1261 MaybeCompleteUpdate();
1264 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1265 const GURL& url, AppCacheResponseInfo* response_info) {
1266 if (internal_state_ == CACHE_FAILURE)
1269 // Re-insert url at front of fetch list. Indicate storage has been checked.
1270 urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
1274 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1275 DCHECK(internal_state_ != CACHE_FAILURE);
1277 // Must wait for any pending master entries or url fetches to complete.
1278 if (master_entries_completed_ != pending_master_entries_.size() ||
1279 url_fetches_completed_ != url_file_list_.size()) {
1280 DCHECK(internal_state_ != COMPLETED);
1284 switch (internal_state_) {
1286 if (master_entries_completed_ > 0) {
1287 switch (stored_state_) {
1289 StoreGroupAndCache();
1297 // 6.9.4 steps 7.3-7.7.
1298 NotifyAllAssociatedHosts(NO_UPDATE_EVENT);
1299 DiscardDuplicateResponses();
1300 internal_state_ = COMPLETED;
1303 internal_state_ = REFETCH_MANIFEST;
1304 FetchManifest(false);
1306 case REFETCH_MANIFEST:
1307 DCHECK(stored_state_ == STORED);
1308 NotifyAllFinalProgress();
1309 if (update_type_ == CACHE_ATTEMPT)
1310 NotifyAllAssociatedHosts(CACHED_EVENT);
1312 NotifyAllAssociatedHosts(UPDATE_READY_EVENT);
1313 DiscardDuplicateResponses();
1314 internal_state_ = COMPLETED;
1315 AppCacheHistograms::CountUpdateJobResult(
1316 UPDATE_OK, manifest_url_.GetOrigin());
1319 NOTREACHED(); // See HandleCacheFailure
1325 // Let the stack unwind before deletion to make it less risky as this
1326 // method is called from multiple places in this file.
1327 if (internal_state_ == COMPLETED)
1331 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
1332 // TODO(jennb): post a delayed task with the "same parameters" as this job
1333 // to retry the update at a later time. Need group, URLs of pending master
1334 // entries and their hosts.
1337 void AppCacheUpdateJob::Cancel() {
1338 internal_state_ = CANCELLED;
1340 if (manifest_fetcher_) {
1341 delete manifest_fetcher_;
1342 manifest_fetcher_ = NULL;
1345 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1346 it != pending_url_fetches_.end(); ++it) {
1349 pending_url_fetches_.clear();
1351 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1352 it != master_entry_fetches_.end(); ++it) {
1355 master_entry_fetches_.clear();
1357 ClearPendingMasterEntries();
1358 DiscardInprogressCache();
1360 // Delete response writer to avoid any callbacks.
1361 if (manifest_response_writer_)
1362 manifest_response_writer_.reset();
1364 storage_->CancelDelegateCallbacks(this);
1367 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1368 for (PendingMasters::iterator it = pending_master_entries_.begin();
1369 it != pending_master_entries_.end(); ++it) {
1370 PendingHosts& hosts = it->second;
1371 for (PendingHosts::iterator host_it = hosts.begin();
1372 host_it != hosts.end(); ++host_it) {
1373 (*host_it)->RemoveObserver(this);
1377 pending_master_entries_.clear();
1380 void AppCacheUpdateJob::DiscardInprogressCache() {
1381 if (stored_state_ == STORING) {
1382 // We can make no assumptions about whether the StoreGroupAndCacheTask
1383 // actually completed or not. This condition should only be reachable
1384 // during shutdown. Free things up and return to do no harm.
1385 inprogress_cache_ = NULL;
1386 added_master_entries_.clear();
1390 storage_->DoomResponses(manifest_url_, stored_response_ids_);
1392 if (!inprogress_cache_.get()) {
1393 // We have to undo the changes we made, if any, to the existing cache.
1394 if (group_ && group_->newest_complete_cache()) {
1395 for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
1396 iter != added_master_entries_.end(); ++iter) {
1397 group_->newest_complete_cache()->RemoveEntry(*iter);
1400 added_master_entries_.clear();
1404 AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
1405 while (!hosts.empty())
1406 (*hosts.begin())->AssociateNoCache(GURL());
1408 inprogress_cache_ = NULL;
1409 added_master_entries_.clear();
1412 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1413 storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
1416 void AppCacheUpdateJob::DeleteSoon() {
1417 ClearPendingMasterEntries();
1418 manifest_response_writer_.reset();
1419 storage_->CancelDelegateCallbacks(this);
1420 service_->RemoveObserver(this);
1423 // Break the connection with the group so the group cannot call delete
1424 // on this object after we've posted a task to delete ourselves.
1425 group_->SetUpdateStatus(AppCacheGroup::IDLE);
1428 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
1431 } // namespace appcache