Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / content / browser / appcache / appcache_update_job.cc
blob351bd80aade6b634b26bbe4983310b028fb20bec
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"
7 #include "base/bind.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/io_buffer.h"
16 #include "net/base/load_flags.h"
17 #include "net/base/net_errors.h"
18 #include "net/base/request_priority.h"
19 #include "net/http/http_request_headers.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/url_request/url_request_context.h"
23 namespace content {
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,
32 int response_code) {
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.
42 class HostNotifier {
43 public:
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) {
57 AddHost(*it);
61 void SendNotifications(AppCacheEventID 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 AppCacheErrorDetails& 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, APPCACHE_LOG_WARNING, message);
99 private:
100 NotifyHostMap hosts_to_notify;
103 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
104 bool checked,
105 AppCacheResponseInfo* info)
106 : url(url),
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)
120 : url_(url),
121 job_(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)),
127 result_(UPDATE_OK),
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());
139 request_->Start();
142 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
143 net::URLRequest* request,
144 const net::RedirectInfo& redirect_info,
145 bool* defer_redirect) {
146 DCHECK(request_ == request);
147 // Redirect is not allowed by the update process.
148 job_->MadeProgress();
149 redirect_response_code_ = request->GetResponseCode();
150 request->Cancel();
151 result_ = REDIRECT_ERROR;
152 OnResponseCompleted();
155 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
156 net::URLRequest *request) {
157 DCHECK(request == request_);
158 int response_code = -1;
159 if (request->status().is_success()) {
160 response_code = request->GetResponseCode();
161 job_->MadeProgress();
163 if ((response_code / 100) == 2) {
165 // See http://code.google.com/p/chromium/issues/detail?id=69594
166 // We willfully violate the HTML5 spec at this point in order
167 // to support the appcaching of cross-origin HTTPS resources.
168 // We've opted for a milder constraint and allow caching unless
169 // the resource has a "no-store" header. A spec change has been
170 // requested on the whatwg list.
171 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
172 if (url_.SchemeIsSecure() &&
173 url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
174 if (request->response_headers()->
175 HasHeaderValue("cache-control", "no-store")) {
176 DCHECK_EQ(-1, redirect_response_code_);
177 request->Cancel();
178 result_ = SERVER_ERROR; // Not the best match?
179 OnResponseCompleted();
180 return;
184 // Write response info to storage for URL fetches. Wait for async write
185 // completion before reading any response data.
186 if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
187 response_writer_.reset(job_->CreateResponseWriter());
188 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
189 new HttpResponseInfoIOBuffer(
190 new net::HttpResponseInfo(request->response_info())));
191 response_writer_->WriteInfo(
192 io_buffer.get(),
193 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
194 } else {
195 ReadResponseData();
197 } else {
198 if (response_code > 0)
199 result_ = SERVER_ERROR;
200 else
201 result_ = NETWORK_ERROR;
202 OnResponseCompleted();
206 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
207 net::URLRequest* request, int bytes_read) {
208 DCHECK(request_ == request);
209 bool data_consumed = true;
210 if (request->status().is_success() && bytes_read > 0) {
211 job_->MadeProgress();
212 data_consumed = ConsumeResponseData(bytes_read);
213 if (data_consumed) {
214 bytes_read = 0;
215 while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
216 if (bytes_read > 0) {
217 data_consumed = ConsumeResponseData(bytes_read);
218 if (!data_consumed)
219 break; // wait for async data processing, then read more
220 } else {
221 break;
226 if (data_consumed && !request->status().is_io_pending()) {
227 DCHECK_EQ(UPDATE_OK, result_);
228 OnResponseCompleted();
232 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
233 const net::HttpResponseHeaders* headers) {
234 DCHECK(request_.get() && headers);
235 net::HttpRequestHeaders extra_headers;
237 // Add If-Modified-Since header if response info has Last-Modified header.
238 const std::string last_modified = "Last-Modified";
239 std::string last_modified_value;
240 headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
241 if (!last_modified_value.empty()) {
242 extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
243 last_modified_value);
246 // Add If-None-Match header if response info has ETag header.
247 const std::string etag = "ETag";
248 std::string etag_value;
249 headers->EnumerateHeader(NULL, etag, &etag_value);
250 if (!etag_value.empty()) {
251 extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
252 etag_value);
254 if (!extra_headers.IsEmpty())
255 request_->SetExtraRequestHeaders(extra_headers);
258 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
259 if (result < 0) {
260 request_->Cancel();
261 result_ = DISKCACHE_ERROR;
262 OnResponseCompleted();
263 return;
265 ReadResponseData();
268 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
269 InternalUpdateState state = job_->internal_state_;
270 if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
271 return;
272 int bytes_read = 0;
273 request_->Read(buffer_.get(), kBufferSize, &bytes_read);
274 OnReadCompleted(request_.get(), bytes_read);
277 // Returns false if response data is processed asynchronously, in which
278 // case ReadResponseData will be invoked when it is safe to continue
279 // reading more response data from the request.
280 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
281 DCHECK_GT(bytes_read, 0);
282 switch (fetch_type_) {
283 case MANIFEST_FETCH:
284 case MANIFEST_REFETCH:
285 manifest_data_.append(buffer_->data(), bytes_read);
286 break;
287 case URL_FETCH:
288 case MASTER_ENTRY_FETCH:
289 DCHECK(response_writer_.get());
290 response_writer_->WriteData(
291 buffer_.get(),
292 bytes_read,
293 base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
294 return false; // wait for async write completion to continue reading
295 default:
296 NOTREACHED();
298 return true;
301 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
302 if (request_->status().is_success())
303 job_->MadeProgress();
305 // Retry for 503s where retry-after is 0.
306 if (request_->status().is_success() &&
307 request_->GetResponseCode() == 503 &&
308 MaybeRetryRequest()) {
309 return;
312 switch (fetch_type_) {
313 case MANIFEST_FETCH:
314 job_->HandleManifestFetchCompleted(this);
315 break;
316 case URL_FETCH:
317 job_->HandleUrlFetchCompleted(this);
318 break;
319 case MASTER_ENTRY_FETCH:
320 job_->HandleMasterEntryFetchCompleted(this);
321 break;
322 case MANIFEST_REFETCH:
323 job_->HandleManifestRefetchCompleted(this);
324 break;
325 default:
326 NOTREACHED();
329 delete this;
332 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
333 if (retry_503_attempts_ >= kMax503Retries ||
334 !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
335 return false;
337 ++retry_503_attempts_;
338 result_ = UPDATE_OK;
339 request_ = job_->service_->request_context()->CreateRequest(
340 url_, net::DEFAULT_PRIORITY, this, NULL);
341 Start();
342 return true;
345 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheServiceImpl* service,
346 AppCacheGroup* group)
347 : service_(service),
348 manifest_url_(group->manifest_url()),
349 group_(group),
350 update_type_(UNKNOWN_TYPE),
351 internal_state_(FETCH_MANIFEST),
352 master_entries_completed_(0),
353 url_fetches_completed_(0),
354 manifest_fetcher_(NULL),
355 manifest_has_valid_mime_type_(false),
356 stored_state_(UNSTORED),
357 storage_(service->storage()) {
358 service_->AddObserver(this);
361 AppCacheUpdateJob::~AppCacheUpdateJob() {
362 if (service_)
363 service_->RemoveObserver(this);
364 if (internal_state_ != COMPLETED)
365 Cancel();
367 DCHECK(!manifest_fetcher_);
368 DCHECK(pending_url_fetches_.empty());
369 DCHECK(!inprogress_cache_.get());
370 DCHECK(pending_master_entries_.empty());
371 DCHECK(master_entry_fetches_.empty());
373 if (group_)
374 group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
377 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
378 const GURL& new_master_resource) {
379 DCHECK(group_->update_job() == this);
380 DCHECK(!group_->is_obsolete());
382 bool is_new_pending_master_entry = false;
383 if (!new_master_resource.is_empty()) {
384 DCHECK(new_master_resource == host->pending_master_entry_url());
385 DCHECK(!new_master_resource.has_ref());
386 DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
388 // Cannot add more to this update if already terminating.
389 if (IsTerminating()) {
390 group_->QueueUpdate(host, new_master_resource);
391 return;
394 std::pair<PendingMasters::iterator, bool> ret =
395 pending_master_entries_.insert(
396 PendingMasters::value_type(new_master_resource, PendingHosts()));
397 is_new_pending_master_entry = ret.second;
398 ret.first->second.push_back(host);
399 host->AddObserver(this);
402 // Notify host (if any) if already checking or downloading.
403 AppCacheGroup::UpdateAppCacheStatus update_status = group_->update_status();
404 if (update_status == AppCacheGroup::CHECKING ||
405 update_status == AppCacheGroup::DOWNLOADING) {
406 if (host) {
407 NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
408 if (update_status == AppCacheGroup::DOWNLOADING)
409 NotifySingleHost(host, APPCACHE_DOWNLOADING_EVENT);
411 // Add to fetch list or an existing entry if already fetched.
412 if (!new_master_resource.is_empty()) {
413 AddMasterEntryToFetchList(host, new_master_resource,
414 is_new_pending_master_entry);
417 return;
420 // Begin update process for the group.
421 MadeProgress();
422 group_->SetUpdateAppCacheStatus(AppCacheGroup::CHECKING);
423 if (group_->HasCache()) {
424 update_type_ = UPGRADE_ATTEMPT;
425 NotifyAllAssociatedHosts(APPCACHE_CHECKING_EVENT);
426 } else {
427 update_type_ = CACHE_ATTEMPT;
428 DCHECK(host);
429 NotifySingleHost(host, APPCACHE_CHECKING_EVENT);
432 if (!new_master_resource.is_empty()) {
433 AddMasterEntryToFetchList(host, new_master_resource,
434 is_new_pending_master_entry);
437 FetchManifest(true);
440 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
441 AppCacheResponseWriter* writer =
442 storage_->CreateResponseWriter(manifest_url_,
443 group_->group_id());
444 stored_response_ids_.push_back(writer->response_id());
445 return writer;
448 void AppCacheUpdateJob::HandleCacheFailure(
449 const AppCacheErrorDetails& error_details,
450 ResultType result,
451 const GURL& failed_resource_url) {
452 // 6.9.4 cache failure steps 2-8.
453 DCHECK(internal_state_ != CACHE_FAILURE);
454 DCHECK(!error_details.message.empty());
455 DCHECK(result != UPDATE_OK);
456 internal_state_ = CACHE_FAILURE;
457 LogHistogramStats(result, failed_resource_url);
458 CancelAllUrlFetches();
459 CancelAllMasterEntryFetches(error_details);
460 NotifyAllError(error_details);
461 DiscardInprogressCache();
462 internal_state_ = COMPLETED;
463 DeleteSoon(); // To unwind the stack prior to deletion.
466 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
467 DCHECK(!manifest_fetcher_);
468 manifest_fetcher_ = new URLFetcher(
469 manifest_url_,
470 is_first_fetch ? URLFetcher::MANIFEST_FETCH :
471 URLFetcher::MANIFEST_REFETCH,
472 this);
474 // Add any necessary Http headers before sending fetch request.
475 if (is_first_fetch) {
476 AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
477 group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
478 if (entry) {
479 // Asynchronously load response info for manifest from newest cache.
480 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
481 entry->response_id(), this);
482 } else {
483 manifest_fetcher_->Start();
485 } else {
486 DCHECK(internal_state_ == REFETCH_MANIFEST);
487 DCHECK(manifest_response_info_.get());
488 manifest_fetcher_->set_existing_response_headers(
489 manifest_response_info_->headers.get());
490 manifest_fetcher_->Start();
495 void AppCacheUpdateJob::HandleManifestFetchCompleted(
496 URLFetcher* fetcher) {
497 DCHECK_EQ(internal_state_, FETCH_MANIFEST);
498 DCHECK_EQ(manifest_fetcher_, fetcher);
499 manifest_fetcher_ = NULL;
501 net::URLRequest* request = fetcher->request();
502 int response_code = -1;
503 bool is_valid_response_code = false;
504 if (request->status().is_success()) {
505 response_code = request->GetResponseCode();
506 is_valid_response_code = (response_code / 100 == 2);
508 std::string mime_type;
509 request->GetMimeType(&mime_type);
510 manifest_has_valid_mime_type_ = (mime_type == "text/cache-manifest");
513 if (is_valid_response_code) {
514 manifest_data_ = fetcher->manifest_data();
515 manifest_response_info_.reset(
516 new net::HttpResponseInfo(request->response_info()));
517 if (update_type_ == UPGRADE_ATTEMPT)
518 CheckIfManifestChanged(); // continues asynchronously
519 else
520 ContinueHandleManifestFetchCompleted(true);
521 } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
522 ContinueHandleManifestFetchCompleted(false);
523 } else if ((response_code == 404 || response_code == 410) &&
524 update_type_ == UPGRADE_ATTEMPT) {
525 storage_->MakeGroupObsolete(group_, this, response_code); // async
526 } else {
527 const char* kFormatString = "Manifest fetch failed (%d) %s";
528 std::string message = FormatUrlErrorMessage(
529 kFormatString, manifest_url_, fetcher->result(), response_code);
530 HandleCacheFailure(AppCacheErrorDetails(message,
531 APPCACHE_MANIFEST_ERROR,
532 manifest_url_,
533 response_code,
534 false /*is_cross_origin*/),
535 fetcher->result(),
536 GURL());
540 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
541 bool success,
542 int response_code) {
543 DCHECK(master_entry_fetches_.empty());
544 CancelAllMasterEntryFetches(AppCacheErrorDetails(
545 "The cache has been made obsolete, "
546 "the manifest file returned 404 or 410",
547 APPCACHE_MANIFEST_ERROR,
548 GURL(),
549 response_code,
550 false /*is_cross_origin*/));
551 if (success) {
552 DCHECK(group->is_obsolete());
553 NotifyAllAssociatedHosts(APPCACHE_OBSOLETE_EVENT);
554 internal_state_ = COMPLETED;
555 MaybeCompleteUpdate();
556 } else {
557 // Treat failure to mark group obsolete as a cache failure.
558 HandleCacheFailure(AppCacheErrorDetails(
559 "Failed to mark the cache as obsolete",
560 APPCACHE_UNKNOWN_ERROR,
561 GURL(),
563 false /*is_cross_origin*/),
564 DB_ERROR,
565 GURL());
569 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
570 DCHECK(internal_state_ == FETCH_MANIFEST);
572 if (!changed) {
573 DCHECK(update_type_ == UPGRADE_ATTEMPT);
574 internal_state_ = NO_UPDATE;
576 // Wait for pending master entries to download.
577 FetchMasterEntries();
578 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
579 return;
582 Manifest manifest;
583 if (!ParseManifest(manifest_url_, manifest_data_.data(),
584 manifest_data_.length(),
585 manifest_has_valid_mime_type_ ?
586 PARSE_MANIFEST_ALLOWING_INTERCEPTS :
587 PARSE_MANIFEST_PER_STANDARD,
588 manifest)) {
589 const char* kFormatString = "Failed to parse manifest %s";
590 const std::string message = base::StringPrintf(kFormatString,
591 manifest_url_.spec().c_str());
592 HandleCacheFailure(
593 AppCacheErrorDetails(
594 message, APPCACHE_SIGNATURE_ERROR, GURL(), 0,
595 false /*is_cross_origin*/),
596 MANIFEST_ERROR,
597 GURL());
598 VLOG(1) << message;
599 return;
602 // Proceed with update process. Section 6.9.4 steps 8-20.
603 internal_state_ = DOWNLOADING;
604 inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
605 BuildUrlFileList(manifest);
606 inprogress_cache_->InitializeWithManifest(&manifest);
608 // Associate all pending master hosts with the newly created cache.
609 for (PendingMasters::iterator it = pending_master_entries_.begin();
610 it != pending_master_entries_.end(); ++it) {
611 PendingHosts& hosts = it->second;
612 for (PendingHosts::iterator host_it = hosts.begin();
613 host_it != hosts.end(); ++host_it) {
614 (*host_it)
615 ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
619 if (manifest.did_ignore_intercept_namespaces) {
620 // Must be done after associating all pending master hosts.
621 std::string message(
622 "Ignoring the INTERCEPT section of the application cache manifest "
623 "because the content type is not text/cache-manifest");
624 LogConsoleMessageToAll(message);
627 group_->SetUpdateAppCacheStatus(AppCacheGroup::DOWNLOADING);
628 NotifyAllAssociatedHosts(APPCACHE_DOWNLOADING_EVENT);
629 FetchUrls();
630 FetchMasterEntries();
631 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
634 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
635 DCHECK(internal_state_ == DOWNLOADING);
637 net::URLRequest* request = fetcher->request();
638 const GURL& url = request->original_url();
639 pending_url_fetches_.erase(url);
640 NotifyAllProgress(url);
641 ++url_fetches_completed_;
643 int response_code = request->status().is_success()
644 ? request->GetResponseCode()
645 : fetcher->redirect_response_code();
647 AppCacheEntry& entry = url_file_list_.find(url)->second;
649 if (response_code / 100 == 2) {
650 // Associate storage with the new entry.
651 DCHECK(fetcher->response_writer());
652 entry.set_response_id(fetcher->response_writer()->response_id());
653 entry.set_response_size(fetcher->response_writer()->amount_written());
654 if (!inprogress_cache_->AddOrModifyEntry(url, entry))
655 duplicate_response_ids_.push_back(entry.response_id());
657 // TODO(michaeln): Check for <html manifest=xxx>
658 // See http://code.google.com/p/chromium/issues/detail?id=97930
659 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
660 // if (!manifestAttribute) skip it
662 // Foreign entries will be detected during cache selection.
663 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
664 // file whose root element is an html element with a manifest attribute
665 // whose value doesn't match the manifest url of the application cache
666 // being processed, mark the entry as being foreign.
667 } else {
668 VLOG(1) << "Request status: " << request->status().status()
669 << " error: " << request->status().error()
670 << " response code: " << response_code;
671 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
672 if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
673 // Keep the existing response.
674 entry.set_response_id(fetcher->existing_entry().response_id());
675 entry.set_response_size(fetcher->existing_entry().response_size());
676 inprogress_cache_->AddOrModifyEntry(url, entry);
677 } else {
678 const char* kFormatString = "Resource fetch failed (%d) %s";
679 std::string message = FormatUrlErrorMessage(
680 kFormatString, url, fetcher->result(), response_code);
681 ResultType result = fetcher->result();
682 bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin();
683 switch (result) {
684 case DISKCACHE_ERROR:
685 HandleCacheFailure(
686 AppCacheErrorDetails(
687 message, APPCACHE_UNKNOWN_ERROR, GURL(), 0,
688 is_cross_origin),
689 result,
690 url);
691 break;
692 case NETWORK_ERROR:
693 HandleCacheFailure(
694 AppCacheErrorDetails(message, APPCACHE_RESOURCE_ERROR, url, 0,
695 is_cross_origin),
696 result,
697 url);
698 break;
699 default:
700 HandleCacheFailure(AppCacheErrorDetails(message,
701 APPCACHE_RESOURCE_ERROR,
702 url,
703 response_code,
704 is_cross_origin),
705 result,
706 url);
707 break;
709 return;
711 } else if (response_code == 404 || response_code == 410) {
712 // Entry is skipped. They are dropped from the cache.
713 } else if (update_type_ == UPGRADE_ATTEMPT &&
714 fetcher->existing_entry().has_response_id()) {
715 // Keep the existing response.
716 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
717 // but the old resource may or may not be compatible with the new contents
718 // of the cache. Impossible to know one way or the other.
719 entry.set_response_id(fetcher->existing_entry().response_id());
720 entry.set_response_size(fetcher->existing_entry().response_size());
721 inprogress_cache_->AddOrModifyEntry(url, entry);
725 // Fetch another URL now that one request has completed.
726 DCHECK(internal_state_ != CACHE_FAILURE);
727 FetchUrls();
728 MaybeCompleteUpdate();
731 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
732 URLFetcher* fetcher) {
733 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
735 // TODO(jennb): Handle downloads completing during cache failure when update
736 // no longer fetches master entries directly. For now, we cancel all pending
737 // master entry fetches when entering cache failure state so this will never
738 // be called in CACHE_FAILURE state.
740 net::URLRequest* request = fetcher->request();
741 const GURL& url = request->original_url();
742 master_entry_fetches_.erase(url);
743 ++master_entries_completed_;
745 int response_code = request->status().is_success()
746 ? request->GetResponseCode() : -1;
748 PendingMasters::iterator found = pending_master_entries_.find(url);
749 DCHECK(found != pending_master_entries_.end());
750 PendingHosts& hosts = found->second;
752 // Section 6.9.4. No update case: step 7.3, else step 22.
753 if (response_code / 100 == 2) {
754 // Add fetched master entry to the appropriate cache.
755 AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
756 : group_->newest_complete_cache();
757 DCHECK(fetcher->response_writer());
758 AppCacheEntry master_entry(AppCacheEntry::MASTER,
759 fetcher->response_writer()->response_id(),
760 fetcher->response_writer()->amount_written());
761 if (cache->AddOrModifyEntry(url, master_entry))
762 added_master_entries_.push_back(url);
763 else
764 duplicate_response_ids_.push_back(master_entry.response_id());
766 // In no-update case, associate host with the newest cache.
767 if (!inprogress_cache_.get()) {
768 // TODO(michaeln): defer until the updated cache has been stored
769 DCHECK(cache == group_->newest_complete_cache());
770 for (PendingHosts::iterator host_it = hosts.begin();
771 host_it != hosts.end(); ++host_it) {
772 (*host_it)->AssociateCompleteCache(cache);
775 } else {
776 HostNotifier host_notifier;
777 for (PendingHosts::iterator host_it = hosts.begin();
778 host_it != hosts.end(); ++host_it) {
779 AppCacheHost* host = *host_it;
780 host_notifier.AddHost(host);
782 // In downloading case, disassociate host from inprogress cache.
783 if (inprogress_cache_.get())
784 host->AssociateNoCache(GURL());
786 host->RemoveObserver(this);
788 hosts.clear();
790 const char* kFormatString = "Manifest fetch failed (%d) %s";
791 std::string message = FormatUrlErrorMessage(
792 kFormatString, request->url(), fetcher->result(), response_code);
793 host_notifier.SendErrorNotifications(
794 AppCacheErrorDetails(message,
795 APPCACHE_MANIFEST_ERROR,
796 request->url(),
797 response_code,
798 false /*is_cross_origin*/));
800 // In downloading case, update result is different if all master entries
801 // failed vs. only some failing.
802 if (inprogress_cache_.get()) {
803 // Only count successful downloads to know if all master entries failed.
804 pending_master_entries_.erase(found);
805 --master_entries_completed_;
807 // Section 6.9.4, step 22.3.
808 if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
809 HandleCacheFailure(AppCacheErrorDetails(message,
810 APPCACHE_MANIFEST_ERROR,
811 request->url(),
812 response_code,
813 false /*is_cross_origin*/),
814 fetcher->result(),
815 GURL());
816 return;
821 DCHECK(internal_state_ != CACHE_FAILURE);
822 FetchMasterEntries();
823 MaybeCompleteUpdate();
826 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
827 URLFetcher* fetcher) {
828 DCHECK(internal_state_ == REFETCH_MANIFEST);
829 DCHECK(manifest_fetcher_ == fetcher);
830 manifest_fetcher_ = NULL;
832 net::URLRequest* request = fetcher->request();
833 int response_code = request->status().is_success()
834 ? request->GetResponseCode() : -1;
835 if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
836 // Only need to store response in storage if manifest is not already
837 // an entry in the cache.
838 AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
839 if (entry) {
840 entry->add_types(AppCacheEntry::MANIFEST);
841 StoreGroupAndCache();
842 } else {
843 manifest_response_writer_.reset(CreateResponseWriter());
844 scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
845 new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
846 manifest_response_writer_->WriteInfo(
847 io_buffer.get(),
848 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
849 base::Unretained(this)));
851 } else {
852 VLOG(1) << "Request status: " << request->status().status()
853 << " error: " << request->status().error()
854 << " response code: " << response_code;
855 ScheduleUpdateRetry(kRerunDelayMs);
856 if (response_code == 200) {
857 HandleCacheFailure(AppCacheErrorDetails("Manifest changed during update",
858 APPCACHE_CHANGED_ERROR,
859 GURL(),
861 false /*is_cross_origin*/),
862 MANIFEST_ERROR,
863 GURL());
864 } else {
865 const char* kFormatString = "Manifest re-fetch failed (%d) %s";
866 std::string message = FormatUrlErrorMessage(
867 kFormatString, manifest_url_, fetcher->result(), response_code);
868 HandleCacheFailure(AppCacheErrorDetails(message,
869 APPCACHE_MANIFEST_ERROR,
870 GURL(),
871 response_code,
872 false /*is_cross_origin*/),
873 fetcher->result(),
874 GURL());
879 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
880 if (result > 0) {
881 scoped_refptr<net::StringIOBuffer> io_buffer(
882 new net::StringIOBuffer(manifest_data_));
883 manifest_response_writer_->WriteData(
884 io_buffer.get(),
885 manifest_data_.length(),
886 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
887 base::Unretained(this)));
888 } else {
889 HandleCacheFailure(
890 AppCacheErrorDetails("Failed to write the manifest headers to storage",
891 APPCACHE_UNKNOWN_ERROR,
892 GURL(),
894 false /*is_cross_origin*/),
895 DISKCACHE_ERROR,
896 GURL());
900 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
901 if (result > 0) {
902 AppCacheEntry entry(AppCacheEntry::MANIFEST,
903 manifest_response_writer_->response_id(),
904 manifest_response_writer_->amount_written());
905 if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
906 duplicate_response_ids_.push_back(entry.response_id());
907 StoreGroupAndCache();
908 } else {
909 HandleCacheFailure(
910 AppCacheErrorDetails("Failed to write the manifest data to storage",
911 APPCACHE_UNKNOWN_ERROR,
912 GURL(),
914 false /*is_cross_origin*/),
915 DISKCACHE_ERROR,
916 GURL());
920 void AppCacheUpdateJob::StoreGroupAndCache() {
921 DCHECK(stored_state_ == UNSTORED);
922 stored_state_ = STORING;
923 scoped_refptr<AppCache> newest_cache;
924 if (inprogress_cache_.get())
925 newest_cache.swap(inprogress_cache_);
926 else
927 newest_cache = group_->newest_complete_cache();
928 newest_cache->set_update_time(base::Time::Now());
930 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
931 DCHECK_EQ(manifest_url_, group_->manifest_url());
932 storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
935 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
936 AppCache* newest_cache,
937 bool success,
938 bool would_exceed_quota) {
939 DCHECK(stored_state_ == STORING);
940 if (success) {
941 stored_state_ = STORED;
942 MaybeCompleteUpdate(); // will definitely complete
943 } else {
944 stored_state_ = UNSTORED;
946 // Restore inprogress_cache_ to get the proper events delivered
947 // and the proper cleanup to occur.
948 if (newest_cache != group->newest_complete_cache())
949 inprogress_cache_ = newest_cache;
951 ResultType result = DB_ERROR;
952 AppCacheErrorReason reason = APPCACHE_UNKNOWN_ERROR;
953 std::string message("Failed to commit new cache to storage");
954 if (would_exceed_quota) {
955 message.append(", would exceed quota");
956 result = QUOTA_ERROR;
957 reason = APPCACHE_QUOTA_ERROR;
959 HandleCacheFailure(
960 AppCacheErrorDetails(message, reason, GURL(), 0,
961 false /*is_cross_origin*/),
962 result,
963 GURL());
967 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
968 AppCacheEventID event_id) {
969 std::vector<int> ids(1, host->host_id());
970 host->frontend()->OnEventRaised(ids, event_id);
973 void AppCacheUpdateJob::NotifyAllAssociatedHosts(AppCacheEventID event_id) {
974 HostNotifier host_notifier;
975 AddAllAssociatedHostsToNotifier(&host_notifier);
976 host_notifier.SendNotifications(event_id);
979 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
980 HostNotifier host_notifier;
981 AddAllAssociatedHostsToNotifier(&host_notifier);
982 host_notifier.SendProgressNotifications(
983 url, url_file_list_.size(), url_fetches_completed_);
986 void AppCacheUpdateJob::NotifyAllFinalProgress() {
987 DCHECK(url_file_list_.size() == url_fetches_completed_);
988 NotifyAllProgress(GURL());
991 void AppCacheUpdateJob::NotifyAllError(const AppCacheErrorDetails& details) {
992 HostNotifier host_notifier;
993 AddAllAssociatedHostsToNotifier(&host_notifier);
994 host_notifier.SendErrorNotifications(details);
997 void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string& message) {
998 HostNotifier host_notifier;
999 AddAllAssociatedHostsToNotifier(&host_notifier);
1000 host_notifier.SendLogMessage(message);
1003 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
1004 HostNotifier* host_notifier) {
1005 // Collect hosts so we only send one notification per frontend.
1006 // A host can only be associated with a single cache so no need to worry
1007 // about duplicate hosts being added to the notifier.
1008 if (inprogress_cache_.get()) {
1009 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
1010 host_notifier->AddHosts(inprogress_cache_->associated_hosts());
1013 AppCacheGroup::Caches old_caches = group_->old_caches();
1014 for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
1015 it != old_caches.end(); ++it) {
1016 host_notifier->AddHosts((*it)->associated_hosts());
1019 AppCache* newest_cache = group_->newest_complete_cache();
1020 if (newest_cache)
1021 host_notifier->AddHosts(newest_cache->associated_hosts());
1024 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
1025 // The host is about to be deleted; remove from our collection.
1026 PendingMasters::iterator found =
1027 pending_master_entries_.find(host->pending_master_entry_url());
1028 DCHECK(found != pending_master_entries_.end());
1029 PendingHosts& hosts = found->second;
1030 PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
1031 DCHECK(it != hosts.end());
1032 hosts.erase(it);
1035 void AppCacheUpdateJob::OnServiceReinitialized(
1036 AppCacheStorageReference* old_storage_ref) {
1037 // We continue to use the disabled instance, but arrange for its
1038 // deletion when its no longer needed.
1039 if (old_storage_ref->storage() == storage_)
1040 disabled_storage_reference_ = old_storage_ref;
1043 void AppCacheUpdateJob::CheckIfManifestChanged() {
1044 DCHECK(update_type_ == UPGRADE_ATTEMPT);
1045 AppCacheEntry* entry = NULL;
1046 if (group_->newest_complete_cache())
1047 entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
1048 if (!entry) {
1049 // TODO(michaeln): This is just a bandaid to avoid a crash.
1050 // http://code.google.com/p/chromium/issues/detail?id=95101
1051 if (service_->storage() == storage_) {
1052 // Use a local variable because service_ is reset in HandleCacheFailure.
1053 AppCacheServiceImpl* service = service_;
1054 HandleCacheFailure(
1055 AppCacheErrorDetails("Manifest entry not found in existing cache",
1056 APPCACHE_UNKNOWN_ERROR,
1057 GURL(),
1059 false /*is_cross_origin*/),
1060 DB_ERROR,
1061 GURL());
1062 AppCacheHistograms::AddMissingManifestEntrySample();
1063 service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
1065 return;
1068 // Load manifest data from storage to compare against fetched manifest.
1069 manifest_response_reader_.reset(
1070 storage_->CreateResponseReader(manifest_url_,
1071 group_->group_id(),
1072 entry->response_id()));
1073 read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
1074 manifest_response_reader_->ReadData(
1075 read_manifest_buffer_.get(),
1076 kBufferSize,
1077 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1078 base::Unretained(this))); // async read
1081 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
1082 if (result > 0) {
1083 loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
1084 manifest_response_reader_->ReadData(
1085 read_manifest_buffer_.get(),
1086 kBufferSize,
1087 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
1088 base::Unretained(this))); // read more
1089 } else {
1090 read_manifest_buffer_ = NULL;
1091 manifest_response_reader_.reset();
1092 ContinueHandleManifestFetchCompleted(
1093 result < 0 || manifest_data_ != loaded_manifest_data_);
1097 void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) {
1098 for (base::hash_set<std::string>::const_iterator it =
1099 manifest.explicit_urls.begin();
1100 it != manifest.explicit_urls.end(); ++it) {
1101 AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
1104 const std::vector<AppCacheNamespace>& intercepts =
1105 manifest.intercept_namespaces;
1106 for (std::vector<AppCacheNamespace>::const_iterator it = intercepts.begin();
1107 it != intercepts.end(); ++it) {
1108 int flags = AppCacheEntry::INTERCEPT;
1109 if (it->is_executable)
1110 flags |= AppCacheEntry::EXECUTABLE;
1111 AddUrlToFileList(it->target_url, flags);
1114 const std::vector<AppCacheNamespace>& fallbacks =
1115 manifest.fallback_namespaces;
1116 for (std::vector<AppCacheNamespace>::const_iterator it = fallbacks.begin();
1117 it != fallbacks.end(); ++it) {
1118 AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
1121 // Add all master entries from newest complete cache.
1122 if (update_type_ == UPGRADE_ATTEMPT) {
1123 const AppCache::EntryMap& entries =
1124 group_->newest_complete_cache()->entries();
1125 for (AppCache::EntryMap::const_iterator it = entries.begin();
1126 it != entries.end(); ++it) {
1127 const AppCacheEntry& entry = it->second;
1128 if (entry.IsMaster())
1129 AddUrlToFileList(it->first, AppCacheEntry::MASTER);
1134 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
1135 std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
1136 AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
1138 if (ret.second)
1139 urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
1140 else
1141 ret.first->second.add_types(type); // URL already exists. Merge types.
1144 void AppCacheUpdateJob::FetchUrls() {
1145 DCHECK(internal_state_ == DOWNLOADING);
1147 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
1148 // Fetch up to the concurrent limit. Other fetches will be triggered as each
1149 // each fetch completes.
1150 while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
1151 !urls_to_fetch_.empty()) {
1152 UrlToFetch url_to_fetch = urls_to_fetch_.front();
1153 urls_to_fetch_.pop_front();
1155 AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
1156 DCHECK(it != url_file_list_.end());
1157 AppCacheEntry& entry = it->second;
1158 if (ShouldSkipUrlFetch(entry)) {
1159 NotifyAllProgress(url_to_fetch.url);
1160 ++url_fetches_completed_;
1161 } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
1162 NotifyAllProgress(url_to_fetch.url);
1163 ++url_fetches_completed_; // saved a URL request
1164 } else if (!url_to_fetch.storage_checked &&
1165 MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
1166 // Continues asynchronously after data is loaded from newest cache.
1167 } else {
1168 URLFetcher* fetcher = new URLFetcher(
1169 url_to_fetch.url, URLFetcher::URL_FETCH, this);
1170 if (url_to_fetch.existing_response_info.get()) {
1171 DCHECK(group_->newest_complete_cache());
1172 AppCacheEntry* existing_entry =
1173 group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
1174 DCHECK(existing_entry);
1175 DCHECK(existing_entry->response_id() ==
1176 url_to_fetch.existing_response_info->response_id());
1177 fetcher->set_existing_response_headers(
1178 url_to_fetch.existing_response_info->http_response_info()->headers
1179 .get());
1180 fetcher->set_existing_entry(*existing_entry);
1182 fetcher->Start();
1183 pending_url_fetches_.insert(
1184 PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
1189 void AppCacheUpdateJob::CancelAllUrlFetches() {
1190 // Cancel any pending URL requests.
1191 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1192 it != pending_url_fetches_.end(); ++it) {
1193 delete it->second;
1196 url_fetches_completed_ +=
1197 pending_url_fetches_.size() + urls_to_fetch_.size();
1198 pending_url_fetches_.clear();
1199 urls_to_fetch_.clear();
1202 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
1203 // 6.6.4 Step 17
1204 // If the resource URL being processed was flagged as neither an
1205 // "explicit entry" nor or a "fallback entry", then the user agent
1206 // may skip this URL.
1207 if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
1208 return false;
1210 // TODO(jennb): decide if entry should be skipped to expire it from cache
1211 return false;
1214 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
1215 int entry_type) {
1216 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
1217 AppCacheEntry* existing =
1218 inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
1219 : group_->newest_complete_cache()->GetEntry(url);
1220 if (existing) {
1221 existing->add_types(entry_type);
1222 return true;
1224 return false;
1227 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
1228 const GURL& url,
1229 bool is_new) {
1230 DCHECK(!IsTerminating());
1232 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
1233 AppCache* cache;
1234 if (inprogress_cache_.get()) {
1235 // always associate
1236 host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
1237 cache = inprogress_cache_.get();
1238 } else {
1239 cache = group_->newest_complete_cache();
1242 // Update existing entry if it has already been fetched.
1243 AppCacheEntry* entry = cache->GetEntry(url);
1244 if (entry) {
1245 entry->add_types(AppCacheEntry::MASTER);
1246 if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
1247 // only associate if have entry
1248 host->AssociateCompleteCache(cache);
1250 if (is_new)
1251 ++master_entries_completed_; // pretend fetching completed
1252 return;
1256 // Add to fetch list if not already fetching.
1257 if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
1258 master_entries_to_fetch_.insert(url);
1259 if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
1260 FetchMasterEntries();
1264 void AppCacheUpdateJob::FetchMasterEntries() {
1265 DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
1267 // Fetch each master entry in the list, up to the concurrent limit.
1268 // Additional fetches will be triggered as each fetch completes.
1269 while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
1270 !master_entries_to_fetch_.empty()) {
1271 const GURL& url = *master_entries_to_fetch_.begin();
1273 if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
1274 ++master_entries_completed_; // saved a URL request
1276 // In no update case, associate hosts to newest cache in group
1277 // now that master entry has been "successfully downloaded".
1278 if (internal_state_ == NO_UPDATE) {
1279 // TODO(michaeln): defer until the updated cache has been stored.
1280 DCHECK(!inprogress_cache_.get());
1281 AppCache* cache = group_->newest_complete_cache();
1282 PendingMasters::iterator found = pending_master_entries_.find(url);
1283 DCHECK(found != pending_master_entries_.end());
1284 PendingHosts& hosts = found->second;
1285 for (PendingHosts::iterator host_it = hosts.begin();
1286 host_it != hosts.end(); ++host_it) {
1287 (*host_it)->AssociateCompleteCache(cache);
1290 } else {
1291 URLFetcher* fetcher = new URLFetcher(
1292 url, URLFetcher::MASTER_ENTRY_FETCH, this);
1293 fetcher->Start();
1294 master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
1297 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1301 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1302 const AppCacheErrorDetails& error_details) {
1303 // For now, cancel all in-progress fetches for master entries and pretend
1304 // all master entries fetches have completed.
1305 // TODO(jennb): Delete this when update no longer fetches master entries
1306 // directly.
1308 // Cancel all in-progress fetches.
1309 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1310 it != master_entry_fetches_.end(); ++it) {
1311 delete it->second;
1312 master_entries_to_fetch_.insert(it->first); // back in unfetched list
1314 master_entry_fetches_.clear();
1316 master_entries_completed_ += master_entries_to_fetch_.size();
1318 // Cache failure steps, step 2.
1319 // Pretend all master entries that have not yet been fetched have completed
1320 // downloading. Unassociate hosts from any appcache and send ERROR event.
1321 HostNotifier host_notifier;
1322 while (!master_entries_to_fetch_.empty()) {
1323 const GURL& url = *master_entries_to_fetch_.begin();
1324 PendingMasters::iterator found = pending_master_entries_.find(url);
1325 DCHECK(found != pending_master_entries_.end());
1326 PendingHosts& hosts = found->second;
1327 for (PendingHosts::iterator host_it = hosts.begin();
1328 host_it != hosts.end(); ++host_it) {
1329 AppCacheHost* host = *host_it;
1330 host->AssociateNoCache(GURL());
1331 host_notifier.AddHost(host);
1332 host->RemoveObserver(this);
1334 hosts.clear();
1336 master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
1338 host_notifier.SendErrorNotifications(error_details);
1341 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
1342 AppCacheEntry& entry) {
1343 if (update_type_ != UPGRADE_ATTEMPT)
1344 return false;
1346 AppCache* newest = group_->newest_complete_cache();
1347 AppCacheEntry* copy_me = newest->GetEntry(url);
1348 if (!copy_me || !copy_me->has_response_id())
1349 return false;
1351 // Load HTTP headers for entry from newest cache.
1352 loading_responses_.insert(
1353 LoadingResponses::value_type(copy_me->response_id(), url));
1354 storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
1355 copy_me->response_id(),
1356 this);
1357 // Async: wait for OnResponseInfoLoaded to complete.
1358 return true;
1361 void AppCacheUpdateJob::OnResponseInfoLoaded(
1362 AppCacheResponseInfo* response_info, int64 response_id) {
1363 const net::HttpResponseInfo* http_info = response_info ?
1364 response_info->http_response_info() : NULL;
1366 // Needed response info for a manifest fetch request.
1367 if (internal_state_ == FETCH_MANIFEST) {
1368 if (http_info)
1369 manifest_fetcher_->set_existing_response_headers(
1370 http_info->headers.get());
1371 manifest_fetcher_->Start();
1372 return;
1375 LoadingResponses::iterator found = loading_responses_.find(response_id);
1376 DCHECK(found != loading_responses_.end());
1377 const GURL& url = found->second;
1379 if (!http_info) {
1380 LoadFromNewestCacheFailed(url, NULL); // no response found
1381 } else {
1382 // Check if response can be re-used according to HTTP caching semantics.
1383 // Responses with a "vary" header get treated as expired.
1384 const std::string name = "vary";
1385 std::string value;
1386 void* iter = NULL;
1387 if (!http_info->headers.get() ||
1388 http_info->headers->RequiresValidation(http_info->request_time,
1389 http_info->response_time,
1390 base::Time::Now()) ||
1391 http_info->headers->EnumerateHeader(&iter, name, &value)) {
1392 LoadFromNewestCacheFailed(url, response_info);
1393 } else {
1394 DCHECK(group_->newest_complete_cache());
1395 AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
1396 DCHECK(copy_me);
1397 DCHECK(copy_me->response_id() == response_id);
1399 AppCache::EntryMap::iterator it = url_file_list_.find(url);
1400 DCHECK(it != url_file_list_.end());
1401 AppCacheEntry& entry = it->second;
1402 entry.set_response_id(response_id);
1403 entry.set_response_size(copy_me->response_size());
1404 inprogress_cache_->AddOrModifyEntry(url, entry);
1405 NotifyAllProgress(url);
1406 ++url_fetches_completed_;
1409 loading_responses_.erase(found);
1411 MaybeCompleteUpdate();
1414 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1415 const GURL& url, AppCacheResponseInfo* response_info) {
1416 if (internal_state_ == CACHE_FAILURE)
1417 return;
1419 // Re-insert url at front of fetch list. Indicate storage has been checked.
1420 urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
1421 FetchUrls();
1424 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1425 DCHECK(internal_state_ != CACHE_FAILURE);
1427 // Must wait for any pending master entries or url fetches to complete.
1428 if (master_entries_completed_ != pending_master_entries_.size() ||
1429 url_fetches_completed_ != url_file_list_.size()) {
1430 DCHECK(internal_state_ != COMPLETED);
1431 return;
1434 switch (internal_state_) {
1435 case NO_UPDATE:
1436 if (master_entries_completed_ > 0) {
1437 switch (stored_state_) {
1438 case UNSTORED:
1439 StoreGroupAndCache();
1440 return;
1441 case STORING:
1442 return;
1443 case STORED:
1444 break;
1447 // 6.9.4 steps 7.3-7.7.
1448 NotifyAllAssociatedHosts(APPCACHE_NO_UPDATE_EVENT);
1449 DiscardDuplicateResponses();
1450 internal_state_ = COMPLETED;
1451 break;
1452 case DOWNLOADING:
1453 internal_state_ = REFETCH_MANIFEST;
1454 FetchManifest(false);
1455 break;
1456 case REFETCH_MANIFEST:
1457 DCHECK(stored_state_ == STORED);
1458 NotifyAllFinalProgress();
1459 if (update_type_ == CACHE_ATTEMPT)
1460 NotifyAllAssociatedHosts(APPCACHE_CACHED_EVENT);
1461 else
1462 NotifyAllAssociatedHosts(APPCACHE_UPDATE_READY_EVENT);
1463 DiscardDuplicateResponses();
1464 internal_state_ = COMPLETED;
1465 LogHistogramStats(UPDATE_OK, GURL());
1466 break;
1467 case CACHE_FAILURE:
1468 NOTREACHED(); // See HandleCacheFailure
1469 break;
1470 default:
1471 break;
1474 // Let the stack unwind before deletion to make it less risky as this
1475 // method is called from multiple places in this file.
1476 if (internal_state_ == COMPLETED)
1477 DeleteSoon();
1480 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
1481 // TODO(jennb): post a delayed task with the "same parameters" as this job
1482 // to retry the update at a later time. Need group, URLs of pending master
1483 // entries and their hosts.
1486 void AppCacheUpdateJob::Cancel() {
1487 internal_state_ = CANCELLED;
1489 LogHistogramStats(CANCELLED_ERROR, GURL());
1491 if (manifest_fetcher_) {
1492 delete manifest_fetcher_;
1493 manifest_fetcher_ = NULL;
1496 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
1497 it != pending_url_fetches_.end(); ++it) {
1498 delete it->second;
1500 pending_url_fetches_.clear();
1502 for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
1503 it != master_entry_fetches_.end(); ++it) {
1504 delete it->second;
1506 master_entry_fetches_.clear();
1508 ClearPendingMasterEntries();
1509 DiscardInprogressCache();
1511 // Delete response writer to avoid any callbacks.
1512 if (manifest_response_writer_)
1513 manifest_response_writer_.reset();
1515 storage_->CancelDelegateCallbacks(this);
1518 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1519 for (PendingMasters::iterator it = pending_master_entries_.begin();
1520 it != pending_master_entries_.end(); ++it) {
1521 PendingHosts& hosts = it->second;
1522 for (PendingHosts::iterator host_it = hosts.begin();
1523 host_it != hosts.end(); ++host_it) {
1524 (*host_it)->RemoveObserver(this);
1528 pending_master_entries_.clear();
1531 void AppCacheUpdateJob::DiscardInprogressCache() {
1532 if (stored_state_ == STORING) {
1533 // We can make no assumptions about whether the StoreGroupAndCacheTask
1534 // actually completed or not. This condition should only be reachable
1535 // during shutdown. Free things up and return to do no harm.
1536 inprogress_cache_ = NULL;
1537 added_master_entries_.clear();
1538 return;
1541 storage_->DoomResponses(manifest_url_, stored_response_ids_);
1543 if (!inprogress_cache_.get()) {
1544 // We have to undo the changes we made, if any, to the existing cache.
1545 if (group_ && group_->newest_complete_cache()) {
1546 for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
1547 iter != added_master_entries_.end(); ++iter) {
1548 group_->newest_complete_cache()->RemoveEntry(*iter);
1551 added_master_entries_.clear();
1552 return;
1555 AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
1556 while (!hosts.empty())
1557 (*hosts.begin())->AssociateNoCache(GURL());
1559 inprogress_cache_ = NULL;
1560 added_master_entries_.clear();
1563 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1564 storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
1567 void AppCacheUpdateJob::LogHistogramStats(
1568 ResultType result, const GURL& failed_resource_url) {
1569 AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin());
1570 if (result == UPDATE_OK)
1571 return;
1573 int percent_complete = 0;
1574 if (url_file_list_.size() > 0) {
1575 size_t actual_fetches_completed = url_fetches_completed_;
1576 if (!failed_resource_url.is_empty() && actual_fetches_completed)
1577 --actual_fetches_completed;
1578 percent_complete = (static_cast<double>(actual_fetches_completed) /
1579 static_cast<double>(url_file_list_.size())) * 100.0;
1580 percent_complete = std::min(percent_complete, 99);
1583 bool was_making_progress =
1584 base::Time::Now() - last_progress_time_ <
1585 base::TimeDelta::FromMinutes(5);
1587 bool off_origin_resource_failure =
1588 !failed_resource_url.is_empty() &&
1589 (failed_resource_url.GetOrigin() != manifest_url_.GetOrigin());
1591 AppCacheHistograms::LogUpdateFailureStats(
1592 manifest_url_.GetOrigin(),
1593 percent_complete,
1594 was_making_progress,
1595 off_origin_resource_failure);
1598 void AppCacheUpdateJob::DeleteSoon() {
1599 ClearPendingMasterEntries();
1600 manifest_response_writer_.reset();
1601 storage_->CancelDelegateCallbacks(this);
1602 service_->RemoveObserver(this);
1603 service_ = NULL;
1605 // Break the connection with the group so the group cannot call delete
1606 // on this object after we've posted a task to delete ourselves.
1607 group_->SetUpdateAppCacheStatus(AppCacheGroup::IDLE);
1608 group_ = NULL;
1610 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
1613 } // namespace content