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 "content/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 "content/browser/appcache/appcache_group.h"
14 #include "content/browser/appcache/appcache_histograms.h"
15 #include "net/base/host_port_pair.h"
16 #include "net/base/io_buffer.h"
17 #include "net/base/load_flags.h"
18 #include "net/base/net_errors.h"
19 #include "net/base/request_priority.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/url_request/url_request_context.h"
25 bool IsDataReductionProxy(const net::HostPortPair& proxy_server) {
27 proxy_server.Equals(net::HostPortPair("proxy.googlezip.net", 443)) ||
28 proxy_server.Equals(net::HostPortPair("compress.googlezip.net", 80)) ||
29 proxy_server.Equals(net::HostPortPair("proxy-dev.googlezip.net", 80)));
35 static const int kBufferSize = 32768;
36 static const size_t kMaxConcurrentUrlFetches = 2;
37 static const int kMax503Retries = 3;
39 static std::string FormatUrlErrorMessage(
40 const char* format, const GURL& url,
41 AppCacheUpdateJob::ResultType error,
43 // Show the net response code if we have one.
44 int code = response_code;
45 if (error != AppCacheUpdateJob::SERVER_ERROR)
46 code = static_cast<int>(error);
47 return base::StringPrintf(format, code, url.spec().c_str());
50 // Helper class for collecting hosts per frontend when sending notifications
51 // so that only one notification is sent for all hosts using the same frontend.
54 typedef std::vector<int> HostIds;
55 typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap;
57 // Caller is responsible for ensuring there will be no duplicate hosts.
58 void AddHost(AppCacheHost* host) {
59 std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert(
60 NotifyHostMap::value_type(host->frontend(), HostIds()));
61 ret.first->second.push_back(host->host_id());
64 void AddHosts(const std::set<AppCacheHost*>& hosts) {
65 for (std::set<AppCacheHost*>::const_iterator it = hosts.begin();
66 it != hosts.end(); ++it) {
71 void SendNotifications(AppCacheEventID event_id) {
72 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
73 it != hosts_to_notify.end(); ++it) {
74 AppCacheFrontend* frontend = it->first;
75 frontend->OnEventRaised(it->second, event_id);
79 void SendProgressNotifications(
80 const GURL& url, int num_total, int num_complete) {
81 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
82 it != hosts_to_notify.end(); ++it) {
83 AppCacheFrontend* frontend = it->first;
84 frontend->OnProgressEventRaised(it->second, url,
85 num_total, num_complete);
89 void SendErrorNotifications(const AppCacheErrorDetails& details) {
90 DCHECK(!details.message.empty());
91 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
92 it != hosts_to_notify.end(); ++it) {
93 AppCacheFrontend* frontend = it->first;
94 frontend->OnErrorEventRaised(it->second, details);
98 void SendLogMessage(const std::string& message) {
99 for (NotifyHostMap::iterator it = hosts_to_notify.begin();
100 it != hosts_to_notify.end(); ++it) {
101 AppCacheFrontend* frontend = it->first;
102 for (HostIds::iterator id = it->second.begin();
103 id != it->second.end(); ++id) {
104 frontend->OnLogMessage(*id, APPCACHE_LOG_WARNING, message);
110 NotifyHostMap hosts_to_notify;
113 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
115 AppCacheResponseInfo* info)
117 storage_checked(checked),
118 existing_response_info(info) {
121 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
124 // Helper class to fetch resources. Depending on the fetch type,
125 // can either fetch to an in-memory string or write the response
126 // data out to the disk cache.
127 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
128 FetchType fetch_type,
129 AppCacheUpdateJob* job)
132 fetch_type_(fetch_type),
133 retry_503_attempts_(0),
134 buffer_(new net::IOBuffer(kBufferSize)),
135 request_(job->service_->request_context()
136 ->CreateRequest(url, net::DEFAULT_PRIORITY, this, NULL)),
138 redirect_response_code_(-1) {}
140 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
143 void AppCacheUpdateJob::URLFetcher::Start() {
144 request_->set_first_party_for_cookies(job_->manifest_url_);
145 request_->SetLoadFlags(request_->load_flags() |
146 net::LOAD_DISABLE_INTERCEPT);
147 if (existing_response_headers_.get())
148 AddConditionalHeaders(existing_response_headers_.get());
152 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
153 net::URLRequest* request,
154 const net::RedirectInfo& redirect_info,
155 bool* defer_redirect) {
156 DCHECK(request_ == request);
157 // TODO(bengr): Remove this special case logic when crbug.com/429505 is
158 // resolved. Until then, the data reduction proxy client logic uses the
159 // redirect mechanism to resend requests over a direct connection when
160 // the proxy instructs it to do so. The redirect is to the same location
161 // as the original URL.
162 if ((request->load_flags() & net::LOAD_BYPASS_PROXY) &&
163 IsDataReductionProxy(request->proxy_server())) {
164 DCHECK_EQ(request->original_url(), request->url());
167 // Redirect is not allowed by the update process.
168 job_->MadeProgress();
169 redirect_response_code_ = request->GetResponseCode();
171 result_ = REDIRECT_ERROR;
172 OnResponseCompleted();
175 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
176 net::URLRequest *request) {
177 DCHECK(request == request_);
178 int response_code = -1;
179 if (request->status().is_success()) {
180 response_code = request->GetResponseCode();
181 job_->MadeProgress();
183 if ((response_code / 100) == 2) {
185 // See http://code.google.com/p/chromium/issues/detail?id=69594
186 // We willfully violate the HTML5 spec at this point in order
187 // to support the appcaching of cross-origin HTTPS resources.
188 // We've opted for a milder constraint and allow caching unless
189 // the resource has a "no-store" header. A spec change has been
190 // requested on the whatwg list.
191 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
192 if (url_.SchemeIsSecure() &&
193 url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
194 if (request->response_headers()->
195 HasHeaderValue("cache-control", "no-store")) {
196 DCHECK_EQ(-1, redirect_response_code_);
198 result_ = SERVER_ERROR; // Not the best match?
199 OnResponseCompleted();
204 // Write response info to storage for URL fetches. Wait for async write
205 // completion before reading any response data.
206 if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
207 response_writer_.reset(job_->CreateResponseWriter());
208 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
209 new HttpResponseInfoIOBuffer(
210 new net::HttpResponseInfo(request->response_info())));
211 response_writer_->WriteInfo(
213 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
218 if (response_code > 0)
219 result_ = SERVER_ERROR;
221 result_ = NETWORK_ERROR;
222 OnResponseCompleted();
226 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
227 net::URLRequest* request, int bytes_read) {
228 DCHECK(request_ == request);
229 bool data_consumed = true;
230 if (request->status().is_success() && bytes_read > 0) {
231 job_->MadeProgress();
232 data_consumed = ConsumeResponseData(bytes_read);
235 while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
236 if (bytes_read > 0) {
237 data_consumed = ConsumeResponseData(bytes_read);
239 break; // wait for async data processing, then read more
246 if (data_consumed && !request->status().is_io_pending()) {
247 DCHECK_EQ(UPDATE_OK, result_);
248 OnResponseCompleted();
252 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
253 const net::HttpResponseHeaders* headers) {
254 DCHECK(request_.get() && headers);
255 net::HttpRequestHeaders extra_headers;
257 // Add If-Modified-Since header if response info has Last-Modified header.
258 const std::string last_modified = "Last-Modified";
259 std::string last_modified_value;
260 headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
261 if (!last_modified_value.empty()) {
262 extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
263 last_modified_value);
266 // Add If-None-Match header if response info has ETag header.
267 const std::string etag = "ETag";
268 std::string etag_value;
269 headers->EnumerateHeader(NULL, etag, &etag_value);
270 if (!etag_value.empty()) {
271 extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
274 if (!extra_headers.IsEmpty())
275 request_->SetExtraRequestHeaders(extra_headers);
278 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
281 result_ = DISKCACHE_ERROR;
282 OnResponseCompleted();
288 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
289 InternalUpdateState state = job_->internal_state_;
290 if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
293 request_->Read(buffer_.get(), kBufferSize, &bytes_read);
294 OnReadCompleted(request_.get(), bytes_read);
297 // Returns false if response data is processed asynchronously, in which
298 // case ReadResponseData will be invoked when it is safe to continue
299 // reading more response data from the request.
300 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
301 DCHECK_GT(bytes_read, 0);
302 switch (fetch_type_) {
304 case MANIFEST_REFETCH:
305 manifest_data_.append(buffer_->data(), bytes_read);
308 case MASTER_ENTRY_FETCH:
309 DCHECK(response_writer_.get());
310 response_writer_->WriteData(
313 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
314 return false; // wait for async write completion to continue reading
321 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
322 if (request_->status().is_success())
323 job_->MadeProgress();
325 // Retry for 503s where retry-after is 0.
326 if (request_->status().is_success() &&
327 request_->GetResponseCode() == 503 &&
328 MaybeRetryRequest()) {
332 switch (fetch_type_) {
334 job_->HandleManifestFetchCompleted(this);
337 job_->HandleUrlFetchCompleted(this);
339 case MASTER_ENTRY_FETCH:
340 job_->HandleMasterEntryFetchCompleted(this);
342 case MANIFEST_REFETCH:
343 job_->HandleManifestRefetchCompleted(this);
352 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
353 if (retry_503_attempts_ >= kMax503Retries ||
354 !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
357 ++retry_503_attempts_;
359 request_ = job_->service_->request_context()->CreateRequest(
360 url_, net::DEFAULT_PRIORITY, this, NULL);
365 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheServiceImpl* service,
366 AppCacheGroup* group)
368 manifest_url_(group->manifest_url()),
370 update_type_(UNKNOWN_TYPE),
371 internal_state_(FETCH_MANIFEST),
372 master_entries_completed_(0),
373 url_fetches_completed_(0),
374 manifest_fetcher_(NULL),
375 manifest_has_valid_mime_type_(false),
376 stored_state_(UNSTORED),
377 storage_(service->storage()) {
378 service_->AddObserver(this);
381 AppCacheUpdateJob::~AppCacheUpdateJob() {
383 service_->RemoveObserver(this);
384 if (internal_state_ != COMPLETED)
387 DCHECK(!manifest_fetcher_);
388 DCHECK(pending_url_fetches_.empty());
389 DCHECK(!inprogress_cache_.get());
390 DCHECK(pending_master_entries_.empty());
391 DCHECK(master_entry_fetches_.empty());
394 group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
397 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
398 const GURL& new_master_resource) {
399 DCHECK(group_->update_job() == this);
400 DCHECK(!group_->is_obsolete());
402 bool is_new_pending_master_entry = false;
403 if (!new_master_resource.is_empty()) {
404 DCHECK(new_master_resource == host->pending_master_entry_url());
405 DCHECK(!new_master_resource.has_ref());
406 DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
408 // Cannot add more to this update if already terminating.
409 if (IsTerminating()) {
410 group_->QueueUpdate(host, new_master_resource);
414 std::pair<PendingMasters::iterator, bool> ret =
415 pending_master_entries_.insert(
416 PendingMasters::value_type(new_master_resource, PendingHosts()));
417 is_new_pending_master_entry = ret.second;
418 ret.first->second.push_back(host);
419 host->AddObserver(this);
422 // Notify host (if any) if already checking or downloading.
423 AppCacheGroup::UpdateAppCacheStatus update_status = group_->update_status();
424 if (update_status == AppCacheGroup::CHECKING ||
425 update_status == AppCacheGroup::DOWNLOADING) {
427 NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
428 if (update_status == AppCacheGroup::DOWNLOADING)
429 NotifySingleHost(host, APPCACHE_DOWNLOADING_EVENT);
431 // Add to fetch list or an existing entry if already fetched.
432 if (!new_master_resource.is_empty()) {
433 AddMasterEntryToFetchList(host, new_master_resource,
434 is_new_pending_master_entry);
440 // Begin update process for the group.
442 group_->SetUpdateAppCacheStatus(AppCacheGroup::CHECKING);
443 if (group_->HasCache()) {
444 update_type_ = UPGRADE_ATTEMPT;
445 NotifyAllAssociatedHosts(APPCACHE_CHECKING_EVENT);
447 update_type_ = CACHE_ATTEMPT;
449 NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
452 if (!new_master_resource.is_empty()) {
453 AddMasterEntryToFetchList(host, new_master_resource,
454 is_new_pending_master_entry);
460 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
461 AppCacheResponseWriter* writer =
462 storage_->CreateResponseWriter(manifest_url_,
464 stored_response_ids_.push_back(writer->response_id());
468 void AppCacheUpdateJob::HandleCacheFailure(
469 const AppCacheErrorDetails& error_details,
471 const GURL& failed_resource_url) {
472 // 6.9.4 cache failure steps 2-8.
473 DCHECK(internal_state_ != CACHE_FAILURE);
474 DCHECK(!error_details.message.empty());
475 DCHECK(result != UPDATE_OK);
476 internal_state_ = CACHE_FAILURE;
477 LogHistogramStats(result, failed_resource_url);
478 CancelAllUrlFetches();
479 CancelAllMasterEntryFetches(error_details);
480 NotifyAllError(error_details);
481 DiscardInprogressCache();
482 internal_state_ = COMPLETED;
483 DeleteSoon(); // To unwind the stack prior to deletion.
486 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
487 DCHECK(!manifest_fetcher_);
488 manifest_fetcher_ = new URLFetcher(
490 is_first_fetch ? URLFetcher::MANIFEST_FETCH :
491 URLFetcher::MANIFEST_REFETCH,
494 // Add any necessary Http headers before sending fetch request.
495 if (is_first_fetch) {
496 AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
497 group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
499 // Asynchronously load response info for manifest from newest cache.
500 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
501 entry->response_id(), this);
503 manifest_fetcher_->Start();
506 DCHECK(internal_state_ == REFETCH_MANIFEST);
507 DCHECK(manifest_response_info_.get());
508 manifest_fetcher_->set_existing_response_headers(
509 manifest_response_info_->headers.get());
510 manifest_fetcher_->Start();
515 void AppCacheUpdateJob::HandleManifestFetchCompleted(
516 URLFetcher* fetcher) {
517 DCHECK_EQ(internal_state_, FETCH_MANIFEST);
518 DCHECK_EQ(manifest_fetcher_, fetcher);
519 manifest_fetcher_ = NULL;
521 net::URLRequest* request = fetcher->request();
522 int response_code = -1;
523 bool is_valid_response_code = false;
524 if (request->status().is_success()) {
525 response_code = request->GetResponseCode();
526 is_valid_response_code = (response_code / 100 == 2);
528 std::string mime_type;
529 request->GetMimeType(&mime_type);
530 manifest_has_valid_mime_type_ = (mime_type == "text/cache-manifest");
533 if (is_valid_response_code) {
534 manifest_data_ = fetcher->manifest_data();
535 manifest_response_info_.reset(
536 new net::HttpResponseInfo(request->response_info()));
537 if (update_type_ == UPGRADE_ATTEMPT)
538 CheckIfManifestChanged(); // continues asynchronously
540 ContinueHandleManifestFetchCompleted(true);
541 } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
542 ContinueHandleManifestFetchCompleted(false);
543 } else if ((response_code == 404 || response_code == 410) &&
544 update_type_ == UPGRADE_ATTEMPT) {
545 storage_->MakeGroupObsolete(group_, this, response_code); // async
547 const char* kFormatString = "Manifest fetch failed (%d) %s";
548 std::string message = FormatUrlErrorMessage(
549 kFormatString, manifest_url_, fetcher->result(), response_code);
550 HandleCacheFailure(AppCacheErrorDetails(message,
551 APPCACHE_MANIFEST_ERROR,
554 false /*is_cross_origin*/),
560 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
563 DCHECK(master_entry_fetches_.empty());
564 CancelAllMasterEntryFetches(AppCacheErrorDetails(
565 "The cache has been made obsolete, "
566 "the manifest file returned 404 or 410",
567 APPCACHE_MANIFEST_ERROR,
570 false /*is_cross_origin*/));
572 DCHECK(group->is_obsolete());
573 NotifyAllAssociatedHosts(APPCACHE_OBSOLETE_EVENT);
574 internal_state_ = COMPLETED;
575 MaybeCompleteUpdate();
577 // Treat failure to mark group obsolete as a cache failure.
578 HandleCacheFailure(AppCacheErrorDetails(
579 "Failed to mark the cache as obsolete",
580 APPCACHE_UNKNOWN_ERROR,
583 false /*is_cross_origin*/),
589 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
590 DCHECK(internal_state_ == FETCH_MANIFEST);
593 DCHECK(update_type_ == UPGRADE_ATTEMPT);
594 internal_state_ = NO_UPDATE;
596 // Wait for pending master entries to download.
597 FetchMasterEntries();
598 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
602 AppCacheManifest manifest;
603 if (!ParseManifest(manifest_url_, manifest_data_.data(),
604 manifest_data_.length(),
605 manifest_has_valid_mime_type_ ?
606 PARSE_MANIFEST_ALLOWING_INTERCEPTS :
607 PARSE_MANIFEST_PER_STANDARD,
609 const char* kFormatString = "Failed to parse manifest %s";
610 const std::string message = base::StringPrintf(kFormatString,
611 manifest_url_.spec().c_str());
613 AppCacheErrorDetails(
614 message, APPCACHE_SIGNATURE_ERROR, GURL(), 0,
615 false /*is_cross_origin*/),
622 // Proceed with update process. Section 6.9.4 steps 8-20.
623 internal_state_ = DOWNLOADING;
624 inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
625 BuildUrlFileList(manifest);
626 inprogress_cache_->InitializeWithManifest(&manifest);
628 // Associate all pending master hosts with the newly created cache.
629 for (PendingMasters::iterator it = pending_master_entries_.begin();
630 it != pending_master_entries_.end(); ++it) {
631 PendingHosts& hosts = it->second;
632 for (PendingHosts::iterator host_it = hosts.begin();
633 host_it != hosts.end(); ++host_it) {
635 ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
639 if (manifest.did_ignore_intercept_namespaces) {
640 // Must be done after associating all pending master hosts.
642 "Ignoring the INTERCEPT section of the application cache manifest "
643 "because the content type is not text/cache-manifest");
644 LogConsoleMessageToAll(message);
647 group_->SetUpdateAppCacheStatus(AppCacheGroup::DOWNLOADING);
648 NotifyAllAssociatedHosts(APPCACHE_DOWNLOADING_EVENT);
650 FetchMasterEntries();
651 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
654 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
655 DCHECK(internal_state_ == DOWNLOADING);
657 net::URLRequest* request = fetcher->request();
658 const GURL& url = request->original_url();
659 pending_url_fetches_.erase(url);
660 NotifyAllProgress(url);
661 ++url_fetches_completed_;
663 int response_code = request->status().is_success()
664 ? request->GetResponseCode()
665 : fetcher->redirect_response_code();
667 AppCacheEntry& entry = url_file_list_.find(url)->second;
669 if (response_code / 100 == 2) {
670 // Associate storage with the new entry.
671 DCHECK(fetcher->response_writer());
672 entry.set_response_id(fetcher->response_writer()->response_id());
673 entry.set_response_size(fetcher->response_writer()->amount_written());
674 if (!inprogress_cache_->AddOrModifyEntry(url, entry))
675 duplicate_response_ids_.push_back(entry.response_id());
677 // TODO(michaeln): Check for <html manifest=xxx>
678 // See http://code.google.com/p/chromium/issues/detail?id=97930
679 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
680 // if (!manifestAttribute) skip it
682 // Foreign entries will be detected during cache selection.
683 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
684 // file whose root element is an html element with a manifest attribute
685 // whose value doesn't match the manifest url of the application cache
686 // being processed, mark the entry as being foreign.
688 VLOG(1) << "Request status: " << request->status().status()
689 << " error: " << request->status().error()
690 << " response code: " << response_code;
691 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
692 if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
693 // Keep the existing response.
694 entry.set_response_id(fetcher->existing_entry().response_id());
695 entry.set_response_size(fetcher->existing_entry().response_size());
696 inprogress_cache_->AddOrModifyEntry(url, entry);
698 const char* kFormatString = "Resource fetch failed (%d) %s";
699 std::string message = FormatUrlErrorMessage(
700 kFormatString, url, fetcher->result(), response_code);
701 ResultType result = fetcher->result();
702 bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin();
704 case DISKCACHE_ERROR:
706 AppCacheErrorDetails(
707 message, APPCACHE_UNKNOWN_ERROR, GURL(), 0,
714 AppCacheErrorDetails(message, APPCACHE_RESOURCE_ERROR, url, 0,
720 HandleCacheFailure(AppCacheErrorDetails(message,
721 APPCACHE_RESOURCE_ERROR,
731 } else if (response_code == 404 || response_code == 410) {
732 // Entry is skipped. They are dropped from the cache.
733 } else if (update_type_ == UPGRADE_ATTEMPT &&
734 fetcher->existing_entry().has_response_id()) {
735 // Keep the existing response.
736 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
737 // but the old resource may or may not be compatible with the new contents
738 // of the cache. Impossible to know one way or the other.
739 entry.set_response_id(fetcher->existing_entry().response_id());
740 entry.set_response_size(fetcher->existing_entry().response_size());
741 inprogress_cache_->AddOrModifyEntry(url, entry);
745 // Fetch another URL now that one request has completed.
746 DCHECK(internal_state_ != CACHE_FAILURE);
748 MaybeCompleteUpdate();
751 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
752 URLFetcher* fetcher) {
753 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
755 // TODO(jennb): Handle downloads completing during cache failure when update
756 // no longer fetches master entries directly. For now, we cancel all pending
757 // master entry fetches when entering cache failure state so this will never
758 // be called in CACHE_FAILURE state.
760 net::URLRequest* request = fetcher->request();
761 const GURL& url = request->original_url();
762 master_entry_fetches_.erase(url);
763 ++master_entries_completed_;
765 int response_code = request->status().is_success()
766 ? request->GetResponseCode() : -1;
768 PendingMasters::iterator found = pending_master_entries_.find(url);
769 DCHECK(found != pending_master_entries_.end());
770 PendingHosts& hosts = found->second;
772 // Section 6.9.4. No update case: step 7.3, else step 22.
773 if (response_code / 100 == 2) {
774 // Add fetched master entry to the appropriate cache.
775 AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
776 : group_->newest_complete_cache();
777 DCHECK(fetcher->response_writer());
778 AppCacheEntry master_entry(AppCacheEntry::MASTER,
779 fetcher->response_writer()->response_id(),
780 fetcher->response_writer()->amount_written());
781 if (cache->AddOrModifyEntry(url, master_entry))
782 added_master_entries_.push_back(url);
784 duplicate_response_ids_.push_back(master_entry.response_id());
786 // In no-update case, associate host with the newest cache.
787 if (!inprogress_cache_.get()) {
788 // TODO(michaeln): defer until the updated cache has been stored
789 DCHECK(cache == group_->newest_complete_cache());
790 for (PendingHosts::iterator host_it = hosts.begin();
791 host_it != hosts.end(); ++host_it) {
792 (*host_it)->AssociateCompleteCache(cache);
796 HostNotifier host_notifier;
797 for (PendingHosts::iterator host_it = hosts.begin();
798 host_it != hosts.end(); ++host_it) {
799 AppCacheHost* host = *host_it;
800 host_notifier.AddHost(host);
802 // In downloading case, disassociate host from inprogress cache.
803 if (inprogress_cache_.get())
804 host->AssociateNoCache(GURL());
806 host->RemoveObserver(this);
810 const char* kFormatString = "Manifest fetch failed (%d) %s";
811 std::string message = FormatUrlErrorMessage(
812 kFormatString, request->url(), fetcher->result(), response_code);
813 host_notifier.SendErrorNotifications(
814 AppCacheErrorDetails(message,
815 APPCACHE_MANIFEST_ERROR,
818 false /*is_cross_origin*/));
820 // In downloading case, update result is different if all master entries
821 // failed vs. only some failing.
822 if (inprogress_cache_.get()) {
823 // Only count successful downloads to know if all master entries failed.
824 pending_master_entries_.erase(found);
825 --master_entries_completed_;
827 // Section 6.9.4, step 22.3.
828 if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
829 HandleCacheFailure(AppCacheErrorDetails(message,
830 APPCACHE_MANIFEST_ERROR,
833 false /*is_cross_origin*/),
841 DCHECK(internal_state_ != CACHE_FAILURE);
842 FetchMasterEntries();
843 MaybeCompleteUpdate();
846 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
847 URLFetcher* fetcher) {
848 DCHECK(internal_state_ == REFETCH_MANIFEST);
849 DCHECK(manifest_fetcher_ == fetcher);
850 manifest_fetcher_ = NULL;
852 net::URLRequest* request = fetcher->request();
853 int response_code = request->status().is_success()
854 ? request->GetResponseCode() : -1;
855 if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
856 // Only need to store response in storage if manifest is not already
857 // an entry in the cache.
858 AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
860 entry->add_types(AppCacheEntry::MANIFEST);
861 StoreGroupAndCache();
863 manifest_response_writer_.reset(CreateResponseWriter());
864 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
865 new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
866 manifest_response_writer_->WriteInfo(
868 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
869 base::Unretained(this)));
872 VLOG(1) << "Request status: " << request->status().status()
873 << " error: " << request->status().error()
874 << " response code: " << response_code;
875 ScheduleUpdateRetry(kRerunDelayMs);
876 if (response_code == 200) {
877 HandleCacheFailure(AppCacheErrorDetails("Manifest changed during update",
878 APPCACHE_CHANGED_ERROR,
881 false /*is_cross_origin*/),
885 const char* kFormatString = "Manifest re-fetch failed (%d) %s";
886 std::string message = FormatUrlErrorMessage(
887 kFormatString, manifest_url_, fetcher->result(), response_code);
888 HandleCacheFailure(AppCacheErrorDetails(message,
889 APPCACHE_MANIFEST_ERROR,
892 false /*is_cross_origin*/),
899 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
901 scoped_refptr<net::StringIOBuffer> io_buffer(
902 new net::StringIOBuffer(manifest_data_));
903 manifest_response_writer_->WriteData(
905 manifest_data_.length(),
906 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
907 base::Unretained(this)));
910 AppCacheErrorDetails("Failed to write the manifest headers to storage",
911 APPCACHE_UNKNOWN_ERROR,
914 false /*is_cross_origin*/),
920 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
922 AppCacheEntry entry(AppCacheEntry::MANIFEST,
923 manifest_response_writer_->response_id(),
924 manifest_response_writer_->amount_written());
925 if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
926 duplicate_response_ids_.push_back(entry.response_id());
927 StoreGroupAndCache();
930 AppCacheErrorDetails("Failed to write the manifest data to storage",
931 APPCACHE_UNKNOWN_ERROR,
934 false /*is_cross_origin*/),
940 void AppCacheUpdateJob::StoreGroupAndCache() {
941 DCHECK(stored_state_ == UNSTORED);
942 stored_state_ = STORING;
943 scoped_refptr<AppCache> newest_cache;
944 if (inprogress_cache_.get())
945 newest_cache.swap(inprogress_cache_);
947 newest_cache = group_->newest_complete_cache();
948 newest_cache->set_update_time(base::Time::Now());
950 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
951 DCHECK_EQ(manifest_url_, group_->manifest_url());
952 storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
955 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
956 AppCache* newest_cache,
958 bool would_exceed_quota) {
959 DCHECK(stored_state_ == STORING);
961 stored_state_ = STORED;
962 MaybeCompleteUpdate(); // will definitely complete
964 stored_state_ = UNSTORED;
966 // Restore inprogress_cache_ to get the proper events delivered
967 // and the proper cleanup to occur.
968 if (newest_cache != group->newest_complete_cache())
969 inprogress_cache_ = newest_cache;
971 ResultType result = DB_ERROR;
972 AppCacheErrorReason reason = APPCACHE_UNKNOWN_ERROR;
973 std::string message("Failed to commit new cache to storage");
974 if (would_exceed_quota) {
975 message.append(", would exceed quota");
976 result = QUOTA_ERROR;
977 reason = APPCACHE_QUOTA_ERROR;
980 AppCacheErrorDetails(message, reason, GURL(), 0,
981 false /*is_cross_origin*/),
987 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
988 AppCacheEventID event_id) {
989 std::vector<int> ids(1, host->host_id());
990 host->frontend()->OnEventRaised(ids, event_id);
993 void AppCacheUpdateJob::NotifyAllAssociatedHosts(AppCacheEventID event_id) {
994 HostNotifier host_notifier;
995 AddAllAssociatedHostsToNotifier(&host_notifier);
996 host_notifier.SendNotifications(event_id);
999 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
1000 HostNotifier host_notifier;
1001 AddAllAssociatedHostsToNotifier(&host_notifier);
1002 host_notifier.SendProgressNotifications(
1003 url, url_file_list_.size(), url_fetches_completed_);
1006 void AppCacheUpdateJob::NotifyAllFinalProgress() {
1007 DCHECK(url_file_list_.size() == url_fetches_completed_);
1008 NotifyAllProgress(GURL());
1011 void AppCacheUpdateJob::NotifyAllError(const AppCacheErrorDetails& details) {
1012 HostNotifier host_notifier;
1013 AddAllAssociatedHostsToNotifier(&host_notifier);
1014 host_notifier.SendErrorNotifications(details);
1017 void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string& message) {
1018 HostNotifier host_notifier;
1019 AddAllAssociatedHostsToNotifier(&host_notifier);
1020 host_notifier.SendLogMessage(message);
1023 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
1024 HostNotifier* host_notifier) {
1025 // Collect hosts so we only send one notification per frontend.
1026 // A host can only be associated with a single cache so no need to worry
1027 // about duplicate hosts being added to the notifier.
1028 if (inprogress_cache_.get()) {
1029 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
1030 host_notifier->AddHosts(inprogress_cache_->associated_hosts());
1033 AppCacheGroup::Caches old_caches = group_->old_caches();
1034 for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
1035 it != old_caches.end(); ++it) {
1036 host_notifier->AddHosts((*it)->associated_hosts());
1039 AppCache* newest_cache = group_->newest_complete_cache();
1041 host_notifier->AddHosts(newest_cache->associated_hosts());
1044 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
1045 // The host is about to be deleted; remove from our collection.
1046 PendingMasters::iterator found =
1047 pending_master_entries_.find(host->pending_master_entry_url());
1048 DCHECK(found != pending_master_entries_.end());
1049 PendingHosts& hosts = found->second;
1050 PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
1051 DCHECK(it != hosts.end());
1055 void AppCacheUpdateJob::OnServiceReinitialized(
1056 AppCacheStorageReference* old_storage_ref) {
1057 // We continue to use the disabled instance, but arrange for its
1058 // deletion when its no longer needed.
1059 if (old_storage_ref->storage() == storage_)
1060 disabled_storage_reference_ = old_storage_ref;
1063 void AppCacheUpdateJob::CheckIfManifestChanged() {
1064 DCHECK(update_type_ == UPGRADE_ATTEMPT);
1065 AppCacheEntry* entry = NULL;
1066 if (group_->newest_complete_cache())
1067 entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
1069 // TODO(michaeln): This is just a bandaid to avoid a crash.
1070 // http://code.google.com/p/chromium/issues/detail?id=95101
1071 if (service_->storage() == storage_) {
1072 // Use a local variable because service_ is reset in HandleCacheFailure.
1073 AppCacheServiceImpl* service = service_;
1075 AppCacheErrorDetails("Manifest entry not found in existing cache",
1076 APPCACHE_UNKNOWN_ERROR,
1079 false /*is_cross_origin*/),
1082 AppCacheHistograms::AddMissingManifestEntrySample();
1083 service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
1088 // Load manifest data from storage to compare against fetched manifest.
1089 manifest_response_reader_.reset(
1090 storage_->CreateResponseReader(manifest_url_,
1092 entry->response_id()));
1093 read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
1094 manifest_response_reader_->ReadData(
1095 read_manifest_buffer_.get(),
1097 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1098 base::Unretained(this))); // async read
1101 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
1103 loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
1104 manifest_response_reader_->ReadData(
1105 read_manifest_buffer_.get(),
1107 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1108 base::Unretained(this))); // read more
1110 read_manifest_buffer_ = NULL;
1111 manifest_response_reader_.reset();
1112 ContinueHandleManifestFetchCompleted(
1113 result < 0 || manifest_data_ != loaded_manifest_data_);
1117 void AppCacheUpdateJob::BuildUrlFileList(const AppCacheManifest& manifest) {
1118 for (base::hash_set<std::string>::const_iterator it =
1119 manifest.explicit_urls.begin();
1120 it != manifest.explicit_urls.end(); ++it) {
1121 AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
1124 const std::vector<AppCacheNamespace>& intercepts =
1125 manifest.intercept_namespaces;
1126 for (std::vector<AppCacheNamespace>::const_iterator it = intercepts.begin();
1127 it != intercepts.end(); ++it) {
1128 int flags = AppCacheEntry::INTERCEPT;
1129 if (it->is_executable)
1130 flags |= AppCacheEntry::EXECUTABLE;
1131 AddUrlToFileList(it->target_url, flags);
1134 const std::vector<AppCacheNamespace>& fallbacks =
1135 manifest.fallback_namespaces;
1136 for (std::vector<AppCacheNamespace>::const_iterator it = fallbacks.begin();
1137 it != fallbacks.end(); ++it) {
1138 AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
1141 // Add all master entries from newest complete cache.
1142 if (update_type_ == UPGRADE_ATTEMPT) {
1143 const AppCache::EntryMap& entries =
1144 group_->newest_complete_cache()->entries();
1145 for (AppCache::EntryMap::const_iterator it = entries.begin();
1146 it != entries.end(); ++it) {
1147 const AppCacheEntry& entry = it->second;
1148 if (entry.IsMaster())
1149 AddUrlToFileList(it->first, AppCacheEntry::MASTER);
1154 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
1155 std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
1156 AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
1159 urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
1161 ret.first->second.add_types(type); // URL already exists. Merge types.
1164 void AppCacheUpdateJob::FetchUrls() {
1165 DCHECK(internal_state_ == DOWNLOADING);
1167 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
1168 // Fetch up to the concurrent limit. Other fetches will be triggered as each
1169 // each fetch completes.
1170 while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
1171 !urls_to_fetch_.empty()) {
1172 UrlToFetch url_to_fetch = urls_to_fetch_.front();
1173 urls_to_fetch_.pop_front();
1175 AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
1176 DCHECK(it != url_file_list_.end());
1177 AppCacheEntry& entry = it->second;
1178 if (ShouldSkipUrlFetch(entry)) {
1179 NotifyAllProgress(url_to_fetch.url);
1180 ++url_fetches_completed_;
1181 } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
1182 NotifyAllProgress(url_to_fetch.url);
1183 ++url_fetches_completed_; // saved a URL request
1184 } else if (!url_to_fetch.storage_checked &&
1185 MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
1186 // Continues asynchronously after data is loaded from newest cache.
1188 URLFetcher* fetcher = new URLFetcher(
1189 url_to_fetch.url, URLFetcher::URL_FETCH, this);
1190 if (url_to_fetch.existing_response_info.get()) {
1191 DCHECK(group_->newest_complete_cache());
1192 AppCacheEntry* existing_entry =
1193 group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
1194 DCHECK(existing_entry);
1195 DCHECK(existing_entry->response_id() ==
1196 url_to_fetch.existing_response_info->response_id());
1197 fetcher->set_existing_response_headers(
1198 url_to_fetch.existing_response_info->http_response_info()->headers
1200 fetcher->set_existing_entry(*existing_entry);
1203 pending_url_fetches_.insert(
1204 PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
1209 void AppCacheUpdateJob::CancelAllUrlFetches() {
1210 // Cancel any pending URL requests.
1211 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1212 it != pending_url_fetches_.end(); ++it) {
1216 url_fetches_completed_ +=
1217 pending_url_fetches_.size() + urls_to_fetch_.size();
1218 pending_url_fetches_.clear();
1219 urls_to_fetch_.clear();
1222 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
1224 // If the resource URL being processed was flagged as neither an
1225 // "explicit entry" nor or a "fallback entry", then the user agent
1226 // may skip this URL.
1227 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
1230 // TODO(jennb): decide if entry should be skipped to expire it from cache
1234 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
1236 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
1237 AppCacheEntry* existing =
1238 inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
1239 : group_->newest_complete_cache()->GetEntry(url);
1241 existing->add_types(entry_type);
1247 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
1250 DCHECK(!IsTerminating());
1252 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
1254 if (inprogress_cache_.get()) {
1256 host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
1257 cache = inprogress_cache_.get();
1259 cache = group_->newest_complete_cache();
1262 // Update existing entry if it has already been fetched.
1263 AppCacheEntry* entry = cache->GetEntry(url);
1265 entry->add_types(AppCacheEntry::MASTER);
1266 if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
1267 // only associate if have entry
1268 host->AssociateCompleteCache(cache);
1271 ++master_entries_completed_; // pretend fetching completed
1276 // Add to fetch list if not already fetching.
1277 if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
1278 master_entries_to_fetch_.insert(url);
1279 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
1280 FetchMasterEntries();
1284 void AppCacheUpdateJob::FetchMasterEntries() {
1285 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
1287 // Fetch each master entry in the list, up to the concurrent limit.
1288 // Additional fetches will be triggered as each fetch completes.
1289 while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
1290 !master_entries_to_fetch_.empty()) {
1291 const GURL& url = *master_entries_to_fetch_.begin();
1293 if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
1294 ++master_entries_completed_; // saved a URL request
1296 // In no update case, associate hosts to newest cache in group
1297 // now that master entry has been "successfully downloaded".
1298 if (internal_state_ == NO_UPDATE) {
1299 // TODO(michaeln): defer until the updated cache has been stored.
1300 DCHECK(!inprogress_cache_.get());
1301 AppCache* cache = group_->newest_complete_cache();
1302 PendingMasters::iterator found = pending_master_entries_.find(url);
1303 DCHECK(found != pending_master_entries_.end());
1304 PendingHosts& hosts = found->second;
1305 for (PendingHosts::iterator host_it = hosts.begin();
1306 host_it != hosts.end(); ++host_it) {
1307 (*host_it)->AssociateCompleteCache(cache);
1311 URLFetcher* fetcher = new URLFetcher(
1312 url, URLFetcher::MASTER_ENTRY_FETCH, this);
1314 master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
1317 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1321 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1322 const AppCacheErrorDetails& error_details) {
1323 // For now, cancel all in-progress fetches for master entries and pretend
1324 // all master entries fetches have completed.
1325 // TODO(jennb): Delete this when update no longer fetches master entries
1328 // Cancel all in-progress fetches.
1329 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1330 it != master_entry_fetches_.end(); ++it) {
1332 master_entries_to_fetch_.insert(it->first); // back in unfetched list
1334 master_entry_fetches_.clear();
1336 master_entries_completed_ += master_entries_to_fetch_.size();
1338 // Cache failure steps, step 2.
1339 // Pretend all master entries that have not yet been fetched have completed
1340 // downloading. Unassociate hosts from any appcache and send ERROR event.
1341 HostNotifier host_notifier;
1342 while (!master_entries_to_fetch_.empty()) {
1343 const GURL& url = *master_entries_to_fetch_.begin();
1344 PendingMasters::iterator found = pending_master_entries_.find(url);
1345 DCHECK(found != pending_master_entries_.end());
1346 PendingHosts& hosts = found->second;
1347 for (PendingHosts::iterator host_it = hosts.begin();
1348 host_it != hosts.end(); ++host_it) {
1349 AppCacheHost* host = *host_it;
1350 host->AssociateNoCache(GURL());
1351 host_notifier.AddHost(host);
1352 host->RemoveObserver(this);
1356 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1358 host_notifier.SendErrorNotifications(error_details);
1361 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
1362 AppCacheEntry& entry) {
1363 if (update_type_ != UPGRADE_ATTEMPT)
1366 AppCache* newest = group_->newest_complete_cache();
1367 AppCacheEntry* copy_me = newest->GetEntry(url);
1368 if (!copy_me || !copy_me->has_response_id())
1371 // Load HTTP headers for entry from newest cache.
1372 loading_responses_.insert(
1373 LoadingResponses::value_type(copy_me->response_id(), url));
1374 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
1375 copy_me->response_id(),
1377 // Async: wait for OnResponseInfoLoaded to complete.
1381 void AppCacheUpdateJob::OnResponseInfoLoaded(
1382 AppCacheResponseInfo* response_info, int64 response_id) {
1383 const net::HttpResponseInfo* http_info = response_info ?
1384 response_info->http_response_info() : NULL;
1386 // Needed response info for a manifest fetch request.
1387 if (internal_state_ == FETCH_MANIFEST) {
1389 manifest_fetcher_->set_existing_response_headers(
1390 http_info->headers.get());
1391 manifest_fetcher_->Start();
1395 LoadingResponses::iterator found = loading_responses_.find(response_id);
1396 DCHECK(found != loading_responses_.end());
1397 const GURL& url = found->second;
1400 LoadFromNewestCacheFailed(url, NULL); // no response found
1402 // Check if response can be re-used according to HTTP caching semantics.
1403 // Responses with a "vary" header get treated as expired.
1404 const std::string name = "vary";
1407 if (!http_info->headers.get() ||
1408 http_info->headers->RequiresValidation(http_info->request_time,
1409 http_info->response_time,
1410 base::Time::Now()) ||
1411 http_info->headers->EnumerateHeader(&iter, name, &value)) {
1412 LoadFromNewestCacheFailed(url, response_info);
1414 DCHECK(group_->newest_complete_cache());
1415 AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
1417 DCHECK(copy_me->response_id() == response_id);
1419 AppCache::EntryMap::iterator it = url_file_list_.find(url);
1420 DCHECK(it != url_file_list_.end());
1421 AppCacheEntry& entry = it->second;
1422 entry.set_response_id(response_id);
1423 entry.set_response_size(copy_me->response_size());
1424 inprogress_cache_->AddOrModifyEntry(url, entry);
1425 NotifyAllProgress(url);
1426 ++url_fetches_completed_;
1429 loading_responses_.erase(found);
1431 MaybeCompleteUpdate();
1434 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1435 const GURL& url, AppCacheResponseInfo* response_info) {
1436 if (internal_state_ == CACHE_FAILURE)
1439 // Re-insert url at front of fetch list. Indicate storage has been checked.
1440 urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
1444 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1445 DCHECK(internal_state_ != CACHE_FAILURE);
1447 // Must wait for any pending master entries or url fetches to complete.
1448 if (master_entries_completed_ != pending_master_entries_.size() ||
1449 url_fetches_completed_ != url_file_list_.size()) {
1450 DCHECK(internal_state_ != COMPLETED);
1454 switch (internal_state_) {
1456 if (master_entries_completed_ > 0) {
1457 switch (stored_state_) {
1459 StoreGroupAndCache();
1467 // 6.9.4 steps 7.3-7.7.
1468 NotifyAllAssociatedHosts(APPCACHE_NO_UPDATE_EVENT);
1469 DiscardDuplicateResponses();
1470 internal_state_ = COMPLETED;
1473 internal_state_ = REFETCH_MANIFEST;
1474 FetchManifest(false);
1476 case REFETCH_MANIFEST:
1477 DCHECK(stored_state_ == STORED);
1478 NotifyAllFinalProgress();
1479 if (update_type_ == CACHE_ATTEMPT)
1480 NotifyAllAssociatedHosts(APPCACHE_CACHED_EVENT);
1482 NotifyAllAssociatedHosts(APPCACHE_UPDATE_READY_EVENT);
1483 DiscardDuplicateResponses();
1484 internal_state_ = COMPLETED;
1485 LogHistogramStats(UPDATE_OK, GURL());
1488 NOTREACHED(); // See HandleCacheFailure
1494 // Let the stack unwind before deletion to make it less risky as this
1495 // method is called from multiple places in this file.
1496 if (internal_state_ == COMPLETED)
1500 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
1501 // TODO(jennb): post a delayed task with the "same parameters" as this job
1502 // to retry the update at a later time. Need group, URLs of pending master
1503 // entries and their hosts.
1506 void AppCacheUpdateJob::Cancel() {
1507 internal_state_ = CANCELLED;
1509 LogHistogramStats(CANCELLED_ERROR, GURL());
1511 if (manifest_fetcher_) {
1512 delete manifest_fetcher_;
1513 manifest_fetcher_ = NULL;
1516 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1517 it != pending_url_fetches_.end(); ++it) {
1520 pending_url_fetches_.clear();
1522 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1523 it != master_entry_fetches_.end(); ++it) {
1526 master_entry_fetches_.clear();
1528 ClearPendingMasterEntries();
1529 DiscardInprogressCache();
1531 // Delete response writer to avoid any callbacks.
1532 if (manifest_response_writer_)
1533 manifest_response_writer_.reset();
1535 storage_->CancelDelegateCallbacks(this);
1538 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1539 for (PendingMasters::iterator it = pending_master_entries_.begin();
1540 it != pending_master_entries_.end(); ++it) {
1541 PendingHosts& hosts = it->second;
1542 for (PendingHosts::iterator host_it = hosts.begin();
1543 host_it != hosts.end(); ++host_it) {
1544 (*host_it)->RemoveObserver(this);
1548 pending_master_entries_.clear();
1551 void AppCacheUpdateJob::DiscardInprogressCache() {
1552 if (stored_state_ == STORING) {
1553 // We can make no assumptions about whether the StoreGroupAndCacheTask
1554 // actually completed or not. This condition should only be reachable
1555 // during shutdown. Free things up and return to do no harm.
1556 inprogress_cache_ = NULL;
1557 added_master_entries_.clear();
1561 storage_->DoomResponses(manifest_url_, stored_response_ids_);
1563 if (!inprogress_cache_.get()) {
1564 // We have to undo the changes we made, if any, to the existing cache.
1565 if (group_ && group_->newest_complete_cache()) {
1566 for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
1567 iter != added_master_entries_.end(); ++iter) {
1568 group_->newest_complete_cache()->RemoveEntry(*iter);
1571 added_master_entries_.clear();
1575 AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
1576 while (!hosts.empty())
1577 (*hosts.begin())->AssociateNoCache(GURL());
1579 inprogress_cache_ = NULL;
1580 added_master_entries_.clear();
1583 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1584 storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
1587 void AppCacheUpdateJob::LogHistogramStats(
1588 ResultType result, const GURL& failed_resource_url) {
1589 AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin());
1590 if (result == UPDATE_OK)
1593 int percent_complete = 0;
1594 if (url_file_list_.size() > 0) {
1595 size_t actual_fetches_completed = url_fetches_completed_;
1596 if (!failed_resource_url.is_empty() && actual_fetches_completed)
1597 --actual_fetches_completed;
1598 percent_complete = (static_cast<double>(actual_fetches_completed) /
1599 static_cast<double>(url_file_list_.size())) * 100.0;
1600 percent_complete = std::min(percent_complete, 99);
1603 bool was_making_progress =
1604 base::Time::Now() - last_progress_time_ <
1605 base::TimeDelta::FromMinutes(5);
1607 bool off_origin_resource_failure =
1608 !failed_resource_url.is_empty() &&
1609 (failed_resource_url.GetOrigin() != manifest_url_.GetOrigin());
1611 AppCacheHistograms::LogUpdateFailureStats(
1612 manifest_url_.GetOrigin(),
1614 was_making_progress,
1615 off_origin_resource_failure);
1618 void AppCacheUpdateJob::DeleteSoon() {
1619 ClearPendingMasterEntries();
1620 manifest_response_writer_.reset();
1621 storage_->CancelDelegateCallbacks(this);
1622 service_->RemoveObserver(this);
1625 // Break the connection with the group so the group cannot call delete
1626 // on this object after we've posted a task to delete ourselves.
1627 group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
1630 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
1633 } // namespace content