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 ErrorDetails& details) {
80 DCHECK(!details.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, details);
88 void SendLogMessage(const std::string& message) {
89 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
90 it != hosts_to_notify.end(); ++it) {
91 AppCacheFrontend* frontend = it->first;
92 for (HostIds::iterator id = it->second.begin();
93 id != it->second.end(); ++id) {
94 frontend->OnLogMessage(*id, LOG_WARNING, message);
100 NotifyHostMap hosts_to_notify;
103 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
105 AppCacheResponseInfo* info)
107 storage_checked(checked),
108 existing_response_info(info) {
111 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
114 // Helper class to fetch resources. Depending on the fetch type,
115 // can either fetch to an in-memory string or write the response
116 // data out to the disk cache.
117 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
118 FetchType fetch_type,
119 AppCacheUpdateJob* job)
122 fetch_type_(fetch_type),
123 retry_503_attempts_(0),
124 buffer_(new net::IOBuffer(kBufferSize)),
125 request_(job->service_->request_context()
126 ->CreateRequest(url, net::DEFAULT_PRIORITY, this, NULL)),
128 redirect_response_code_(-1) {}
130 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
133 void AppCacheUpdateJob::URLFetcher::Start() {
134 request_->set_first_party_for_cookies(job_->manifest_url_);
135 request_->SetLoadFlags(request_->load_flags() |
136 net::LOAD_DISABLE_INTERCEPT);
137 if (existing_response_headers_.get())
138 AddConditionalHeaders(existing_response_headers_.get());
142 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
143 net::URLRequest* request, const GURL& new_url, bool* defer_redirect) {
144 DCHECK(request_ == request);
145 // Redirect is not allowed by the update process.
146 job_->MadeProgress();
147 redirect_response_code_ = request->GetResponseCode();
149 result_ = REDIRECT_ERROR;
150 OnResponseCompleted();
153 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
154 net::URLRequest *request) {
155 DCHECK(request == request_);
156 int response_code = -1;
157 if (request->status().is_success()) {
158 response_code = request->GetResponseCode();
159 job_->MadeProgress();
161 if ((response_code / 100) == 2) {
163 // See http://code.google.com/p/chromium/issues/detail?id=69594
164 // We willfully violate the HTML5 spec at this point in order
165 // to support the appcaching of cross-origin HTTPS resources.
166 // We've opted for a milder constraint and allow caching unless
167 // the resource has a "no-store" header. A spec change has been
168 // requested on the whatwg list.
169 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
170 if (url_.SchemeIsSecure() &&
171 url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
172 if (request->response_headers()->
173 HasHeaderValue("cache-control", "no-store")) {
174 DCHECK_EQ(-1, redirect_response_code_);
176 result_ = SERVER_ERROR; // Not the best match?
177 OnResponseCompleted();
182 // Write response info to storage for URL fetches. Wait for async write
183 // completion before reading any response data.
184 if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
185 response_writer_.reset(job_->CreateResponseWriter());
186 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
187 new HttpResponseInfoIOBuffer(
188 new net::HttpResponseInfo(request->response_info())));
189 response_writer_->WriteInfo(
191 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
196 if (response_code > 0)
197 result_ = SERVER_ERROR;
199 result_ = NETWORK_ERROR;
200 OnResponseCompleted();
204 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
205 net::URLRequest* request, int bytes_read) {
206 DCHECK(request_ == request);
207 bool data_consumed = true;
208 if (request->status().is_success() && bytes_read > 0) {
209 job_->MadeProgress();
210 data_consumed = ConsumeResponseData(bytes_read);
213 while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
214 if (bytes_read > 0) {
215 data_consumed = ConsumeResponseData(bytes_read);
217 break; // wait for async data processing, then read more
224 if (data_consumed && !request->status().is_io_pending()) {
225 DCHECK_EQ(UPDATE_OK, result_);
226 OnResponseCompleted();
230 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
231 const net::HttpResponseHeaders* headers) {
232 DCHECK(request_.get() && headers);
233 net::HttpRequestHeaders extra_headers;
235 // Add If-Modified-Since header if response info has Last-Modified header.
236 const std::string last_modified = "Last-Modified";
237 std::string last_modified_value;
238 headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
239 if (!last_modified_value.empty()) {
240 extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
241 last_modified_value);
244 // Add If-None-Match header if response info has ETag header.
245 const std::string etag = "ETag";
246 std::string etag_value;
247 headers->EnumerateHeader(NULL, etag, &etag_value);
248 if (!etag_value.empty()) {
249 extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
252 if (!extra_headers.IsEmpty())
253 request_->SetExtraRequestHeaders(extra_headers);
256 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
259 result_ = DISKCACHE_ERROR;
260 OnResponseCompleted();
266 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
267 InternalUpdateState state = job_->internal_state_;
268 if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
271 request_->Read(buffer_.get(), kBufferSize, &bytes_read);
272 OnReadCompleted(request_.get(), bytes_read);
275 // Returns false if response data is processed asynchronously, in which
276 // case ReadResponseData will be invoked when it is safe to continue
277 // reading more response data from the request.
278 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
279 DCHECK_GT(bytes_read, 0);
280 switch (fetch_type_) {
282 case MANIFEST_REFETCH:
283 manifest_data_.append(buffer_->data(), bytes_read);
286 case MASTER_ENTRY_FETCH:
287 DCHECK(response_writer_.get());
288 response_writer_->WriteData(
291 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
292 return false; // wait for async write completion to continue reading
299 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
300 if (request_->status().is_success())
301 job_->MadeProgress();
303 // Retry for 503s where retry-after is 0.
304 if (request_->status().is_success() &&
305 request_->GetResponseCode() == 503 &&
306 MaybeRetryRequest()) {
310 switch (fetch_type_) {
312 job_->HandleManifestFetchCompleted(this);
315 job_->HandleUrlFetchCompleted(this);
317 case MASTER_ENTRY_FETCH:
318 job_->HandleMasterEntryFetchCompleted(this);
320 case MANIFEST_REFETCH:
321 job_->HandleManifestRefetchCompleted(this);
330 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
331 if (retry_503_attempts_ >= kMax503Retries ||
332 !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
335 ++retry_503_attempts_;
337 request_ = job_->service_->request_context()->CreateRequest(
338 url_, net::DEFAULT_PRIORITY, this, NULL);
343 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service,
344 AppCacheGroup* group)
346 manifest_url_(group->manifest_url()),
348 update_type_(UNKNOWN_TYPE),
349 internal_state_(FETCH_MANIFEST),
350 master_entries_completed_(0),
351 url_fetches_completed_(0),
352 manifest_fetcher_(NULL),
353 manifest_has_valid_mime_type_(false),
354 stored_state_(UNSTORED),
355 storage_(service->storage()) {
356 service_->AddObserver(this);
359 AppCacheUpdateJob::~AppCacheUpdateJob() {
361 service_->RemoveObserver(this);
362 if (internal_state_ != COMPLETED)
365 DCHECK(!manifest_fetcher_);
366 DCHECK(pending_url_fetches_.empty());
367 DCHECK(!inprogress_cache_.get());
368 DCHECK(pending_master_entries_.empty());
369 DCHECK(master_entry_fetches_.empty());
372 group_->SetUpdateStatus(AppCacheGroup::IDLE);
375 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
376 const GURL& new_master_resource) {
377 DCHECK(group_->update_job() == this);
378 DCHECK(!group_->is_obsolete());
380 bool is_new_pending_master_entry = false;
381 if (!new_master_resource.is_empty()) {
382 DCHECK(new_master_resource == host->pending_master_entry_url());
383 DCHECK(!new_master_resource.has_ref());
384 DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
386 // Cannot add more to this update if already terminating.
387 if (IsTerminating()) {
388 group_->QueueUpdate(host, new_master_resource);
392 std::pair<PendingMasters::iterator, bool> ret =
393 pending_master_entries_.insert(
394 PendingMasters::value_type(new_master_resource, PendingHosts()));
395 is_new_pending_master_entry = ret.second;
396 ret.first->second.push_back(host);
397 host->AddObserver(this);
400 // Notify host (if any) if already checking or downloading.
401 AppCacheGroup::UpdateStatus update_status = group_->update_status();
402 if (update_status == AppCacheGroup::CHECKING ||
403 update_status == AppCacheGroup::DOWNLOADING) {
405 NotifySingleHost(host, CHECKING_EVENT);
406 if (update_status == AppCacheGroup::DOWNLOADING)
407 NotifySingleHost(host, DOWNLOADING_EVENT);
409 // Add to fetch list or an existing entry if already fetched.
410 if (!new_master_resource.is_empty()) {
411 AddMasterEntryToFetchList(host, new_master_resource,
412 is_new_pending_master_entry);
418 // Begin update process for the group.
420 group_->SetUpdateStatus(AppCacheGroup::CHECKING);
421 if (group_->HasCache()) {
422 update_type_ = UPGRADE_ATTEMPT;
423 NotifyAllAssociatedHosts(CHECKING_EVENT);
425 update_type_ = CACHE_ATTEMPT;
427 NotifySingleHost(host, CHECKING_EVENT);
430 if (!new_master_resource.is_empty()) {
431 AddMasterEntryToFetchList(host, new_master_resource,
432 is_new_pending_master_entry);
438 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
439 AppCacheResponseWriter* writer =
440 storage_->CreateResponseWriter(manifest_url_,
442 stored_response_ids_.push_back(writer->response_id());
446 void AppCacheUpdateJob::HandleCacheFailure(const ErrorDetails& error_details,
448 const GURL& failed_resource_url) {
449 // 6.9.4 cache failure steps 2-8.
450 DCHECK(internal_state_ != CACHE_FAILURE);
451 DCHECK(!error_details.message.empty());
452 DCHECK(result != UPDATE_OK);
453 internal_state_ = CACHE_FAILURE;
454 LogHistogramStats(result, failed_resource_url);
455 CancelAllUrlFetches();
456 CancelAllMasterEntryFetches(error_details);
457 NotifyAllError(error_details);
458 DiscardInprogressCache();
459 internal_state_ = COMPLETED;
460 DeleteSoon(); // To unwind the stack prior to deletion.
463 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
464 DCHECK(!manifest_fetcher_);
465 manifest_fetcher_ = new URLFetcher(
467 is_first_fetch ? URLFetcher::MANIFEST_FETCH :
468 URLFetcher::MANIFEST_REFETCH,
471 // Add any necessary Http headers before sending fetch request.
472 if (is_first_fetch) {
473 AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
474 group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
476 // Asynchronously load response info for manifest from newest cache.
477 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
478 entry->response_id(), this);
480 manifest_fetcher_->Start();
483 DCHECK(internal_state_ == REFETCH_MANIFEST);
484 DCHECK(manifest_response_info_.get());
485 manifest_fetcher_->set_existing_response_headers(
486 manifest_response_info_->headers.get());
487 manifest_fetcher_->Start();
492 void AppCacheUpdateJob::HandleManifestFetchCompleted(
493 URLFetcher* fetcher) {
494 DCHECK_EQ(internal_state_, FETCH_MANIFEST);
495 DCHECK_EQ(manifest_fetcher_, fetcher);
496 manifest_fetcher_ = NULL;
498 net::URLRequest* request = fetcher->request();
499 int response_code = -1;
500 bool is_valid_response_code = false;
501 if (request->status().is_success()) {
502 response_code = request->GetResponseCode();
503 is_valid_response_code = (response_code / 100 == 2);
505 std::string mime_type;
506 request->GetMimeType(&mime_type);
507 manifest_has_valid_mime_type_ = (mime_type == "text/cache-manifest");
510 if (is_valid_response_code) {
511 manifest_data_ = fetcher->manifest_data();
512 manifest_response_info_.reset(
513 new net::HttpResponseInfo(request->response_info()));
514 if (update_type_ == UPGRADE_ATTEMPT)
515 CheckIfManifestChanged(); // continues asynchronously
517 ContinueHandleManifestFetchCompleted(true);
518 } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
519 ContinueHandleManifestFetchCompleted(false);
520 } else if ((response_code == 404 || response_code == 410) &&
521 update_type_ == UPGRADE_ATTEMPT) {
522 storage_->MakeGroupObsolete(group_, this, response_code); // async
524 const char* kFormatString = "Manifest fetch failed (%d) %s";
525 std::string message = FormatUrlErrorMessage(
526 kFormatString, manifest_url_, fetcher->result(), response_code);
527 HandleCacheFailure(ErrorDetails(message,
528 appcache::MANIFEST_ERROR,
531 false /*is_cross_origin*/),
537 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
540 DCHECK(master_entry_fetches_.empty());
541 CancelAllMasterEntryFetches(ErrorDetails(
542 "The cache has been made obsolete, "
543 "the manifest file returned 404 or 410",
544 appcache::MANIFEST_ERROR,
547 false /*is_cross_origin*/));
549 DCHECK(group->is_obsolete());
550 NotifyAllAssociatedHosts(OBSOLETE_EVENT);
551 internal_state_ = COMPLETED;
552 MaybeCompleteUpdate();
554 // Treat failure to mark group obsolete as a cache failure.
555 HandleCacheFailure(ErrorDetails("Failed to mark the cache as obsolete",
559 false /*is_cross_origin*/),
565 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
566 DCHECK(internal_state_ == FETCH_MANIFEST);
569 DCHECK(update_type_ == UPGRADE_ATTEMPT);
570 internal_state_ = NO_UPDATE;
572 // Wait for pending master entries to download.
573 FetchMasterEntries();
574 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
579 if (!ParseManifest(manifest_url_, manifest_data_.data(),
580 manifest_data_.length(),
581 manifest_has_valid_mime_type_ ?
582 PARSE_MANIFEST_ALLOWING_INTERCEPTS :
583 PARSE_MANIFEST_PER_STANDARD,
585 const char* kFormatString = "Failed to parse manifest %s";
586 const std::string message = base::StringPrintf(kFormatString,
587 manifest_url_.spec().c_str());
590 message, SIGNATURE_ERROR, GURL(), 0, false /*is_cross_origin*/),
597 // Proceed with update process. Section 6.9.4 steps 8-20.
598 internal_state_ = DOWNLOADING;
599 inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
600 BuildUrlFileList(manifest);
601 inprogress_cache_->InitializeWithManifest(&manifest);
603 // Associate all pending master hosts with the newly created cache.
604 for (PendingMasters::iterator it = pending_master_entries_.begin();
605 it != pending_master_entries_.end(); ++it) {
606 PendingHosts& hosts = it->second;
607 for (PendingHosts::iterator host_it = hosts.begin();
608 host_it != hosts.end(); ++host_it) {
610 ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
614 if (manifest.did_ignore_intercept_namespaces) {
615 // Must be done after associating all pending master hosts.
617 "Ignoring the INTERCEPT section of the application cache manifest "
618 "because the content type is not text/cache-manifest");
619 LogConsoleMessageToAll(message);
622 group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING);
623 NotifyAllAssociatedHosts(DOWNLOADING_EVENT);
625 FetchMasterEntries();
626 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
629 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
630 DCHECK(internal_state_ == DOWNLOADING);
632 net::URLRequest* request = fetcher->request();
633 const GURL& url = request->original_url();
634 pending_url_fetches_.erase(url);
635 NotifyAllProgress(url);
636 ++url_fetches_completed_;
638 int response_code = request->status().is_success()
639 ? request->GetResponseCode()
640 : fetcher->redirect_response_code();
642 AppCacheEntry& entry = url_file_list_.find(url)->second;
644 if (response_code / 100 == 2) {
645 // Associate storage with the new entry.
646 DCHECK(fetcher->response_writer());
647 entry.set_response_id(fetcher->response_writer()->response_id());
648 entry.set_response_size(fetcher->response_writer()->amount_written());
649 if (!inprogress_cache_->AddOrModifyEntry(url, entry))
650 duplicate_response_ids_.push_back(entry.response_id());
652 // TODO(michaeln): Check for <html manifest=xxx>
653 // See http://code.google.com/p/chromium/issues/detail?id=97930
654 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
655 // if (!manifestAttribute) skip it
657 // Foreign entries will be detected during cache selection.
658 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
659 // file whose root element is an html element with a manifest attribute
660 // whose value doesn't match the manifest url of the application cache
661 // being processed, mark the entry as being foreign.
663 VLOG(1) << "Request status: " << request->status().status()
664 << " error: " << request->status().error()
665 << " response code: " << response_code;
666 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
667 if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
668 // Keep the existing response.
669 entry.set_response_id(fetcher->existing_entry().response_id());
670 entry.set_response_size(fetcher->existing_entry().response_size());
671 inprogress_cache_->AddOrModifyEntry(url, entry);
673 const char* kFormatString = "Resource fetch failed (%d) %s";
674 std::string message = FormatUrlErrorMessage(
675 kFormatString, url, fetcher->result(), response_code);
676 ResultType result = fetcher->result();
677 bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin();
679 case DISKCACHE_ERROR:
682 message, UNKNOWN_ERROR, GURL(), 0, is_cross_origin),
688 ErrorDetails(message, RESOURCE_ERROR, url, 0, is_cross_origin),
693 HandleCacheFailure(ErrorDetails(message,
704 } else if (response_code == 404 || response_code == 410) {
705 // Entry is skipped. They are dropped from the cache.
706 } else if (update_type_ == UPGRADE_ATTEMPT &&
707 fetcher->existing_entry().has_response_id()) {
708 // Keep the existing response.
709 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
710 // but the old resource may or may not be compatible with the new contents
711 // of the cache. Impossible to know one way or the other.
712 entry.set_response_id(fetcher->existing_entry().response_id());
713 entry.set_response_size(fetcher->existing_entry().response_size());
714 inprogress_cache_->AddOrModifyEntry(url, entry);
718 // Fetch another URL now that one request has completed.
719 DCHECK(internal_state_ != CACHE_FAILURE);
721 MaybeCompleteUpdate();
724 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
725 URLFetcher* fetcher) {
726 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
728 // TODO(jennb): Handle downloads completing during cache failure when update
729 // no longer fetches master entries directly. For now, we cancel all pending
730 // master entry fetches when entering cache failure state so this will never
731 // be called in CACHE_FAILURE state.
733 net::URLRequest* request = fetcher->request();
734 const GURL& url = request->original_url();
735 master_entry_fetches_.erase(url);
736 ++master_entries_completed_;
738 int response_code = request->status().is_success()
739 ? request->GetResponseCode() : -1;
741 PendingMasters::iterator found = pending_master_entries_.find(url);
742 DCHECK(found != pending_master_entries_.end());
743 PendingHosts& hosts = found->second;
745 // Section 6.9.4. No update case: step 7.3, else step 22.
746 if (response_code / 100 == 2) {
747 // Add fetched master entry to the appropriate cache.
748 AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
749 : group_->newest_complete_cache();
750 DCHECK(fetcher->response_writer());
751 AppCacheEntry master_entry(AppCacheEntry::MASTER,
752 fetcher->response_writer()->response_id(),
753 fetcher->response_writer()->amount_written());
754 if (cache->AddOrModifyEntry(url, master_entry))
755 added_master_entries_.push_back(url);
757 duplicate_response_ids_.push_back(master_entry.response_id());
759 // In no-update case, associate host with the newest cache.
760 if (!inprogress_cache_.get()) {
761 // TODO(michaeln): defer until the updated cache has been stored
762 DCHECK(cache == group_->newest_complete_cache());
763 for (PendingHosts::iterator host_it = hosts.begin();
764 host_it != hosts.end(); ++host_it) {
765 (*host_it)->AssociateCompleteCache(cache);
769 HostNotifier host_notifier;
770 for (PendingHosts::iterator host_it = hosts.begin();
771 host_it != hosts.end(); ++host_it) {
772 AppCacheHost* host = *host_it;
773 host_notifier.AddHost(host);
775 // In downloading case, disassociate host from inprogress cache.
776 if (inprogress_cache_.get())
777 host->AssociateNoCache(GURL());
779 host->RemoveObserver(this);
783 const char* kFormatString = "Manifest fetch failed (%d) %s";
784 std::string message = FormatUrlErrorMessage(
785 kFormatString, request->url(), fetcher->result(), response_code);
786 host_notifier.SendErrorNotifications(
787 ErrorDetails(message,
788 appcache::MANIFEST_ERROR,
791 false /*is_cross_origin*/));
793 // In downloading case, update result is different if all master entries
794 // failed vs. only some failing.
795 if (inprogress_cache_.get()) {
796 // Only count successful downloads to know if all master entries failed.
797 pending_master_entries_.erase(found);
798 --master_entries_completed_;
800 // Section 6.9.4, step 22.3.
801 if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
802 HandleCacheFailure(ErrorDetails(message,
803 appcache::MANIFEST_ERROR,
806 false /*is_cross_origin*/),
814 DCHECK(internal_state_ != CACHE_FAILURE);
815 FetchMasterEntries();
816 MaybeCompleteUpdate();
819 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
820 URLFetcher* fetcher) {
821 DCHECK(internal_state_ == REFETCH_MANIFEST);
822 DCHECK(manifest_fetcher_ == fetcher);
823 manifest_fetcher_ = NULL;
825 net::URLRequest* request = fetcher->request();
826 int response_code = request->status().is_success()
827 ? request->GetResponseCode() : -1;
828 if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
829 // Only need to store response in storage if manifest is not already
830 // an entry in the cache.
831 AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
833 entry->add_types(AppCacheEntry::MANIFEST);
834 StoreGroupAndCache();
836 manifest_response_writer_.reset(CreateResponseWriter());
837 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
838 new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
839 manifest_response_writer_->WriteInfo(
841 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
842 base::Unretained(this)));
845 VLOG(1) << "Request status: " << request->status().status()
846 << " error: " << request->status().error()
847 << " response code: " << response_code;
848 ScheduleUpdateRetry(kRerunDelayMs);
849 if (response_code == 200) {
850 HandleCacheFailure(ErrorDetails("Manifest changed during update",
854 false /*is_cross_origin*/),
858 const char* kFormatString = "Manifest re-fetch failed (%d) %s";
859 std::string message = FormatUrlErrorMessage(
860 kFormatString, manifest_url_, fetcher->result(), response_code);
861 HandleCacheFailure(ErrorDetails(message,
862 appcache::MANIFEST_ERROR,
865 false /*is_cross_origin*/),
872 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
874 scoped_refptr<net::StringIOBuffer> io_buffer(
875 new net::StringIOBuffer(manifest_data_));
876 manifest_response_writer_->WriteData(
878 manifest_data_.length(),
879 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
880 base::Unretained(this)));
883 ErrorDetails("Failed to write the manifest headers to storage",
887 false /*is_cross_origin*/),
893 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
895 AppCacheEntry entry(AppCacheEntry::MANIFEST,
896 manifest_response_writer_->response_id(),
897 manifest_response_writer_->amount_written());
898 if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
899 duplicate_response_ids_.push_back(entry.response_id());
900 StoreGroupAndCache();
903 ErrorDetails("Failed to write the manifest data to storage",
907 false /*is_cross_origin*/),
913 void AppCacheUpdateJob::StoreGroupAndCache() {
914 DCHECK(stored_state_ == UNSTORED);
915 stored_state_ = STORING;
916 scoped_refptr<AppCache> newest_cache;
917 if (inprogress_cache_.get())
918 newest_cache.swap(inprogress_cache_);
920 newest_cache = group_->newest_complete_cache();
921 newest_cache->set_update_time(base::Time::Now());
923 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
924 DCHECK_EQ(manifest_url_, group_->manifest_url());
925 storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
928 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
929 AppCache* newest_cache,
931 bool would_exceed_quota) {
932 DCHECK(stored_state_ == STORING);
934 stored_state_ = STORED;
935 MaybeCompleteUpdate(); // will definitely complete
937 stored_state_ = UNSTORED;
939 // Restore inprogress_cache_ to get the proper events delivered
940 // and the proper cleanup to occur.
941 if (newest_cache != group->newest_complete_cache())
942 inprogress_cache_ = newest_cache;
944 ResultType result = DB_ERROR;
945 ErrorReason reason = UNKNOWN_ERROR;
946 std::string message("Failed to commit new cache to storage");
947 if (would_exceed_quota) {
948 message.append(", would exceed quota");
949 result = QUOTA_ERROR;
950 reason = appcache::QUOTA_ERROR;
953 ErrorDetails(message, reason, GURL(), 0, false /*is_cross_origin*/),
959 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
961 std::vector<int> ids(1, host->host_id());
962 host->frontend()->OnEventRaised(ids, event_id);
965 void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) {
966 HostNotifier host_notifier;
967 AddAllAssociatedHostsToNotifier(&host_notifier);
968 host_notifier.SendNotifications(event_id);
971 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
972 HostNotifier host_notifier;
973 AddAllAssociatedHostsToNotifier(&host_notifier);
974 host_notifier.SendProgressNotifications(
975 url, url_file_list_.size(), url_fetches_completed_);
978 void AppCacheUpdateJob::NotifyAllFinalProgress() {
979 DCHECK(url_file_list_.size() == url_fetches_completed_);
980 NotifyAllProgress(GURL());
983 void AppCacheUpdateJob::NotifyAllError(const ErrorDetails& details) {
984 HostNotifier host_notifier;
985 AddAllAssociatedHostsToNotifier(&host_notifier);
986 host_notifier.SendErrorNotifications(details);
989 void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string& message) {
990 HostNotifier host_notifier;
991 AddAllAssociatedHostsToNotifier(&host_notifier);
992 host_notifier.SendLogMessage(message);
995 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
996 HostNotifier* host_notifier) {
997 // Collect hosts so we only send one notification per frontend.
998 // A host can only be associated with a single cache so no need to worry
999 // about duplicate hosts being added to the notifier.
1000 if (inprogress_cache_.get()) {
1001 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
1002 host_notifier->AddHosts(inprogress_cache_->associated_hosts());
1005 AppCacheGroup::Caches old_caches = group_->old_caches();
1006 for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
1007 it != old_caches.end(); ++it) {
1008 host_notifier->AddHosts((*it)->associated_hosts());
1011 AppCache* newest_cache = group_->newest_complete_cache();
1013 host_notifier->AddHosts(newest_cache->associated_hosts());
1016 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
1017 // The host is about to be deleted; remove from our collection.
1018 PendingMasters::iterator found =
1019 pending_master_entries_.find(host->pending_master_entry_url());
1020 DCHECK(found != pending_master_entries_.end());
1021 PendingHosts& hosts = found->second;
1022 PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
1023 DCHECK(it != hosts.end());
1027 void AppCacheUpdateJob::OnServiceReinitialized(
1028 AppCacheStorageReference* old_storage_ref) {
1029 // We continue to use the disabled instance, but arrange for its
1030 // deletion when its no longer needed.
1031 if (old_storage_ref->storage() == storage_)
1032 disabled_storage_reference_ = old_storage_ref;
1035 void AppCacheUpdateJob::CheckIfManifestChanged() {
1036 DCHECK(update_type_ == UPGRADE_ATTEMPT);
1037 AppCacheEntry* entry = NULL;
1038 if (group_->newest_complete_cache())
1039 entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
1041 // TODO(michaeln): This is just a bandaid to avoid a crash.
1042 // http://code.google.com/p/chromium/issues/detail?id=95101
1043 if (service_->storage() == storage_) {
1044 // Use a local variable because service_ is reset in HandleCacheFailure.
1045 AppCacheService* service = service_;
1047 ErrorDetails("Manifest entry not found in existing cache",
1051 false /*is_cross_origin*/),
1054 AppCacheHistograms::AddMissingManifestEntrySample();
1055 service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
1060 // Load manifest data from storage to compare against fetched manifest.
1061 manifest_response_reader_.reset(
1062 storage_->CreateResponseReader(manifest_url_,
1064 entry->response_id()));
1065 read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
1066 manifest_response_reader_->ReadData(
1067 read_manifest_buffer_.get(),
1069 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1070 base::Unretained(this))); // async read
1073 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
1075 loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
1076 manifest_response_reader_->ReadData(
1077 read_manifest_buffer_.get(),
1079 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1080 base::Unretained(this))); // read more
1082 read_manifest_buffer_ = NULL;
1083 manifest_response_reader_.reset();
1084 ContinueHandleManifestFetchCompleted(
1085 result < 0 || manifest_data_ != loaded_manifest_data_);
1089 void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) {
1090 for (base::hash_set<std::string>::const_iterator it =
1091 manifest.explicit_urls.begin();
1092 it != manifest.explicit_urls.end(); ++it) {
1093 AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
1096 const std::vector<Namespace>& intercepts =
1097 manifest.intercept_namespaces;
1098 for (std::vector<Namespace>::const_iterator it = intercepts.begin();
1099 it != intercepts.end(); ++it) {
1100 int flags = AppCacheEntry::INTERCEPT;
1101 if (it->is_executable)
1102 flags |= AppCacheEntry::EXECUTABLE;
1103 AddUrlToFileList(it->target_url, flags);
1106 const std::vector<Namespace>& fallbacks =
1107 manifest.fallback_namespaces;
1108 for (std::vector<Namespace>::const_iterator it = fallbacks.begin();
1109 it != fallbacks.end(); ++it) {
1110 AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
1113 // Add all master entries from newest complete cache.
1114 if (update_type_ == UPGRADE_ATTEMPT) {
1115 const AppCache::EntryMap& entries =
1116 group_->newest_complete_cache()->entries();
1117 for (AppCache::EntryMap::const_iterator it = entries.begin();
1118 it != entries.end(); ++it) {
1119 const AppCacheEntry& entry = it->second;
1120 if (entry.IsMaster())
1121 AddUrlToFileList(it->first, AppCacheEntry::MASTER);
1126 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
1127 std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
1128 AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
1131 urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
1133 ret.first->second.add_types(type); // URL already exists. Merge types.
1136 void AppCacheUpdateJob::FetchUrls() {
1137 DCHECK(internal_state_ == DOWNLOADING);
1139 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
1140 // Fetch up to the concurrent limit. Other fetches will be triggered as each
1141 // each fetch completes.
1142 while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
1143 !urls_to_fetch_.empty()) {
1144 UrlToFetch url_to_fetch = urls_to_fetch_.front();
1145 urls_to_fetch_.pop_front();
1147 AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
1148 DCHECK(it != url_file_list_.end());
1149 AppCacheEntry& entry = it->second;
1150 if (ShouldSkipUrlFetch(entry)) {
1151 NotifyAllProgress(url_to_fetch.url);
1152 ++url_fetches_completed_;
1153 } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
1154 NotifyAllProgress(url_to_fetch.url);
1155 ++url_fetches_completed_; // saved a URL request
1156 } else if (!url_to_fetch.storage_checked &&
1157 MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
1158 // Continues asynchronously after data is loaded from newest cache.
1160 URLFetcher* fetcher = new URLFetcher(
1161 url_to_fetch.url, URLFetcher::URL_FETCH, this);
1162 if (url_to_fetch.existing_response_info.get()) {
1163 DCHECK(group_->newest_complete_cache());
1164 AppCacheEntry* existing_entry =
1165 group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
1166 DCHECK(existing_entry);
1167 DCHECK(existing_entry->response_id() ==
1168 url_to_fetch.existing_response_info->response_id());
1169 fetcher->set_existing_response_headers(
1170 url_to_fetch.existing_response_info->http_response_info()->headers
1172 fetcher->set_existing_entry(*existing_entry);
1175 pending_url_fetches_.insert(
1176 PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
1181 void AppCacheUpdateJob::CancelAllUrlFetches() {
1182 // Cancel any pending URL requests.
1183 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1184 it != pending_url_fetches_.end(); ++it) {
1188 url_fetches_completed_ +=
1189 pending_url_fetches_.size() + urls_to_fetch_.size();
1190 pending_url_fetches_.clear();
1191 urls_to_fetch_.clear();
1194 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
1196 // If the resource URL being processed was flagged as neither an
1197 // "explicit entry" nor or a "fallback entry", then the user agent
1198 // may skip this URL.
1199 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
1202 // TODO(jennb): decide if entry should be skipped to expire it from cache
1206 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
1208 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
1209 AppCacheEntry* existing =
1210 inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
1211 : group_->newest_complete_cache()->GetEntry(url);
1213 existing->add_types(entry_type);
1219 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
1222 DCHECK(!IsTerminating());
1224 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
1226 if (inprogress_cache_.get()) {
1228 host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
1229 cache = inprogress_cache_.get();
1231 cache = group_->newest_complete_cache();
1234 // Update existing entry if it has already been fetched.
1235 AppCacheEntry* entry = cache->GetEntry(url);
1237 entry->add_types(AppCacheEntry::MASTER);
1238 if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
1239 // only associate if have entry
1240 host->AssociateCompleteCache(cache);
1243 ++master_entries_completed_; // pretend fetching completed
1248 // Add to fetch list if not already fetching.
1249 if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
1250 master_entries_to_fetch_.insert(url);
1251 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
1252 FetchMasterEntries();
1256 void AppCacheUpdateJob::FetchMasterEntries() {
1257 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
1259 // Fetch each master entry in the list, up to the concurrent limit.
1260 // Additional fetches will be triggered as each fetch completes.
1261 while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
1262 !master_entries_to_fetch_.empty()) {
1263 const GURL& url = *master_entries_to_fetch_.begin();
1265 if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
1266 ++master_entries_completed_; // saved a URL request
1268 // In no update case, associate hosts to newest cache in group
1269 // now that master entry has been "successfully downloaded".
1270 if (internal_state_ == NO_UPDATE) {
1271 // TODO(michaeln): defer until the updated cache has been stored.
1272 DCHECK(!inprogress_cache_.get());
1273 AppCache* cache = group_->newest_complete_cache();
1274 PendingMasters::iterator found = pending_master_entries_.find(url);
1275 DCHECK(found != pending_master_entries_.end());
1276 PendingHosts& hosts = found->second;
1277 for (PendingHosts::iterator host_it = hosts.begin();
1278 host_it != hosts.end(); ++host_it) {
1279 (*host_it)->AssociateCompleteCache(cache);
1283 URLFetcher* fetcher = new URLFetcher(
1284 url, URLFetcher::MASTER_ENTRY_FETCH, this);
1286 master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
1289 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1293 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1294 const ErrorDetails& error_details) {
1295 // For now, cancel all in-progress fetches for master entries and pretend
1296 // all master entries fetches have completed.
1297 // TODO(jennb): Delete this when update no longer fetches master entries
1300 // Cancel all in-progress fetches.
1301 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1302 it != master_entry_fetches_.end(); ++it) {
1304 master_entries_to_fetch_.insert(it->first); // back in unfetched list
1306 master_entry_fetches_.clear();
1308 master_entries_completed_ += master_entries_to_fetch_.size();
1310 // Cache failure steps, step 2.
1311 // Pretend all master entries that have not yet been fetched have completed
1312 // downloading. Unassociate hosts from any appcache and send ERROR event.
1313 HostNotifier host_notifier;
1314 while (!master_entries_to_fetch_.empty()) {
1315 const GURL& url = *master_entries_to_fetch_.begin();
1316 PendingMasters::iterator found = pending_master_entries_.find(url);
1317 DCHECK(found != pending_master_entries_.end());
1318 PendingHosts& hosts = found->second;
1319 for (PendingHosts::iterator host_it = hosts.begin();
1320 host_it != hosts.end(); ++host_it) {
1321 AppCacheHost* host = *host_it;
1322 host->AssociateNoCache(GURL());
1323 host_notifier.AddHost(host);
1324 host->RemoveObserver(this);
1328 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1330 host_notifier.SendErrorNotifications(error_details);
1333 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
1334 AppCacheEntry& entry) {
1335 if (update_type_ != UPGRADE_ATTEMPT)
1338 AppCache* newest = group_->newest_complete_cache();
1339 AppCacheEntry* copy_me = newest->GetEntry(url);
1340 if (!copy_me || !copy_me->has_response_id())
1343 // Load HTTP headers for entry from newest cache.
1344 loading_responses_.insert(
1345 LoadingResponses::value_type(copy_me->response_id(), url));
1346 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
1347 copy_me->response_id(),
1349 // Async: wait for OnResponseInfoLoaded to complete.
1353 void AppCacheUpdateJob::OnResponseInfoLoaded(
1354 AppCacheResponseInfo* response_info, int64 response_id) {
1355 const net::HttpResponseInfo* http_info = response_info ?
1356 response_info->http_response_info() : NULL;
1358 // Needed response info for a manifest fetch request.
1359 if (internal_state_ == FETCH_MANIFEST) {
1361 manifest_fetcher_->set_existing_response_headers(
1362 http_info->headers.get());
1363 manifest_fetcher_->Start();
1367 LoadingResponses::iterator found = loading_responses_.find(response_id);
1368 DCHECK(found != loading_responses_.end());
1369 const GURL& url = found->second;
1372 LoadFromNewestCacheFailed(url, NULL); // no response found
1374 // Check if response can be re-used according to HTTP caching semantics.
1375 // Responses with a "vary" header get treated as expired.
1376 const std::string name = "vary";
1379 if (!http_info->headers.get() ||
1380 http_info->headers->RequiresValidation(http_info->request_time,
1381 http_info->response_time,
1382 base::Time::Now()) ||
1383 http_info->headers->EnumerateHeader(&iter, name, &value)) {
1384 LoadFromNewestCacheFailed(url, response_info);
1386 DCHECK(group_->newest_complete_cache());
1387 AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
1389 DCHECK(copy_me->response_id() == response_id);
1391 AppCache::EntryMap::iterator it = url_file_list_.find(url);
1392 DCHECK(it != url_file_list_.end());
1393 AppCacheEntry& entry = it->second;
1394 entry.set_response_id(response_id);
1395 entry.set_response_size(copy_me->response_size());
1396 inprogress_cache_->AddOrModifyEntry(url, entry);
1397 NotifyAllProgress(url);
1398 ++url_fetches_completed_;
1401 loading_responses_.erase(found);
1403 MaybeCompleteUpdate();
1406 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1407 const GURL& url, AppCacheResponseInfo* response_info) {
1408 if (internal_state_ == CACHE_FAILURE)
1411 // Re-insert url at front of fetch list. Indicate storage has been checked.
1412 urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
1416 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1417 DCHECK(internal_state_ != CACHE_FAILURE);
1419 // Must wait for any pending master entries or url fetches to complete.
1420 if (master_entries_completed_ != pending_master_entries_.size() ||
1421 url_fetches_completed_ != url_file_list_.size()) {
1422 DCHECK(internal_state_ != COMPLETED);
1426 switch (internal_state_) {
1428 if (master_entries_completed_ > 0) {
1429 switch (stored_state_) {
1431 StoreGroupAndCache();
1439 // 6.9.4 steps 7.3-7.7.
1440 NotifyAllAssociatedHosts(NO_UPDATE_EVENT);
1441 DiscardDuplicateResponses();
1442 internal_state_ = COMPLETED;
1445 internal_state_ = REFETCH_MANIFEST;
1446 FetchManifest(false);
1448 case REFETCH_MANIFEST:
1449 DCHECK(stored_state_ == STORED);
1450 NotifyAllFinalProgress();
1451 if (update_type_ == CACHE_ATTEMPT)
1452 NotifyAllAssociatedHosts(CACHED_EVENT);
1454 NotifyAllAssociatedHosts(UPDATE_READY_EVENT);
1455 DiscardDuplicateResponses();
1456 internal_state_ = COMPLETED;
1457 LogHistogramStats(UPDATE_OK, GURL());
1460 NOTREACHED(); // See HandleCacheFailure
1466 // Let the stack unwind before deletion to make it less risky as this
1467 // method is called from multiple places in this file.
1468 if (internal_state_ == COMPLETED)
1472 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
1473 // TODO(jennb): post a delayed task with the "same parameters" as this job
1474 // to retry the update at a later time. Need group, URLs of pending master
1475 // entries and their hosts.
1478 void AppCacheUpdateJob::Cancel() {
1479 internal_state_ = CANCELLED;
1481 LogHistogramStats(CANCELLED_ERROR, GURL());
1483 if (manifest_fetcher_) {
1484 delete manifest_fetcher_;
1485 manifest_fetcher_ = NULL;
1488 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1489 it != pending_url_fetches_.end(); ++it) {
1492 pending_url_fetches_.clear();
1494 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1495 it != master_entry_fetches_.end(); ++it) {
1498 master_entry_fetches_.clear();
1500 ClearPendingMasterEntries();
1501 DiscardInprogressCache();
1503 // Delete response writer to avoid any callbacks.
1504 if (manifest_response_writer_)
1505 manifest_response_writer_.reset();
1507 storage_->CancelDelegateCallbacks(this);
1510 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1511 for (PendingMasters::iterator it = pending_master_entries_.begin();
1512 it != pending_master_entries_.end(); ++it) {
1513 PendingHosts& hosts = it->second;
1514 for (PendingHosts::iterator host_it = hosts.begin();
1515 host_it != hosts.end(); ++host_it) {
1516 (*host_it)->RemoveObserver(this);
1520 pending_master_entries_.clear();
1523 void AppCacheUpdateJob::DiscardInprogressCache() {
1524 if (stored_state_ == STORING) {
1525 // We can make no assumptions about whether the StoreGroupAndCacheTask
1526 // actually completed or not. This condition should only be reachable
1527 // during shutdown. Free things up and return to do no harm.
1528 inprogress_cache_ = NULL;
1529 added_master_entries_.clear();
1533 storage_->DoomResponses(manifest_url_, stored_response_ids_);
1535 if (!inprogress_cache_.get()) {
1536 // We have to undo the changes we made, if any, to the existing cache.
1537 if (group_ && group_->newest_complete_cache()) {
1538 for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
1539 iter != added_master_entries_.end(); ++iter) {
1540 group_->newest_complete_cache()->RemoveEntry(*iter);
1543 added_master_entries_.clear();
1547 AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
1548 while (!hosts.empty())
1549 (*hosts.begin())->AssociateNoCache(GURL());
1551 inprogress_cache_ = NULL;
1552 added_master_entries_.clear();
1555 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1556 storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
1559 void AppCacheUpdateJob::LogHistogramStats(
1560 ResultType result, const GURL& failed_resource_url) {
1561 AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin());
1562 if (result == UPDATE_OK)
1565 int percent_complete = 0;
1566 if (url_file_list_.size() > 0) {
1567 size_t actual_fetches_completed = url_fetches_completed_;
1568 if (!failed_resource_url.is_empty() && actual_fetches_completed)
1569 --actual_fetches_completed;
1570 percent_complete = (static_cast<double>(actual_fetches_completed) /
1571 static_cast<double>(url_file_list_.size())) * 100.0;
1572 percent_complete = std::min(percent_complete, 99);
1575 bool was_making_progress =
1576 base::Time::Now() - last_progress_time_ <
1577 base::TimeDelta::FromMinutes(5);
1579 bool off_origin_resource_failure =
1580 !failed_resource_url.is_empty() &&
1581 (failed_resource_url.GetOrigin() != manifest_url_.GetOrigin());
1583 AppCacheHistograms::LogUpdateFailureStats(
1584 manifest_url_.GetOrigin(),
1586 was_making_progress,
1587 off_origin_resource_failure);
1590 void AppCacheUpdateJob::DeleteSoon() {
1591 ClearPendingMasterEntries();
1592 manifest_response_writer_.reset();
1593 storage_->CancelDelegateCallbacks(this);
1594 service_->RemoveObserver(this);
1597 // Break the connection with the group so the group cannot call delete
1598 // on this object after we've posted a task to delete ourselves.
1599 group_->SetUpdateStatus(AppCacheGroup::IDLE);
1602 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
1605 } // namespace appcache