1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webkit/browser/appcache/appcache_update_job.h"
8 #include "base/bind_helpers.h"
9 #include "base/compiler_specific.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "net/base/io_buffer.h"
14 #include "net/base/load_flags.h"
15 #include "net/base/net_errors.h"
16 #include "net/base/request_priority.h"
17 #include "net/http/http_request_headers.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/url_request/url_request_context.h"
20 #include "webkit/browser/appcache/appcache_group.h"
21 #include "webkit/browser/appcache/appcache_histograms.h"
25 static const int kBufferSize
= 32768;
26 static const size_t kMaxConcurrentUrlFetches
= 2;
27 static const int kMax503Retries
= 3;
29 static std::string
FormatUrlErrorMessage(
30 const char* format
, const GURL
& url
,
31 AppCacheUpdateJob::ResultType error
,
33 // Show the net response code if we have one.
34 int code
= response_code
;
35 if (error
!= AppCacheUpdateJob::SERVER_ERROR
)
36 code
= static_cast<int>(error
);
37 return base::StringPrintf(format
, code
, url
.spec().c_str());
40 // Helper class for collecting hosts per frontend when sending notifications
41 // so that only one notification is sent for all hosts using the same frontend.
44 typedef std::vector
<int> HostIds
;
45 typedef std::map
<AppCacheFrontend
*, HostIds
> NotifyHostMap
;
47 // Caller is responsible for ensuring there will be no duplicate hosts.
48 void AddHost(AppCacheHost
* host
) {
49 std::pair
<NotifyHostMap::iterator
, bool> ret
= hosts_to_notify
.insert(
50 NotifyHostMap::value_type(host
->frontend(), HostIds()));
51 ret
.first
->second
.push_back(host
->host_id());
54 void AddHosts(const std::set
<AppCacheHost
*>& hosts
) {
55 for (std::set
<AppCacheHost
*>::const_iterator it
= hosts
.begin();
56 it
!= hosts
.end(); ++it
) {
61 void SendNotifications(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
);
100 NotifyHostMap hosts_to_notify
;
103 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL
& url
,
105 AppCacheResponseInfo
* info
)
107 storage_checked(checked
),
108 existing_response_info(info
) {
111 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
114 // Helper class to fetch resources. Depending on the fetch type,
115 // can either fetch to an in-memory string or write the response
116 // data out to the disk cache.
117 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL
& url
,
118 FetchType fetch_type
,
119 AppCacheUpdateJob
* job
)
122 fetch_type_(fetch_type
),
123 retry_503_attempts_(0),
124 buffer_(new net::IOBuffer(kBufferSize
)),
125 request_(job
->service_
->request_context()
126 ->CreateRequest(url
, net::DEFAULT_PRIORITY
, this, NULL
)),
128 redirect_response_code_(-1) {}
130 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
133 void AppCacheUpdateJob::URLFetcher::Start() {
134 request_
->set_first_party_for_cookies(job_
->manifest_url_
);
135 request_
->SetLoadFlags(request_
->load_flags() |
136 net::LOAD_DISABLE_INTERCEPT
);
137 if (existing_response_headers_
.get())
138 AddConditionalHeaders(existing_response_headers_
.get());
142 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
143 net::URLRequest
* request
, const GURL
& new_url
, bool* defer_redirect
) {
144 DCHECK(request_
== request
);
145 // Redirect is not allowed by the update process.
146 job_
->MadeProgress();
147 redirect_response_code_
= request
->GetResponseCode();
149 result_
= REDIRECT_ERROR
;
150 OnResponseCompleted();
153 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
154 net::URLRequest
*request
) {
155 DCHECK(request
== request_
);
156 int response_code
= -1;
157 if (request
->status().is_success()) {
158 response_code
= request
->GetResponseCode();
159 job_
->MadeProgress();
161 if ((response_code
/ 100) == 2) {
163 // See http://code.google.com/p/chromium/issues/detail?id=69594
164 // We willfully violate the HTML5 spec at this point in order
165 // to support the appcaching of cross-origin HTTPS resources.
166 // We've opted for a milder constraint and allow caching unless
167 // the resource has a "no-store" header. A spec change has been
168 // requested on the whatwg list.
169 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
170 if (url_
.SchemeIsSecure() &&
171 url_
.GetOrigin() != job_
->manifest_url_
.GetOrigin()) {
172 if (request
->response_headers()->
173 HasHeaderValue("cache-control", "no-store")) {
174 DCHECK_EQ(-1, redirect_response_code_
);
176 result_
= SERVER_ERROR
; // Not the best match?
177 OnResponseCompleted();
182 // Write response info to storage for URL fetches. Wait for async write
183 // completion before reading any response data.
184 if (fetch_type_
== URL_FETCH
|| fetch_type_
== MASTER_ENTRY_FETCH
) {
185 response_writer_
.reset(job_
->CreateResponseWriter());
186 scoped_refptr
<HttpResponseInfoIOBuffer
> io_buffer(
187 new HttpResponseInfoIOBuffer(
188 new net::HttpResponseInfo(request
->response_info())));
189 response_writer_
->WriteInfo(
191 base::Bind(&URLFetcher::OnWriteComplete
, base::Unretained(this)));
196 if (response_code
> 0)
197 result_
= SERVER_ERROR
;
199 result_
= NETWORK_ERROR
;
200 OnResponseCompleted();
204 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
205 net::URLRequest
* request
, int bytes_read
) {
206 DCHECK(request_
== request
);
207 bool data_consumed
= true;
208 if (request
->status().is_success() && bytes_read
> 0) {
209 job_
->MadeProgress();
210 data_consumed
= ConsumeResponseData(bytes_read
);
213 while (request
->Read(buffer_
.get(), kBufferSize
, &bytes_read
)) {
214 if (bytes_read
> 0) {
215 data_consumed
= ConsumeResponseData(bytes_read
);
217 break; // wait for async data processing, then read more
224 if (data_consumed
&& !request
->status().is_io_pending()) {
225 DCHECK_EQ(UPDATE_OK
, result_
);
226 OnResponseCompleted();
230 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
231 const net::HttpResponseHeaders
* headers
) {
232 DCHECK(request_
.get() && headers
);
233 net::HttpRequestHeaders extra_headers
;
235 // Add If-Modified-Since header if response info has Last-Modified header.
236 const std::string last_modified
= "Last-Modified";
237 std::string last_modified_value
;
238 headers
->EnumerateHeader(NULL
, last_modified
, &last_modified_value
);
239 if (!last_modified_value
.empty()) {
240 extra_headers
.SetHeader(net::HttpRequestHeaders::kIfModifiedSince
,
241 last_modified_value
);
244 // Add If-None-Match header if response info has ETag header.
245 const std::string etag
= "ETag";
246 std::string etag_value
;
247 headers
->EnumerateHeader(NULL
, etag
, &etag_value
);
248 if (!etag_value
.empty()) {
249 extra_headers
.SetHeader(net::HttpRequestHeaders::kIfNoneMatch
,
252 if (!extra_headers
.IsEmpty())
253 request_
->SetExtraRequestHeaders(extra_headers
);
256 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result
) {
259 result_
= DISKCACHE_ERROR
;
260 OnResponseCompleted();
266 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
267 InternalUpdateState state
= job_
->internal_state_
;
268 if (state
== CACHE_FAILURE
|| state
== CANCELLED
|| state
== COMPLETED
)
271 request_
->Read(buffer_
.get(), kBufferSize
, &bytes_read
);
272 OnReadCompleted(request_
.get(), bytes_read
);
275 // Returns false if response data is processed asynchronously, in which
276 // case ReadResponseData will be invoked when it is safe to continue
277 // reading more response data from the request.
278 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read
) {
279 DCHECK_GT(bytes_read
, 0);
280 switch (fetch_type_
) {
282 case MANIFEST_REFETCH
:
283 manifest_data_
.append(buffer_
->data(), bytes_read
);
286 case MASTER_ENTRY_FETCH
:
287 DCHECK(response_writer_
.get());
288 response_writer_
->WriteData(
291 base::Bind(&URLFetcher::OnWriteComplete
, base::Unretained(this)));
292 return false; // wait for async write completion to continue reading
299 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
300 if (request_
->status().is_success())
301 job_
->MadeProgress();
303 // Retry for 503s where retry-after is 0.
304 if (request_
->status().is_success() &&
305 request_
->GetResponseCode() == 503 &&
306 MaybeRetryRequest()) {
310 switch (fetch_type_
) {
312 job_
->HandleManifestFetchCompleted(this);
315 job_
->HandleUrlFetchCompleted(this);
317 case MASTER_ENTRY_FETCH
:
318 job_
->HandleMasterEntryFetchCompleted(this);
320 case MANIFEST_REFETCH
:
321 job_
->HandleManifestRefetchCompleted(this);
330 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
331 if (retry_503_attempts_
>= kMax503Retries
||
332 !request_
->response_headers()->HasHeaderValue("retry-after", "0")) {
335 ++retry_503_attempts_
;
337 request_
= job_
->service_
->request_context()->CreateRequest(
338 url_
, net::DEFAULT_PRIORITY
, this, NULL
);
343 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheServiceImpl
* service
,
344 AppCacheGroup
* group
)
346 manifest_url_(group
->manifest_url()),
348 update_type_(UNKNOWN_TYPE
),
349 internal_state_(FETCH_MANIFEST
),
350 master_entries_completed_(0),
351 url_fetches_completed_(0),
352 manifest_fetcher_(NULL
),
353 manifest_has_valid_mime_type_(false),
354 stored_state_(UNSTORED
),
355 storage_(service
->storage()) {
356 service_
->AddObserver(this);
359 AppCacheUpdateJob::~AppCacheUpdateJob() {
361 service_
->RemoveObserver(this);
362 if (internal_state_
!= COMPLETED
)
365 DCHECK(!manifest_fetcher_
);
366 DCHECK(pending_url_fetches_
.empty());
367 DCHECK(!inprogress_cache_
.get());
368 DCHECK(pending_master_entries_
.empty());
369 DCHECK(master_entry_fetches_
.empty());
372 group_
->SetUpdateAppCacheStatus(AppCacheGroup::IDLE
);
375 void AppCacheUpdateJob::StartUpdate(AppCacheHost
* host
,
376 const GURL
& new_master_resource
) {
377 DCHECK(group_
->update_job() == this);
378 DCHECK(!group_
->is_obsolete());
380 bool is_new_pending_master_entry
= false;
381 if (!new_master_resource
.is_empty()) {
382 DCHECK(new_master_resource
== host
->pending_master_entry_url());
383 DCHECK(!new_master_resource
.has_ref());
384 DCHECK(new_master_resource
.GetOrigin() == manifest_url_
.GetOrigin());
386 // Cannot add more to this update if already terminating.
387 if (IsTerminating()) {
388 group_
->QueueUpdate(host
, new_master_resource
);
392 std::pair
<PendingMasters::iterator
, bool> ret
=
393 pending_master_entries_
.insert(
394 PendingMasters::value_type(new_master_resource
, PendingHosts()));
395 is_new_pending_master_entry
= ret
.second
;
396 ret
.first
->second
.push_back(host
);
397 host
->AddObserver(this);
400 // Notify host (if any) if already checking or downloading.
401 AppCacheGroup::UpdateAppCacheStatus update_status
= group_
->update_status();
402 if (update_status
== AppCacheGroup::CHECKING
||
403 update_status
== AppCacheGroup::DOWNLOADING
) {
405 NotifySingleHost(host
, APPCACHE_CHECKING_EVENT
);
406 if (update_status
== AppCacheGroup::DOWNLOADING
)
407 NotifySingleHost(host
, APPCACHE_DOWNLOADING_EVENT
);
409 // Add to fetch list or an existing entry if already fetched.
410 if (!new_master_resource
.is_empty()) {
411 AddMasterEntryToFetchList(host
, new_master_resource
,
412 is_new_pending_master_entry
);
418 // Begin update process for the group.
420 group_
->SetUpdateAppCacheStatus(AppCacheGroup::CHECKING
);
421 if (group_
->HasCache()) {
422 update_type_
= UPGRADE_ATTEMPT
;
423 NotifyAllAssociatedHosts(APPCACHE_CHECKING_EVENT
);
425 update_type_
= CACHE_ATTEMPT
;
427 NotifySingleHost(host
, APPCACHE_CHECKING_EVENT
);
430 if (!new_master_resource
.is_empty()) {
431 AddMasterEntryToFetchList(host
, new_master_resource
,
432 is_new_pending_master_entry
);
438 AppCacheResponseWriter
* AppCacheUpdateJob::CreateResponseWriter() {
439 AppCacheResponseWriter
* writer
=
440 storage_
->CreateResponseWriter(manifest_url_
,
442 stored_response_ids_
.push_back(writer
->response_id());
446 void AppCacheUpdateJob::HandleCacheFailure(
447 const AppCacheErrorDetails
& error_details
,
449 const GURL
& failed_resource_url
) {
450 // 6.9.4 cache failure steps 2-8.
451 DCHECK(internal_state_
!= CACHE_FAILURE
);
452 DCHECK(!error_details
.message
.empty());
453 DCHECK(result
!= UPDATE_OK
);
454 internal_state_
= CACHE_FAILURE
;
455 LogHistogramStats(result
, failed_resource_url
);
456 CancelAllUrlFetches();
457 CancelAllMasterEntryFetches(error_details
);
458 NotifyAllError(error_details
);
459 DiscardInprogressCache();
460 internal_state_
= COMPLETED
;
461 DeleteSoon(); // To unwind the stack prior to deletion.
464 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch
) {
465 DCHECK(!manifest_fetcher_
);
466 manifest_fetcher_
= new URLFetcher(
468 is_first_fetch
? URLFetcher::MANIFEST_FETCH
:
469 URLFetcher::MANIFEST_REFETCH
,
472 // Add any necessary Http headers before sending fetch request.
473 if (is_first_fetch
) {
474 AppCacheEntry
* entry
= (update_type_
== UPGRADE_ATTEMPT
) ?
475 group_
->newest_complete_cache()->GetEntry(manifest_url_
) : NULL
;
477 // Asynchronously load response info for manifest from newest cache.
478 storage_
->LoadResponseInfo(manifest_url_
, group_
->group_id(),
479 entry
->response_id(), this);
481 manifest_fetcher_
->Start();
484 DCHECK(internal_state_
== REFETCH_MANIFEST
);
485 DCHECK(manifest_response_info_
.get());
486 manifest_fetcher_
->set_existing_response_headers(
487 manifest_response_info_
->headers
.get());
488 manifest_fetcher_
->Start();
493 void AppCacheUpdateJob::HandleManifestFetchCompleted(
494 URLFetcher
* fetcher
) {
495 DCHECK_EQ(internal_state_
, FETCH_MANIFEST
);
496 DCHECK_EQ(manifest_fetcher_
, fetcher
);
497 manifest_fetcher_
= NULL
;
499 net::URLRequest
* request
= fetcher
->request();
500 int response_code
= -1;
501 bool is_valid_response_code
= false;
502 if (request
->status().is_success()) {
503 response_code
= request
->GetResponseCode();
504 is_valid_response_code
= (response_code
/ 100 == 2);
506 std::string mime_type
;
507 request
->GetMimeType(&mime_type
);
508 manifest_has_valid_mime_type_
= (mime_type
== "text/cache-manifest");
511 if (is_valid_response_code
) {
512 manifest_data_
= fetcher
->manifest_data();
513 manifest_response_info_
.reset(
514 new net::HttpResponseInfo(request
->response_info()));
515 if (update_type_
== UPGRADE_ATTEMPT
)
516 CheckIfManifestChanged(); // continues asynchronously
518 ContinueHandleManifestFetchCompleted(true);
519 } else if (response_code
== 304 && update_type_
== UPGRADE_ATTEMPT
) {
520 ContinueHandleManifestFetchCompleted(false);
521 } else if ((response_code
== 404 || response_code
== 410) &&
522 update_type_
== UPGRADE_ATTEMPT
) {
523 storage_
->MakeGroupObsolete(group_
, this, response_code
); // async
525 const char* kFormatString
= "Manifest fetch failed (%d) %s";
526 std::string message
= FormatUrlErrorMessage(
527 kFormatString
, manifest_url_
, fetcher
->result(), response_code
);
528 HandleCacheFailure(AppCacheErrorDetails(message
,
529 appcache::APPCACHE_MANIFEST_ERROR
,
532 false /*is_cross_origin*/),
538 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup
* group
,
541 DCHECK(master_entry_fetches_
.empty());
542 CancelAllMasterEntryFetches(AppCacheErrorDetails(
543 "The cache has been made obsolete, "
544 "the manifest file returned 404 or 410",
545 appcache::APPCACHE_MANIFEST_ERROR
,
548 false /*is_cross_origin*/));
550 DCHECK(group
->is_obsolete());
551 NotifyAllAssociatedHosts(APPCACHE_OBSOLETE_EVENT
);
552 internal_state_
= COMPLETED
;
553 MaybeCompleteUpdate();
555 // Treat failure to mark group obsolete as a cache failure.
556 HandleCacheFailure(AppCacheErrorDetails(
557 "Failed to mark the cache as obsolete",
558 APPCACHE_UNKNOWN_ERROR
,
561 false /*is_cross_origin*/),
567 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed
) {
568 DCHECK(internal_state_
== FETCH_MANIFEST
);
571 DCHECK(update_type_
== UPGRADE_ATTEMPT
);
572 internal_state_
= NO_UPDATE
;
574 // Wait for pending master entries to download.
575 FetchMasterEntries();
576 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
581 if (!ParseManifest(manifest_url_
, manifest_data_
.data(),
582 manifest_data_
.length(),
583 manifest_has_valid_mime_type_
?
584 PARSE_MANIFEST_ALLOWING_INTERCEPTS
:
585 PARSE_MANIFEST_PER_STANDARD
,
587 const char* kFormatString
= "Failed to parse manifest %s";
588 const std::string message
= base::StringPrintf(kFormatString
,
589 manifest_url_
.spec().c_str());
591 AppCacheErrorDetails(
592 message
, APPCACHE_SIGNATURE_ERROR
, GURL(), 0,
593 false /*is_cross_origin*/),
594 APPCACHE_MANIFEST_ERROR
,
600 // Proceed with update process. Section 6.9.4 steps 8-20.
601 internal_state_
= DOWNLOADING
;
602 inprogress_cache_
= new AppCache(storage_
, storage_
->NewCacheId());
603 BuildUrlFileList(manifest
);
604 inprogress_cache_
->InitializeWithManifest(&manifest
);
606 // Associate all pending master hosts with the newly created cache.
607 for (PendingMasters::iterator it
= pending_master_entries_
.begin();
608 it
!= pending_master_entries_
.end(); ++it
) {
609 PendingHosts
& hosts
= it
->second
;
610 for (PendingHosts::iterator host_it
= hosts
.begin();
611 host_it
!= hosts
.end(); ++host_it
) {
613 ->AssociateIncompleteCache(inprogress_cache_
.get(), manifest_url_
);
617 if (manifest
.did_ignore_intercept_namespaces
) {
618 // Must be done after associating all pending master hosts.
620 "Ignoring the INTERCEPT section of the application cache manifest "
621 "because the content type is not text/cache-manifest");
622 LogConsoleMessageToAll(message
);
625 group_
->SetUpdateAppCacheStatus(AppCacheGroup::DOWNLOADING
);
626 NotifyAllAssociatedHosts(APPCACHE_DOWNLOADING_EVENT
);
628 FetchMasterEntries();
629 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
632 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher
* fetcher
) {
633 DCHECK(internal_state_
== DOWNLOADING
);
635 net::URLRequest
* request
= fetcher
->request();
636 const GURL
& url
= request
->original_url();
637 pending_url_fetches_
.erase(url
);
638 NotifyAllProgress(url
);
639 ++url_fetches_completed_
;
641 int response_code
= request
->status().is_success()
642 ? request
->GetResponseCode()
643 : fetcher
->redirect_response_code();
645 AppCacheEntry
& entry
= url_file_list_
.find(url
)->second
;
647 if (response_code
/ 100 == 2) {
648 // Associate storage with the new entry.
649 DCHECK(fetcher
->response_writer());
650 entry
.set_response_id(fetcher
->response_writer()->response_id());
651 entry
.set_response_size(fetcher
->response_writer()->amount_written());
652 if (!inprogress_cache_
->AddOrModifyEntry(url
, entry
))
653 duplicate_response_ids_
.push_back(entry
.response_id());
655 // TODO(michaeln): Check for <html manifest=xxx>
656 // See http://code.google.com/p/chromium/issues/detail?id=97930
657 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
658 // if (!manifestAttribute) skip it
660 // Foreign entries will be detected during cache selection.
661 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
662 // file whose root element is an html element with a manifest attribute
663 // whose value doesn't match the manifest url of the application cache
664 // being processed, mark the entry as being foreign.
666 VLOG(1) << "Request status: " << request
->status().status()
667 << " error: " << request
->status().error()
668 << " response code: " << response_code
;
669 if (entry
.IsExplicit() || entry
.IsFallback() || entry
.IsIntercept()) {
670 if (response_code
== 304 && fetcher
->existing_entry().has_response_id()) {
671 // Keep the existing response.
672 entry
.set_response_id(fetcher
->existing_entry().response_id());
673 entry
.set_response_size(fetcher
->existing_entry().response_size());
674 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
676 const char* kFormatString
= "Resource fetch failed (%d) %s";
677 std::string message
= FormatUrlErrorMessage(
678 kFormatString
, url
, fetcher
->result(), response_code
);
679 ResultType result
= fetcher
->result();
680 bool is_cross_origin
= url
.GetOrigin() != manifest_url_
.GetOrigin();
682 case DISKCACHE_ERROR
:
684 AppCacheErrorDetails(
685 message
, APPCACHE_UNKNOWN_ERROR
, GURL(), 0,
692 AppCacheErrorDetails(message
, APPCACHE_RESOURCE_ERROR
, url
, 0,
698 HandleCacheFailure(AppCacheErrorDetails(message
,
699 APPCACHE_RESOURCE_ERROR
,
709 } else if (response_code
== 404 || response_code
== 410) {
710 // Entry is skipped. They are dropped from the cache.
711 } else if (update_type_
== UPGRADE_ATTEMPT
&&
712 fetcher
->existing_entry().has_response_id()) {
713 // Keep the existing response.
714 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
715 // but the old resource may or may not be compatible with the new contents
716 // of the cache. Impossible to know one way or the other.
717 entry
.set_response_id(fetcher
->existing_entry().response_id());
718 entry
.set_response_size(fetcher
->existing_entry().response_size());
719 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
723 // Fetch another URL now that one request has completed.
724 DCHECK(internal_state_
!= CACHE_FAILURE
);
726 MaybeCompleteUpdate();
729 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
730 URLFetcher
* fetcher
) {
731 DCHECK(internal_state_
== NO_UPDATE
|| internal_state_
== DOWNLOADING
);
733 // TODO(jennb): Handle downloads completing during cache failure when update
734 // no longer fetches master entries directly. For now, we cancel all pending
735 // master entry fetches when entering cache failure state so this will never
736 // be called in CACHE_FAILURE state.
738 net::URLRequest
* request
= fetcher
->request();
739 const GURL
& url
= request
->original_url();
740 master_entry_fetches_
.erase(url
);
741 ++master_entries_completed_
;
743 int response_code
= request
->status().is_success()
744 ? request
->GetResponseCode() : -1;
746 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
747 DCHECK(found
!= pending_master_entries_
.end());
748 PendingHosts
& hosts
= found
->second
;
750 // Section 6.9.4. No update case: step 7.3, else step 22.
751 if (response_code
/ 100 == 2) {
752 // Add fetched master entry to the appropriate cache.
753 AppCache
* cache
= inprogress_cache_
.get() ? inprogress_cache_
.get()
754 : group_
->newest_complete_cache();
755 DCHECK(fetcher
->response_writer());
756 AppCacheEntry
master_entry(AppCacheEntry::MASTER
,
757 fetcher
->response_writer()->response_id(),
758 fetcher
->response_writer()->amount_written());
759 if (cache
->AddOrModifyEntry(url
, master_entry
))
760 added_master_entries_
.push_back(url
);
762 duplicate_response_ids_
.push_back(master_entry
.response_id());
764 // In no-update case, associate host with the newest cache.
765 if (!inprogress_cache_
.get()) {
766 // TODO(michaeln): defer until the updated cache has been stored
767 DCHECK(cache
== group_
->newest_complete_cache());
768 for (PendingHosts::iterator host_it
= hosts
.begin();
769 host_it
!= hosts
.end(); ++host_it
) {
770 (*host_it
)->AssociateCompleteCache(cache
);
774 HostNotifier host_notifier
;
775 for (PendingHosts::iterator host_it
= hosts
.begin();
776 host_it
!= hosts
.end(); ++host_it
) {
777 AppCacheHost
* host
= *host_it
;
778 host_notifier
.AddHost(host
);
780 // In downloading case, disassociate host from inprogress cache.
781 if (inprogress_cache_
.get())
782 host
->AssociateNoCache(GURL());
784 host
->RemoveObserver(this);
788 const char* kFormatString
= "Manifest fetch failed (%d) %s";
789 std::string message
= FormatUrlErrorMessage(
790 kFormatString
, request
->url(), fetcher
->result(), response_code
);
791 host_notifier
.SendErrorNotifications(
792 AppCacheErrorDetails(message
,
793 appcache::APPCACHE_MANIFEST_ERROR
,
796 false /*is_cross_origin*/));
798 // In downloading case, update result is different if all master entries
799 // failed vs. only some failing.
800 if (inprogress_cache_
.get()) {
801 // Only count successful downloads to know if all master entries failed.
802 pending_master_entries_
.erase(found
);
803 --master_entries_completed_
;
805 // Section 6.9.4, step 22.3.
806 if (update_type_
== CACHE_ATTEMPT
&& pending_master_entries_
.empty()) {
807 HandleCacheFailure(AppCacheErrorDetails(message
,
808 appcache::APPCACHE_MANIFEST_ERROR
,
811 false /*is_cross_origin*/),
819 DCHECK(internal_state_
!= CACHE_FAILURE
);
820 FetchMasterEntries();
821 MaybeCompleteUpdate();
824 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
825 URLFetcher
* fetcher
) {
826 DCHECK(internal_state_
== REFETCH_MANIFEST
);
827 DCHECK(manifest_fetcher_
== fetcher
);
828 manifest_fetcher_
= NULL
;
830 net::URLRequest
* request
= fetcher
->request();
831 int response_code
= request
->status().is_success()
832 ? request
->GetResponseCode() : -1;
833 if (response_code
== 304 || manifest_data_
== fetcher
->manifest_data()) {
834 // Only need to store response in storage if manifest is not already
835 // an entry in the cache.
836 AppCacheEntry
* entry
= inprogress_cache_
->GetEntry(manifest_url_
);
838 entry
->add_types(AppCacheEntry::MANIFEST
);
839 StoreGroupAndCache();
841 manifest_response_writer_
.reset(CreateResponseWriter());
842 scoped_refptr
<HttpResponseInfoIOBuffer
> io_buffer(
843 new HttpResponseInfoIOBuffer(manifest_response_info_
.release()));
844 manifest_response_writer_
->WriteInfo(
846 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete
,
847 base::Unretained(this)));
850 VLOG(1) << "Request status: " << request
->status().status()
851 << " error: " << request
->status().error()
852 << " response code: " << response_code
;
853 ScheduleUpdateRetry(kRerunDelayMs
);
854 if (response_code
== 200) {
855 HandleCacheFailure(AppCacheErrorDetails("Manifest changed during update",
856 APPCACHE_CHANGED_ERROR
,
859 false /*is_cross_origin*/),
860 APPCACHE_MANIFEST_ERROR
,
863 const char* kFormatString
= "Manifest re-fetch failed (%d) %s";
864 std::string message
= FormatUrlErrorMessage(
865 kFormatString
, manifest_url_
, fetcher
->result(), response_code
);
866 HandleCacheFailure(AppCacheErrorDetails(message
,
867 appcache::APPCACHE_MANIFEST_ERROR
,
870 false /*is_cross_origin*/),
877 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result
) {
879 scoped_refptr
<net::StringIOBuffer
> io_buffer(
880 new net::StringIOBuffer(manifest_data_
));
881 manifest_response_writer_
->WriteData(
883 manifest_data_
.length(),
884 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete
,
885 base::Unretained(this)));
888 AppCacheErrorDetails("Failed to write the manifest headers to storage",
889 APPCACHE_UNKNOWN_ERROR
,
892 false /*is_cross_origin*/),
898 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result
) {
900 AppCacheEntry
entry(AppCacheEntry::MANIFEST
,
901 manifest_response_writer_
->response_id(),
902 manifest_response_writer_
->amount_written());
903 if (!inprogress_cache_
->AddOrModifyEntry(manifest_url_
, entry
))
904 duplicate_response_ids_
.push_back(entry
.response_id());
905 StoreGroupAndCache();
908 AppCacheErrorDetails("Failed to write the manifest data to storage",
909 APPCACHE_UNKNOWN_ERROR
,
912 false /*is_cross_origin*/),
918 void AppCacheUpdateJob::StoreGroupAndCache() {
919 DCHECK(stored_state_
== UNSTORED
);
920 stored_state_
= STORING
;
921 scoped_refptr
<AppCache
> newest_cache
;
922 if (inprogress_cache_
.get())
923 newest_cache
.swap(inprogress_cache_
);
925 newest_cache
= group_
->newest_complete_cache();
926 newest_cache
->set_update_time(base::Time::Now());
928 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
929 DCHECK_EQ(manifest_url_
, group_
->manifest_url());
930 storage_
->StoreGroupAndNewestCache(group_
, newest_cache
.get(), this);
933 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup
* group
,
934 AppCache
* newest_cache
,
936 bool would_exceed_quota
) {
937 DCHECK(stored_state_
== STORING
);
939 stored_state_
= STORED
;
940 MaybeCompleteUpdate(); // will definitely complete
942 stored_state_
= UNSTORED
;
944 // Restore inprogress_cache_ to get the proper events delivered
945 // and the proper cleanup to occur.
946 if (newest_cache
!= group
->newest_complete_cache())
947 inprogress_cache_
= newest_cache
;
949 ResultType result
= DB_ERROR
;
950 AppCacheErrorReason reason
= APPCACHE_UNKNOWN_ERROR
;
951 std::string
message("Failed to commit new cache to storage");
952 if (would_exceed_quota
) {
953 message
.append(", would exceed quota");
954 result
= APPCACHE_QUOTA_ERROR
;
955 reason
= appcache::APPCACHE_QUOTA_ERROR
;
958 AppCacheErrorDetails(message
, reason
, GURL(), 0,
959 false /*is_cross_origin*/),
965 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost
* host
,
966 AppCacheEventID event_id
) {
967 std::vector
<int> ids(1, host
->host_id());
968 host
->frontend()->OnEventRaised(ids
, event_id
);
971 void AppCacheUpdateJob::NotifyAllAssociatedHosts(AppCacheEventID event_id
) {
972 HostNotifier host_notifier
;
973 AddAllAssociatedHostsToNotifier(&host_notifier
);
974 host_notifier
.SendNotifications(event_id
);
977 void AppCacheUpdateJob::NotifyAllProgress(const GURL
& url
) {
978 HostNotifier host_notifier
;
979 AddAllAssociatedHostsToNotifier(&host_notifier
);
980 host_notifier
.SendProgressNotifications(
981 url
, url_file_list_
.size(), url_fetches_completed_
);
984 void AppCacheUpdateJob::NotifyAllFinalProgress() {
985 DCHECK(url_file_list_
.size() == url_fetches_completed_
);
986 NotifyAllProgress(GURL());
989 void AppCacheUpdateJob::NotifyAllError(const AppCacheErrorDetails
& details
) {
990 HostNotifier host_notifier
;
991 AddAllAssociatedHostsToNotifier(&host_notifier
);
992 host_notifier
.SendErrorNotifications(details
);
995 void AppCacheUpdateJob::LogConsoleMessageToAll(const std::string
& message
) {
996 HostNotifier host_notifier
;
997 AddAllAssociatedHostsToNotifier(&host_notifier
);
998 host_notifier
.SendLogMessage(message
);
1001 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
1002 HostNotifier
* host_notifier
) {
1003 // Collect hosts so we only send one notification per frontend.
1004 // A host can only be associated with a single cache so no need to worry
1005 // about duplicate hosts being added to the notifier.
1006 if (inprogress_cache_
.get()) {
1007 DCHECK(internal_state_
== DOWNLOADING
|| internal_state_
== CACHE_FAILURE
);
1008 host_notifier
->AddHosts(inprogress_cache_
->associated_hosts());
1011 AppCacheGroup::Caches old_caches
= group_
->old_caches();
1012 for (AppCacheGroup::Caches::const_iterator it
= old_caches
.begin();
1013 it
!= old_caches
.end(); ++it
) {
1014 host_notifier
->AddHosts((*it
)->associated_hosts());
1017 AppCache
* newest_cache
= group_
->newest_complete_cache();
1019 host_notifier
->AddHosts(newest_cache
->associated_hosts());
1022 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost
* host
) {
1023 // The host is about to be deleted; remove from our collection.
1024 PendingMasters::iterator found
=
1025 pending_master_entries_
.find(host
->pending_master_entry_url());
1026 DCHECK(found
!= pending_master_entries_
.end());
1027 PendingHosts
& hosts
= found
->second
;
1028 PendingHosts::iterator it
= std::find(hosts
.begin(), hosts
.end(), host
);
1029 DCHECK(it
!= hosts
.end());
1033 void AppCacheUpdateJob::OnServiceReinitialized(
1034 AppCacheStorageReference
* old_storage_ref
) {
1035 // We continue to use the disabled instance, but arrange for its
1036 // deletion when its no longer needed.
1037 if (old_storage_ref
->storage() == storage_
)
1038 disabled_storage_reference_
= old_storage_ref
;
1041 void AppCacheUpdateJob::CheckIfManifestChanged() {
1042 DCHECK(update_type_
== UPGRADE_ATTEMPT
);
1043 AppCacheEntry
* entry
= NULL
;
1044 if (group_
->newest_complete_cache())
1045 entry
= group_
->newest_complete_cache()->GetEntry(manifest_url_
);
1047 // TODO(michaeln): This is just a bandaid to avoid a crash.
1048 // http://code.google.com/p/chromium/issues/detail?id=95101
1049 if (service_
->storage() == storage_
) {
1050 // Use a local variable because service_ is reset in HandleCacheFailure.
1051 AppCacheServiceImpl
* service
= service_
;
1053 AppCacheErrorDetails("Manifest entry not found in existing cache",
1054 APPCACHE_UNKNOWN_ERROR
,
1057 false /*is_cross_origin*/),
1060 AppCacheHistograms::AddMissingManifestEntrySample();
1061 service
->DeleteAppCacheGroup(manifest_url_
, net::CompletionCallback());
1066 // Load manifest data from storage to compare against fetched manifest.
1067 manifest_response_reader_
.reset(
1068 storage_
->CreateResponseReader(manifest_url_
,
1070 entry
->response_id()));
1071 read_manifest_buffer_
= new net::IOBuffer(kBufferSize
);
1072 manifest_response_reader_
->ReadData(
1073 read_manifest_buffer_
.get(),
1075 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete
,
1076 base::Unretained(this))); // async read
1079 void AppCacheUpdateJob::OnManifestDataReadComplete(int result
) {
1081 loaded_manifest_data_
.append(read_manifest_buffer_
->data(), result
);
1082 manifest_response_reader_
->ReadData(
1083 read_manifest_buffer_
.get(),
1085 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete
,
1086 base::Unretained(this))); // read more
1088 read_manifest_buffer_
= NULL
;
1089 manifest_response_reader_
.reset();
1090 ContinueHandleManifestFetchCompleted(
1091 result
< 0 || manifest_data_
!= loaded_manifest_data_
);
1095 void AppCacheUpdateJob::BuildUrlFileList(const Manifest
& manifest
) {
1096 for (base::hash_set
<std::string
>::const_iterator it
=
1097 manifest
.explicit_urls
.begin();
1098 it
!= manifest
.explicit_urls
.end(); ++it
) {
1099 AddUrlToFileList(GURL(*it
), AppCacheEntry::EXPLICIT
);
1102 const std::vector
<Namespace
>& intercepts
=
1103 manifest
.intercept_namespaces
;
1104 for (std::vector
<Namespace
>::const_iterator it
= intercepts
.begin();
1105 it
!= intercepts
.end(); ++it
) {
1106 int flags
= AppCacheEntry::INTERCEPT
;
1107 if (it
->is_executable
)
1108 flags
|= AppCacheEntry::EXECUTABLE
;
1109 AddUrlToFileList(it
->target_url
, flags
);
1112 const std::vector
<Namespace
>& fallbacks
=
1113 manifest
.fallback_namespaces
;
1114 for (std::vector
<Namespace
>::const_iterator it
= fallbacks
.begin();
1115 it
!= fallbacks
.end(); ++it
) {
1116 AddUrlToFileList(it
->target_url
, AppCacheEntry::FALLBACK
);
1119 // Add all master entries from newest complete cache.
1120 if (update_type_
== UPGRADE_ATTEMPT
) {
1121 const AppCache::EntryMap
& entries
=
1122 group_
->newest_complete_cache()->entries();
1123 for (AppCache::EntryMap::const_iterator it
= entries
.begin();
1124 it
!= entries
.end(); ++it
) {
1125 const AppCacheEntry
& entry
= it
->second
;
1126 if (entry
.IsMaster())
1127 AddUrlToFileList(it
->first
, AppCacheEntry::MASTER
);
1132 void AppCacheUpdateJob::AddUrlToFileList(const GURL
& url
, int type
) {
1133 std::pair
<AppCache::EntryMap::iterator
, bool> ret
= url_file_list_
.insert(
1134 AppCache::EntryMap::value_type(url
, AppCacheEntry(type
)));
1137 urls_to_fetch_
.push_back(UrlToFetch(url
, false, NULL
));
1139 ret
.first
->second
.add_types(type
); // URL already exists. Merge types.
1142 void AppCacheUpdateJob::FetchUrls() {
1143 DCHECK(internal_state_
== DOWNLOADING
);
1145 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
1146 // Fetch up to the concurrent limit. Other fetches will be triggered as each
1147 // each fetch completes.
1148 while (pending_url_fetches_
.size() < kMaxConcurrentUrlFetches
&&
1149 !urls_to_fetch_
.empty()) {
1150 UrlToFetch url_to_fetch
= urls_to_fetch_
.front();
1151 urls_to_fetch_
.pop_front();
1153 AppCache::EntryMap::iterator it
= url_file_list_
.find(url_to_fetch
.url
);
1154 DCHECK(it
!= url_file_list_
.end());
1155 AppCacheEntry
& entry
= it
->second
;
1156 if (ShouldSkipUrlFetch(entry
)) {
1157 NotifyAllProgress(url_to_fetch
.url
);
1158 ++url_fetches_completed_
;
1159 } else if (AlreadyFetchedEntry(url_to_fetch
.url
, entry
.types())) {
1160 NotifyAllProgress(url_to_fetch
.url
);
1161 ++url_fetches_completed_
; // saved a URL request
1162 } else if (!url_to_fetch
.storage_checked
&&
1163 MaybeLoadFromNewestCache(url_to_fetch
.url
, entry
)) {
1164 // Continues asynchronously after data is loaded from newest cache.
1166 URLFetcher
* fetcher
= new URLFetcher(
1167 url_to_fetch
.url
, URLFetcher::URL_FETCH
, this);
1168 if (url_to_fetch
.existing_response_info
.get()) {
1169 DCHECK(group_
->newest_complete_cache());
1170 AppCacheEntry
* existing_entry
=
1171 group_
->newest_complete_cache()->GetEntry(url_to_fetch
.url
);
1172 DCHECK(existing_entry
);
1173 DCHECK(existing_entry
->response_id() ==
1174 url_to_fetch
.existing_response_info
->response_id());
1175 fetcher
->set_existing_response_headers(
1176 url_to_fetch
.existing_response_info
->http_response_info()->headers
1178 fetcher
->set_existing_entry(*existing_entry
);
1181 pending_url_fetches_
.insert(
1182 PendingUrlFetches::value_type(url_to_fetch
.url
, fetcher
));
1187 void AppCacheUpdateJob::CancelAllUrlFetches() {
1188 // Cancel any pending URL requests.
1189 for (PendingUrlFetches::iterator it
= pending_url_fetches_
.begin();
1190 it
!= pending_url_fetches_
.end(); ++it
) {
1194 url_fetches_completed_
+=
1195 pending_url_fetches_
.size() + urls_to_fetch_
.size();
1196 pending_url_fetches_
.clear();
1197 urls_to_fetch_
.clear();
1200 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry
& entry
) {
1202 // If the resource URL being processed was flagged as neither an
1203 // "explicit entry" nor or a "fallback entry", then the user agent
1204 // may skip this URL.
1205 if (entry
.IsExplicit() || entry
.IsFallback() || entry
.IsIntercept())
1208 // TODO(jennb): decide if entry should be skipped to expire it from cache
1212 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL
& url
,
1214 DCHECK(internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
);
1215 AppCacheEntry
* existing
=
1216 inprogress_cache_
.get() ? inprogress_cache_
->GetEntry(url
)
1217 : group_
->newest_complete_cache()->GetEntry(url
);
1219 existing
->add_types(entry_type
);
1225 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost
* host
,
1228 DCHECK(!IsTerminating());
1230 if (internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
) {
1232 if (inprogress_cache_
.get()) {
1234 host
->AssociateIncompleteCache(inprogress_cache_
.get(), manifest_url_
);
1235 cache
= inprogress_cache_
.get();
1237 cache
= group_
->newest_complete_cache();
1240 // Update existing entry if it has already been fetched.
1241 AppCacheEntry
* entry
= cache
->GetEntry(url
);
1243 entry
->add_types(AppCacheEntry::MASTER
);
1244 if (internal_state_
== NO_UPDATE
&& !inprogress_cache_
.get()) {
1245 // only associate if have entry
1246 host
->AssociateCompleteCache(cache
);
1249 ++master_entries_completed_
; // pretend fetching completed
1254 // Add to fetch list if not already fetching.
1255 if (master_entry_fetches_
.find(url
) == master_entry_fetches_
.end()) {
1256 master_entries_to_fetch_
.insert(url
);
1257 if (internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
)
1258 FetchMasterEntries();
1262 void AppCacheUpdateJob::FetchMasterEntries() {
1263 DCHECK(internal_state_
== NO_UPDATE
|| internal_state_
== DOWNLOADING
);
1265 // Fetch each master entry in the list, up to the concurrent limit.
1266 // Additional fetches will be triggered as each fetch completes.
1267 while (master_entry_fetches_
.size() < kMaxConcurrentUrlFetches
&&
1268 !master_entries_to_fetch_
.empty()) {
1269 const GURL
& url
= *master_entries_to_fetch_
.begin();
1271 if (AlreadyFetchedEntry(url
, AppCacheEntry::MASTER
)) {
1272 ++master_entries_completed_
; // saved a URL request
1274 // In no update case, associate hosts to newest cache in group
1275 // now that master entry has been "successfully downloaded".
1276 if (internal_state_
== NO_UPDATE
) {
1277 // TODO(michaeln): defer until the updated cache has been stored.
1278 DCHECK(!inprogress_cache_
.get());
1279 AppCache
* cache
= group_
->newest_complete_cache();
1280 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
1281 DCHECK(found
!= pending_master_entries_
.end());
1282 PendingHosts
& hosts
= found
->second
;
1283 for (PendingHosts::iterator host_it
= hosts
.begin();
1284 host_it
!= hosts
.end(); ++host_it
) {
1285 (*host_it
)->AssociateCompleteCache(cache
);
1289 URLFetcher
* fetcher
= new URLFetcher(
1290 url
, URLFetcher::MASTER_ENTRY_FETCH
, this);
1292 master_entry_fetches_
.insert(PendingUrlFetches::value_type(url
, fetcher
));
1295 master_entries_to_fetch_
.erase(master_entries_to_fetch_
.begin());
1299 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1300 const AppCacheErrorDetails
& error_details
) {
1301 // For now, cancel all in-progress fetches for master entries and pretend
1302 // all master entries fetches have completed.
1303 // TODO(jennb): Delete this when update no longer fetches master entries
1306 // Cancel all in-progress fetches.
1307 for (PendingUrlFetches::iterator it
= master_entry_fetches_
.begin();
1308 it
!= master_entry_fetches_
.end(); ++it
) {
1310 master_entries_to_fetch_
.insert(it
->first
); // back in unfetched list
1312 master_entry_fetches_
.clear();
1314 master_entries_completed_
+= master_entries_to_fetch_
.size();
1316 // Cache failure steps, step 2.
1317 // Pretend all master entries that have not yet been fetched have completed
1318 // downloading. Unassociate hosts from any appcache and send ERROR event.
1319 HostNotifier host_notifier
;
1320 while (!master_entries_to_fetch_
.empty()) {
1321 const GURL
& url
= *master_entries_to_fetch_
.begin();
1322 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
1323 DCHECK(found
!= pending_master_entries_
.end());
1324 PendingHosts
& hosts
= found
->second
;
1325 for (PendingHosts::iterator host_it
= hosts
.begin();
1326 host_it
!= hosts
.end(); ++host_it
) {
1327 AppCacheHost
* host
= *host_it
;
1328 host
->AssociateNoCache(GURL());
1329 host_notifier
.AddHost(host
);
1330 host
->RemoveObserver(this);
1334 master_entries_to_fetch_
.erase(master_entries_to_fetch_
.begin());
1336 host_notifier
.SendErrorNotifications(error_details
);
1339 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL
& url
,
1340 AppCacheEntry
& entry
) {
1341 if (update_type_
!= UPGRADE_ATTEMPT
)
1344 AppCache
* newest
= group_
->newest_complete_cache();
1345 AppCacheEntry
* copy_me
= newest
->GetEntry(url
);
1346 if (!copy_me
|| !copy_me
->has_response_id())
1349 // Load HTTP headers for entry from newest cache.
1350 loading_responses_
.insert(
1351 LoadingResponses::value_type(copy_me
->response_id(), url
));
1352 storage_
->LoadResponseInfo(manifest_url_
, group_
->group_id(),
1353 copy_me
->response_id(),
1355 // Async: wait for OnResponseInfoLoaded to complete.
1359 void AppCacheUpdateJob::OnResponseInfoLoaded(
1360 AppCacheResponseInfo
* response_info
, int64 response_id
) {
1361 const net::HttpResponseInfo
* http_info
= response_info
?
1362 response_info
->http_response_info() : NULL
;
1364 // Needed response info for a manifest fetch request.
1365 if (internal_state_
== FETCH_MANIFEST
) {
1367 manifest_fetcher_
->set_existing_response_headers(
1368 http_info
->headers
.get());
1369 manifest_fetcher_
->Start();
1373 LoadingResponses::iterator found
= loading_responses_
.find(response_id
);
1374 DCHECK(found
!= loading_responses_
.end());
1375 const GURL
& url
= found
->second
;
1378 LoadFromNewestCacheFailed(url
, NULL
); // no response found
1380 // Check if response can be re-used according to HTTP caching semantics.
1381 // Responses with a "vary" header get treated as expired.
1382 const std::string name
= "vary";
1385 if (!http_info
->headers
.get() ||
1386 http_info
->headers
->RequiresValidation(http_info
->request_time
,
1387 http_info
->response_time
,
1388 base::Time::Now()) ||
1389 http_info
->headers
->EnumerateHeader(&iter
, name
, &value
)) {
1390 LoadFromNewestCacheFailed(url
, response_info
);
1392 DCHECK(group_
->newest_complete_cache());
1393 AppCacheEntry
* copy_me
= group_
->newest_complete_cache()->GetEntry(url
);
1395 DCHECK(copy_me
->response_id() == response_id
);
1397 AppCache::EntryMap::iterator it
= url_file_list_
.find(url
);
1398 DCHECK(it
!= url_file_list_
.end());
1399 AppCacheEntry
& entry
= it
->second
;
1400 entry
.set_response_id(response_id
);
1401 entry
.set_response_size(copy_me
->response_size());
1402 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
1403 NotifyAllProgress(url
);
1404 ++url_fetches_completed_
;
1407 loading_responses_
.erase(found
);
1409 MaybeCompleteUpdate();
1412 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1413 const GURL
& url
, AppCacheResponseInfo
* response_info
) {
1414 if (internal_state_
== CACHE_FAILURE
)
1417 // Re-insert url at front of fetch list. Indicate storage has been checked.
1418 urls_to_fetch_
.push_front(UrlToFetch(url
, true, response_info
));
1422 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1423 DCHECK(internal_state_
!= CACHE_FAILURE
);
1425 // Must wait for any pending master entries or url fetches to complete.
1426 if (master_entries_completed_
!= pending_master_entries_
.size() ||
1427 url_fetches_completed_
!= url_file_list_
.size()) {
1428 DCHECK(internal_state_
!= COMPLETED
);
1432 switch (internal_state_
) {
1434 if (master_entries_completed_
> 0) {
1435 switch (stored_state_
) {
1437 StoreGroupAndCache();
1445 // 6.9.4 steps 7.3-7.7.
1446 NotifyAllAssociatedHosts(APPCACHE_NO_UPDATE_EVENT
);
1447 DiscardDuplicateResponses();
1448 internal_state_
= COMPLETED
;
1451 internal_state_
= REFETCH_MANIFEST
;
1452 FetchManifest(false);
1454 case REFETCH_MANIFEST
:
1455 DCHECK(stored_state_
== STORED
);
1456 NotifyAllFinalProgress();
1457 if (update_type_
== CACHE_ATTEMPT
)
1458 NotifyAllAssociatedHosts(APPCACHE_CACHED_EVENT
);
1460 NotifyAllAssociatedHosts(APPCACHE_UPDATE_READY_EVENT
);
1461 DiscardDuplicateResponses();
1462 internal_state_
= COMPLETED
;
1463 LogHistogramStats(UPDATE_OK
, GURL());
1466 NOTREACHED(); // See HandleCacheFailure
1472 // Let the stack unwind before deletion to make it less risky as this
1473 // method is called from multiple places in this file.
1474 if (internal_state_
== COMPLETED
)
1478 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms
) {
1479 // TODO(jennb): post a delayed task with the "same parameters" as this job
1480 // to retry the update at a later time. Need group, URLs of pending master
1481 // entries and their hosts.
1484 void AppCacheUpdateJob::Cancel() {
1485 internal_state_
= CANCELLED
;
1487 LogHistogramStats(CANCELLED_ERROR
, GURL());
1489 if (manifest_fetcher_
) {
1490 delete manifest_fetcher_
;
1491 manifest_fetcher_
= NULL
;
1494 for (PendingUrlFetches::iterator it
= pending_url_fetches_
.begin();
1495 it
!= pending_url_fetches_
.end(); ++it
) {
1498 pending_url_fetches_
.clear();
1500 for (PendingUrlFetches::iterator it
= master_entry_fetches_
.begin();
1501 it
!= master_entry_fetches_
.end(); ++it
) {
1504 master_entry_fetches_
.clear();
1506 ClearPendingMasterEntries();
1507 DiscardInprogressCache();
1509 // Delete response writer to avoid any callbacks.
1510 if (manifest_response_writer_
)
1511 manifest_response_writer_
.reset();
1513 storage_
->CancelDelegateCallbacks(this);
1516 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1517 for (PendingMasters::iterator it
= pending_master_entries_
.begin();
1518 it
!= pending_master_entries_
.end(); ++it
) {
1519 PendingHosts
& hosts
= it
->second
;
1520 for (PendingHosts::iterator host_it
= hosts
.begin();
1521 host_it
!= hosts
.end(); ++host_it
) {
1522 (*host_it
)->RemoveObserver(this);
1526 pending_master_entries_
.clear();
1529 void AppCacheUpdateJob::DiscardInprogressCache() {
1530 if (stored_state_
== STORING
) {
1531 // We can make no assumptions about whether the StoreGroupAndCacheTask
1532 // actually completed or not. This condition should only be reachable
1533 // during shutdown. Free things up and return to do no harm.
1534 inprogress_cache_
= NULL
;
1535 added_master_entries_
.clear();
1539 storage_
->DoomResponses(manifest_url_
, stored_response_ids_
);
1541 if (!inprogress_cache_
.get()) {
1542 // We have to undo the changes we made, if any, to the existing cache.
1543 if (group_
&& group_
->newest_complete_cache()) {
1544 for (std::vector
<GURL
>::iterator iter
= added_master_entries_
.begin();
1545 iter
!= added_master_entries_
.end(); ++iter
) {
1546 group_
->newest_complete_cache()->RemoveEntry(*iter
);
1549 added_master_entries_
.clear();
1553 AppCache::AppCacheHosts
& hosts
= inprogress_cache_
->associated_hosts();
1554 while (!hosts
.empty())
1555 (*hosts
.begin())->AssociateNoCache(GURL());
1557 inprogress_cache_
= NULL
;
1558 added_master_entries_
.clear();
1561 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1562 storage_
->DoomResponses(manifest_url_
, duplicate_response_ids_
);
1565 void AppCacheUpdateJob::LogHistogramStats(
1566 ResultType result
, const GURL
& failed_resource_url
) {
1567 AppCacheHistograms::CountUpdateJobResult(result
, manifest_url_
.GetOrigin());
1568 if (result
== UPDATE_OK
)
1571 int percent_complete
= 0;
1572 if (url_file_list_
.size() > 0) {
1573 size_t actual_fetches_completed
= url_fetches_completed_
;
1574 if (!failed_resource_url
.is_empty() && actual_fetches_completed
)
1575 --actual_fetches_completed
;
1576 percent_complete
= (static_cast<double>(actual_fetches_completed
) /
1577 static_cast<double>(url_file_list_
.size())) * 100.0;
1578 percent_complete
= std::min(percent_complete
, 99);
1581 bool was_making_progress
=
1582 base::Time::Now() - last_progress_time_
<
1583 base::TimeDelta::FromMinutes(5);
1585 bool off_origin_resource_failure
=
1586 !failed_resource_url
.is_empty() &&
1587 (failed_resource_url
.GetOrigin() != manifest_url_
.GetOrigin());
1589 AppCacheHistograms::LogUpdateFailureStats(
1590 manifest_url_
.GetOrigin(),
1592 was_making_progress
,
1593 off_origin_resource_failure
);
1596 void AppCacheUpdateJob::DeleteSoon() {
1597 ClearPendingMasterEntries();
1598 manifest_response_writer_
.reset();
1599 storage_
->CancelDelegateCallbacks(this);
1600 service_
->RemoveObserver(this);
1603 // Break the connection with the group so the group cannot call delete
1604 // on this object after we've posted a task to delete ourselves.
1605 group_
->SetUpdateAppCacheStatus(AppCacheGroup::IDLE
);
1608 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
1611 } // namespace appcache