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/http/http_request_headers.h"
17 #include "net/http/http_response_headers.h"
18 #include "net/url_request/url_request_context.h"
19 #include "webkit/browser/appcache/appcache_group.h"
20 #include "webkit/browser/appcache/appcache_histograms.h"
24 static const int kBufferSize
= 32768;
25 static const size_t kMaxConcurrentUrlFetches
= 2;
26 static const int kMax503Retries
= 3;
28 // Helper class for collecting hosts per frontend when sending notifications
29 // so that only one notification is sent for all hosts using the same frontend.
32 typedef std::vector
<int> HostIds
;
33 typedef std::map
<AppCacheFrontend
*, HostIds
> NotifyHostMap
;
35 // Caller is responsible for ensuring there will be no duplicate hosts.
36 void AddHost(AppCacheHost
* host
) {
37 std::pair
<NotifyHostMap::iterator
, bool> ret
= hosts_to_notify
.insert(
38 NotifyHostMap::value_type(host
->frontend(), HostIds()));
39 ret
.first
->second
.push_back(host
->host_id());
42 void AddHosts(const std::set
<AppCacheHost
*>& hosts
) {
43 for (std::set
<AppCacheHost
*>::const_iterator it
= hosts
.begin();
44 it
!= hosts
.end(); ++it
) {
49 void SendNotifications(EventID event_id
) {
50 for (NotifyHostMap::iterator it
= hosts_to_notify
.begin();
51 it
!= hosts_to_notify
.end(); ++it
) {
52 AppCacheFrontend
* frontend
= it
->first
;
53 frontend
->OnEventRaised(it
->second
, event_id
);
57 void SendProgressNotifications(
58 const GURL
& url
, int num_total
, int num_complete
) {
59 for (NotifyHostMap::iterator it
= hosts_to_notify
.begin();
60 it
!= hosts_to_notify
.end(); ++it
) {
61 AppCacheFrontend
* frontend
= it
->first
;
62 frontend
->OnProgressEventRaised(it
->second
, url
,
63 num_total
, num_complete
);
67 void SendErrorNotifications(const std::string
& error_message
) {
68 DCHECK(!error_message
.empty());
69 for (NotifyHostMap::iterator it
= hosts_to_notify
.begin();
70 it
!= hosts_to_notify
.end(); ++it
) {
71 AppCacheFrontend
* frontend
= it
->first
;
72 frontend
->OnErrorEventRaised(it
->second
, error_message
);
77 NotifyHostMap hosts_to_notify
;
80 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL
& url
,
82 AppCacheResponseInfo
* info
)
84 storage_checked(checked
),
85 existing_response_info(info
) {
88 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
91 // Helper class to fetch resources. Depending on the fetch type,
92 // can either fetch to an in-memory string or write the response
93 // data out to the disk cache.
94 AppCacheUpdateJob::URLFetcher::URLFetcher(
95 const GURL
& url
, FetchType fetch_type
, AppCacheUpdateJob
* job
)
98 fetch_type_(fetch_type
),
99 retry_503_attempts_(0),
100 buffer_(new net::IOBuffer(kBufferSize
)),
101 request_(job
->service_
->request_context()->CreateRequest(url
, this)) {
104 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
107 void AppCacheUpdateJob::URLFetcher::Start() {
108 request_
->set_first_party_for_cookies(job_
->manifest_url_
);
109 request_
->set_load_flags(request_
->load_flags() |
110 net::LOAD_DISABLE_INTERCEPT
);
111 if (existing_response_headers_
.get())
112 AddConditionalHeaders(existing_response_headers_
.get());
116 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
117 net::URLRequest
* request
, const GURL
& new_url
, bool* defer_redirect
) {
118 DCHECK(request_
== request
);
119 // Redirect is not allowed by the update process.
121 OnResponseCompleted();
124 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
125 net::URLRequest
*request
) {
126 DCHECK(request
== request_
);
127 if (request
->status().is_success() &&
128 (request
->GetResponseCode() / 100) == 2) {
130 // See http://code.google.com/p/chromium/issues/detail?id=69594
131 // We willfully violate the HTML5 spec at this point in order
132 // to support the appcaching of cross-origin HTTPS resources.
133 // We've opted for a milder constraint and allow caching unless
134 // the resource has a "no-store" header. A spec change has been
135 // requested on the whatwg list.
136 // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
137 if (url_
.SchemeIsSecure() &&
138 url_
.GetOrigin() != job_
->manifest_url_
.GetOrigin()) {
139 if (request
->response_headers()->
140 HasHeaderValue("cache-control", "no-store")) {
142 OnResponseCompleted();
147 // Write response info to storage for URL fetches. Wait for async write
148 // completion before reading any response data.
149 if (fetch_type_
== URL_FETCH
|| fetch_type_
== MASTER_ENTRY_FETCH
) {
150 response_writer_
.reset(job_
->CreateResponseWriter());
151 scoped_refptr
<HttpResponseInfoIOBuffer
> io_buffer(
152 new HttpResponseInfoIOBuffer(
153 new net::HttpResponseInfo(request
->response_info())));
154 response_writer_
->WriteInfo(
156 base::Bind(&URLFetcher::OnWriteComplete
, base::Unretained(this)));
161 OnResponseCompleted();
165 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
166 net::URLRequest
* request
, int bytes_read
) {
167 DCHECK(request_
== request
);
168 bool data_consumed
= true;
169 if (request
->status().is_success() && bytes_read
> 0) {
170 data_consumed
= ConsumeResponseData(bytes_read
);
173 while (request
->Read(buffer_
.get(), kBufferSize
, &bytes_read
)) {
174 if (bytes_read
> 0) {
175 data_consumed
= ConsumeResponseData(bytes_read
);
177 break; // wait for async data processing, then read more
184 if (data_consumed
&& !request
->status().is_io_pending())
185 OnResponseCompleted();
188 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
189 const net::HttpResponseHeaders
* headers
) {
190 DCHECK(request_
.get() && headers
);
191 net::HttpRequestHeaders extra_headers
;
193 // Add If-Modified-Since header if response info has Last-Modified header.
194 const std::string last_modified
= "Last-Modified";
195 std::string last_modified_value
;
196 headers
->EnumerateHeader(NULL
, last_modified
, &last_modified_value
);
197 if (!last_modified_value
.empty()) {
198 extra_headers
.SetHeader(net::HttpRequestHeaders::kIfModifiedSince
,
199 last_modified_value
);
202 // Add If-None-Match header if response info has ETag header.
203 const std::string etag
= "ETag";
204 std::string etag_value
;
205 headers
->EnumerateHeader(NULL
, etag
, &etag_value
);
206 if (!etag_value
.empty()) {
207 extra_headers
.SetHeader(net::HttpRequestHeaders::kIfNoneMatch
,
210 if (!extra_headers
.IsEmpty())
211 request_
->SetExtraRequestHeaders(extra_headers
);
214 void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result
) {
217 OnResponseCompleted();
223 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
224 InternalUpdateState state
= job_
->internal_state_
;
225 if (state
== CACHE_FAILURE
|| state
== CANCELLED
|| state
== COMPLETED
)
228 request_
->Read(buffer_
.get(), kBufferSize
, &bytes_read
);
229 OnReadCompleted(request_
.get(), bytes_read
);
232 // Returns false if response data is processed asynchronously, in which
233 // case ReadResponseData will be invoked when it is safe to continue
234 // reading more response data from the request.
235 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read
) {
236 DCHECK_GT(bytes_read
, 0);
237 switch (fetch_type_
) {
239 case MANIFEST_REFETCH
:
240 manifest_data_
.append(buffer_
->data(), bytes_read
);
243 case MASTER_ENTRY_FETCH
:
244 DCHECK(response_writer_
.get());
245 response_writer_
->WriteData(
248 base::Bind(&URLFetcher::OnWriteComplete
, base::Unretained(this)));
249 return false; // wait for async write completion to continue reading
256 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
257 // Retry for 503s where retry-after is 0.
258 if (request_
->status().is_success() &&
259 request_
->GetResponseCode() == 503 &&
260 MaybeRetryRequest()) {
264 switch (fetch_type_
) {
266 job_
->HandleManifestFetchCompleted(this);
269 job_
->HandleUrlFetchCompleted(this);
271 case MASTER_ENTRY_FETCH
:
272 job_
->HandleMasterEntryFetchCompleted(this);
274 case MANIFEST_REFETCH
:
275 job_
->HandleManifestRefetchCompleted(this);
284 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
285 if (retry_503_attempts_
>= kMax503Retries
||
286 !request_
->response_headers()->HasHeaderValue("retry-after", "0")) {
289 ++retry_503_attempts_
;
290 request_
.reset(job_
->service_
->request_context()->CreateRequest(url_
, this));
295 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService
* service
,
296 AppCacheGroup
* group
)
298 manifest_url_(group
->manifest_url()),
300 update_type_(UNKNOWN_TYPE
),
301 internal_state_(FETCH_MANIFEST
),
302 master_entries_completed_(0),
303 url_fetches_completed_(0),
304 manifest_fetcher_(NULL
),
305 stored_state_(UNSTORED
) {
308 AppCacheUpdateJob::~AppCacheUpdateJob() {
309 if (internal_state_
!= COMPLETED
)
312 DCHECK(!manifest_fetcher_
);
313 DCHECK(pending_url_fetches_
.empty());
314 DCHECK(!inprogress_cache_
.get());
315 DCHECK(pending_master_entries_
.empty());
316 DCHECK(master_entry_fetches_
.empty());
319 group_
->SetUpdateStatus(AppCacheGroup::IDLE
);
322 void AppCacheUpdateJob::StartUpdate(AppCacheHost
* host
,
323 const GURL
& new_master_resource
) {
324 DCHECK(group_
->update_job() == this);
325 DCHECK(!group_
->is_obsolete());
327 bool is_new_pending_master_entry
= false;
328 if (!new_master_resource
.is_empty()) {
329 DCHECK(new_master_resource
== host
->pending_master_entry_url());
330 DCHECK(!new_master_resource
.has_ref());
331 DCHECK(new_master_resource
.GetOrigin() == manifest_url_
.GetOrigin());
333 // Cannot add more to this update if already terminating.
334 if (IsTerminating()) {
335 group_
->QueueUpdate(host
, new_master_resource
);
339 std::pair
<PendingMasters::iterator
, bool> ret
=
340 pending_master_entries_
.insert(
341 PendingMasters::value_type(new_master_resource
, PendingHosts()));
342 is_new_pending_master_entry
= ret
.second
;
343 ret
.first
->second
.push_back(host
);
344 host
->AddObserver(this);
347 // Notify host (if any) if already checking or downloading.
348 AppCacheGroup::UpdateStatus update_status
= group_
->update_status();
349 if (update_status
== AppCacheGroup::CHECKING
||
350 update_status
== AppCacheGroup::DOWNLOADING
) {
352 NotifySingleHost(host
, CHECKING_EVENT
);
353 if (update_status
== AppCacheGroup::DOWNLOADING
)
354 NotifySingleHost(host
, DOWNLOADING_EVENT
);
356 // Add to fetch list or an existing entry if already fetched.
357 if (!new_master_resource
.is_empty()) {
358 AddMasterEntryToFetchList(host
, new_master_resource
,
359 is_new_pending_master_entry
);
365 // Begin update process for the group.
366 group_
->SetUpdateStatus(AppCacheGroup::CHECKING
);
367 if (group_
->HasCache()) {
368 update_type_
= UPGRADE_ATTEMPT
;
369 NotifyAllAssociatedHosts(CHECKING_EVENT
);
371 update_type_
= CACHE_ATTEMPT
;
373 NotifySingleHost(host
, CHECKING_EVENT
);
376 if (!new_master_resource
.is_empty()) {
377 AddMasterEntryToFetchList(host
, new_master_resource
,
378 is_new_pending_master_entry
);
384 AppCacheResponseWriter
* AppCacheUpdateJob::CreateResponseWriter() {
385 AppCacheResponseWriter
* writer
=
386 service_
->storage()->CreateResponseWriter(manifest_url_
,
388 stored_response_ids_
.push_back(writer
->response_id());
392 void AppCacheUpdateJob::HandleCacheFailure(const std::string
& error_message
) {
393 // 6.9.4 cache failure steps 2-8.
394 DCHECK(internal_state_
!= CACHE_FAILURE
);
395 DCHECK(!error_message
.empty());
396 internal_state_
= CACHE_FAILURE
;
397 CancelAllUrlFetches();
398 CancelAllMasterEntryFetches(error_message
);
399 NotifyAllError(error_message
);
400 DiscardInprogressCache();
401 internal_state_
= COMPLETED
;
402 DeleteSoon(); // To unwind the stack prior to deletion.
405 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch
) {
406 DCHECK(!manifest_fetcher_
);
407 manifest_fetcher_
= new URLFetcher(
409 is_first_fetch
? URLFetcher::MANIFEST_FETCH
:
410 URLFetcher::MANIFEST_REFETCH
,
413 // Add any necessary Http headers before sending fetch request.
414 if (is_first_fetch
) {
415 AppCacheEntry
* entry
= (update_type_
== UPGRADE_ATTEMPT
) ?
416 group_
->newest_complete_cache()->GetEntry(manifest_url_
) : NULL
;
418 // Asynchronously load response info for manifest from newest cache.
419 service_
->storage()->LoadResponseInfo(manifest_url_
, group_
->group_id(),
420 entry
->response_id(), this);
422 manifest_fetcher_
->Start();
425 DCHECK(internal_state_
== REFETCH_MANIFEST
);
426 DCHECK(manifest_response_info_
.get());
427 manifest_fetcher_
->set_existing_response_headers(
428 manifest_response_info_
->headers
.get());
429 manifest_fetcher_
->Start();
434 void AppCacheUpdateJob::HandleManifestFetchCompleted(
435 URLFetcher
* fetcher
) {
436 DCHECK_EQ(internal_state_
, FETCH_MANIFEST
);
437 DCHECK_EQ(manifest_fetcher_
, fetcher
);
438 manifest_fetcher_
= NULL
;
440 net::URLRequest
* request
= fetcher
->request();
441 int response_code
= -1;
442 bool is_valid_response_code
= false;
443 if (request
->status().is_success()) {
444 response_code
= request
->GetResponseCode();
445 is_valid_response_code
= (response_code
/ 100 == 2);
448 if (is_valid_response_code
) {
449 manifest_data_
= fetcher
->manifest_data();
450 manifest_response_info_
.reset(
451 new net::HttpResponseInfo(request
->response_info()));
452 if (update_type_
== UPGRADE_ATTEMPT
)
453 CheckIfManifestChanged(); // continues asynchronously
455 ContinueHandleManifestFetchCompleted(true);
456 } else if (response_code
== 304 && update_type_
== UPGRADE_ATTEMPT
) {
457 ContinueHandleManifestFetchCompleted(false);
458 } else if ((response_code
== 404 || response_code
== 410) &&
459 update_type_
== UPGRADE_ATTEMPT
) {
460 service_
->storage()->MakeGroupObsolete(group_
, this); // async
462 const char* kFormatString
= "Manifest fetch failed (%d) %s";
463 std::string message
= base::StringPrintf(kFormatString
, response_code
,
464 manifest_url_
.spec().c_str());
465 HandleCacheFailure(message
);
469 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup
* group
,
471 DCHECK(master_entry_fetches_
.empty());
472 CancelAllMasterEntryFetches("The cache has been made obsolete, "
473 "the manifest file returned 404 or 410");
475 DCHECK(group
->is_obsolete());
476 NotifyAllAssociatedHosts(OBSOLETE_EVENT
);
477 internal_state_
= COMPLETED
;
478 MaybeCompleteUpdate();
480 // Treat failure to mark group obsolete as a cache failure.
481 HandleCacheFailure("Failed to mark the cache as obsolete");
485 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed
) {
486 DCHECK(internal_state_
== FETCH_MANIFEST
);
489 DCHECK(update_type_
== UPGRADE_ATTEMPT
);
490 internal_state_
= NO_UPDATE
;
492 // Wait for pending master entries to download.
493 FetchMasterEntries();
494 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps
499 if (!ParseManifest(manifest_url_
, manifest_data_
.data(),
500 manifest_data_
.length(), manifest
)) {
501 const char* kFormatString
= "Failed to parse manifest %s";
502 const std::string message
= base::StringPrintf(kFormatString
,
503 manifest_url_
.spec().c_str());
504 HandleCacheFailure(message
);
509 // Proceed with update process. Section 6.9.4 steps 8-20.
510 internal_state_
= DOWNLOADING
;
511 inprogress_cache_
= new AppCache(service_
->storage(),
512 service_
->storage()->NewCacheId());
513 BuildUrlFileList(manifest
);
514 inprogress_cache_
->InitializeWithManifest(&manifest
);
516 // Associate all pending master hosts with the newly created cache.
517 for (PendingMasters::iterator it
= pending_master_entries_
.begin();
518 it
!= pending_master_entries_
.end(); ++it
) {
519 PendingHosts
& hosts
= it
->second
;
520 for (PendingHosts::iterator host_it
= hosts
.begin();
521 host_it
!= hosts
.end(); ++host_it
) {
523 ->AssociateIncompleteCache(inprogress_cache_
.get(), manifest_url_
);
527 group_
->SetUpdateStatus(AppCacheGroup::DOWNLOADING
);
528 NotifyAllAssociatedHosts(DOWNLOADING_EVENT
);
530 FetchMasterEntries();
531 MaybeCompleteUpdate(); // if not done, continues when async fetches complete
534 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher
* fetcher
) {
535 DCHECK(internal_state_
== DOWNLOADING
);
537 net::URLRequest
* request
= fetcher
->request();
538 const GURL
& url
= request
->original_url();
539 pending_url_fetches_
.erase(url
);
540 NotifyAllProgress(url
);
541 ++url_fetches_completed_
;
543 int response_code
= request
->status().is_success()
544 ? request
->GetResponseCode() : -1;
545 AppCacheEntry
& entry
= url_file_list_
.find(url
)->second
;
547 if (response_code
/ 100 == 2) {
548 // Associate storage with the new entry.
549 DCHECK(fetcher
->response_writer());
550 entry
.set_response_id(fetcher
->response_writer()->response_id());
551 entry
.set_response_size(fetcher
->response_writer()->amount_written());
552 if (!inprogress_cache_
->AddOrModifyEntry(url
, entry
))
553 duplicate_response_ids_
.push_back(entry
.response_id());
555 // TODO(michaeln): Check for <html manifest=xxx>
556 // See http://code.google.com/p/chromium/issues/detail?id=97930
557 // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
558 // if (!manifestAttribute) skip it
560 // Foreign entries will be detected during cache selection.
561 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
562 // file whose root element is an html element with a manifest attribute
563 // whose value doesn't match the manifest url of the application cache
564 // being processed, mark the entry as being foreign.
566 VLOG(1) << "Request status: " << request
->status().status()
567 << " error: " << request
->status().error()
568 << " response code: " << response_code
;
569 if (entry
.IsExplicit() || entry
.IsFallback() || entry
.IsIntercept()) {
570 if (response_code
== 304 && fetcher
->existing_entry().has_response_id()) {
571 // Keep the existing response.
572 entry
.set_response_id(fetcher
->existing_entry().response_id());
573 entry
.set_response_size(fetcher
->existing_entry().response_size());
574 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
576 const char* kFormatString
= "Resource fetch failed (%d) %s";
577 const std::string message
= base::StringPrintf(kFormatString
,
578 response_code
, url
.spec().c_str());
579 HandleCacheFailure(message
);
582 } else if (response_code
== 404 || response_code
== 410) {
583 // Entry is skipped. They are dropped from the cache.
584 } else if (update_type_
== UPGRADE_ATTEMPT
&&
585 fetcher
->existing_entry().has_response_id()) {
586 // Keep the existing response.
587 // TODO(michaeln): Not sure this is a good idea. This is spec compliant
588 // but the old resource may or may not be compatible with the new contents
589 // of the cache. Impossible to know one way or the other.
590 entry
.set_response_id(fetcher
->existing_entry().response_id());
591 entry
.set_response_size(fetcher
->existing_entry().response_size());
592 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
596 // Fetch another URL now that one request has completed.
597 DCHECK(internal_state_
!= CACHE_FAILURE
);
599 MaybeCompleteUpdate();
602 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
603 URLFetcher
* fetcher
) {
604 DCHECK(internal_state_
== NO_UPDATE
|| internal_state_
== DOWNLOADING
);
606 // TODO(jennb): Handle downloads completing during cache failure when update
607 // no longer fetches master entries directly. For now, we cancel all pending
608 // master entry fetches when entering cache failure state so this will never
609 // be called in CACHE_FAILURE state.
611 net::URLRequest
* request
= fetcher
->request();
612 const GURL
& url
= request
->original_url();
613 master_entry_fetches_
.erase(url
);
614 ++master_entries_completed_
;
616 int response_code
= request
->status().is_success()
617 ? request
->GetResponseCode() : -1;
619 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
620 DCHECK(found
!= pending_master_entries_
.end());
621 PendingHosts
& hosts
= found
->second
;
623 // Section 6.9.4. No update case: step 7.3, else step 22.
624 if (response_code
/ 100 == 2) {
625 // Add fetched master entry to the appropriate cache.
626 AppCache
* cache
= inprogress_cache_
.get() ? inprogress_cache_
.get()
627 : group_
->newest_complete_cache();
628 DCHECK(fetcher
->response_writer());
629 AppCacheEntry
master_entry(AppCacheEntry::MASTER
,
630 fetcher
->response_writer()->response_id(),
631 fetcher
->response_writer()->amount_written());
632 if (cache
->AddOrModifyEntry(url
, master_entry
))
633 added_master_entries_
.push_back(url
);
635 duplicate_response_ids_
.push_back(master_entry
.response_id());
637 // In no-update case, associate host with the newest cache.
638 if (!inprogress_cache_
.get()) {
639 // TODO(michaeln): defer until the updated cache has been stored
640 DCHECK(cache
== group_
->newest_complete_cache());
641 for (PendingHosts::iterator host_it
= hosts
.begin();
642 host_it
!= hosts
.end(); ++host_it
) {
643 (*host_it
)->AssociateCompleteCache(cache
);
647 HostNotifier host_notifier
;
648 for (PendingHosts::iterator host_it
= hosts
.begin();
649 host_it
!= hosts
.end(); ++host_it
) {
650 AppCacheHost
* host
= *host_it
;
651 host_notifier
.AddHost(host
);
653 // In downloading case, disassociate host from inprogress cache.
654 if (inprogress_cache_
.get())
655 host
->AssociateNoCache(GURL());
657 host
->RemoveObserver(this);
661 const char* kFormatString
= "Master entry fetch failed (%d) %s";
662 const std::string message
= base::StringPrintf(kFormatString
,
663 response_code
, request
->url().spec().c_str());
664 host_notifier
.SendErrorNotifications(message
);
666 // In downloading case, update result is different if all master entries
667 // failed vs. only some failing.
668 if (inprogress_cache_
.get()) {
669 // Only count successful downloads to know if all master entries failed.
670 pending_master_entries_
.erase(found
);
671 --master_entries_completed_
;
673 // Section 6.9.4, step 22.3.
674 if (update_type_
== CACHE_ATTEMPT
&& pending_master_entries_
.empty()) {
675 HandleCacheFailure(message
);
681 DCHECK(internal_state_
!= CACHE_FAILURE
);
682 FetchMasterEntries();
683 MaybeCompleteUpdate();
686 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
687 URLFetcher
* fetcher
) {
688 DCHECK(internal_state_
== REFETCH_MANIFEST
);
689 DCHECK(manifest_fetcher_
== fetcher
);
690 manifest_fetcher_
= NULL
;
692 net::URLRequest
* request
= fetcher
->request();
693 int response_code
= request
->status().is_success()
694 ? request
->GetResponseCode() : -1;
695 if (response_code
== 304 || manifest_data_
== fetcher
->manifest_data()) {
696 // Only need to store response in storage if manifest is not already
697 // an entry in the cache.
698 AppCacheEntry
* entry
= inprogress_cache_
->GetEntry(manifest_url_
);
700 entry
->add_types(AppCacheEntry::MANIFEST
);
701 StoreGroupAndCache();
703 manifest_response_writer_
.reset(CreateResponseWriter());
704 scoped_refptr
<HttpResponseInfoIOBuffer
> io_buffer(
705 new HttpResponseInfoIOBuffer(manifest_response_info_
.release()));
706 manifest_response_writer_
->WriteInfo(
708 base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete
,
709 base::Unretained(this)));
712 VLOG(1) << "Request status: " << request
->status().status()
713 << " error: " << request
->status().error()
714 << " response code: " << response_code
;
715 ScheduleUpdateRetry(kRerunDelayMs
);
716 HandleCacheFailure("Manifest changed during update, scheduling retry");
720 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result
) {
722 scoped_refptr
<net::StringIOBuffer
> io_buffer(
723 new net::StringIOBuffer(manifest_data_
));
724 manifest_response_writer_
->WriteData(
726 manifest_data_
.length(),
727 base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete
,
728 base::Unretained(this)));
730 HandleCacheFailure("Failed to write the manifest headers to storage");
734 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result
) {
736 AppCacheEntry
entry(AppCacheEntry::MANIFEST
,
737 manifest_response_writer_
->response_id(),
738 manifest_response_writer_
->amount_written());
739 if (!inprogress_cache_
->AddOrModifyEntry(manifest_url_
, entry
))
740 duplicate_response_ids_
.push_back(entry
.response_id());
741 StoreGroupAndCache();
743 HandleCacheFailure("Failed to write the manifest data to storage");
747 void AppCacheUpdateJob::StoreGroupAndCache() {
748 DCHECK(stored_state_
== UNSTORED
);
749 stored_state_
= STORING
;
750 scoped_refptr
<AppCache
> newest_cache
;
751 if (inprogress_cache_
.get())
752 newest_cache
.swap(inprogress_cache_
);
754 newest_cache
= group_
->newest_complete_cache();
755 newest_cache
->set_update_time(base::Time::Now());
757 // TODO(michaeln): dcheck is fishing for clues to crbug/95101
758 DCHECK_EQ(manifest_url_
, group_
->manifest_url());
760 ->StoreGroupAndNewestCache(group_
, newest_cache
.get(), this); // async
763 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup
* group
,
764 AppCache
* newest_cache
,
766 bool would_exceed_quota
) {
767 DCHECK(stored_state_
== STORING
);
769 stored_state_
= STORED
;
770 MaybeCompleteUpdate(); // will definitely complete
772 // Restore inprogress_cache_ to get the proper events delivered
773 // and the proper cleanup to occur.
774 if (newest_cache
!= group
->newest_complete_cache())
775 inprogress_cache_
= newest_cache
;
777 std::string
message("Failed to commit new cache to storage");
778 if (would_exceed_quota
)
779 message
.append(", would exceed quota");
780 HandleCacheFailure(message
);
784 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost
* host
,
786 std::vector
<int> ids(1, host
->host_id());
787 host
->frontend()->OnEventRaised(ids
, event_id
);
790 void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id
) {
791 HostNotifier host_notifier
;
792 AddAllAssociatedHostsToNotifier(&host_notifier
);
793 host_notifier
.SendNotifications(event_id
);
796 void AppCacheUpdateJob::NotifyAllProgress(const GURL
& url
) {
797 HostNotifier host_notifier
;
798 AddAllAssociatedHostsToNotifier(&host_notifier
);
799 host_notifier
.SendProgressNotifications(
800 url
, url_file_list_
.size(), url_fetches_completed_
);
803 void AppCacheUpdateJob::NotifyAllFinalProgress() {
804 DCHECK(url_file_list_
.size() == url_fetches_completed_
);
805 NotifyAllProgress(GURL());
808 void AppCacheUpdateJob::NotifyAllError(const std::string
& error_message
) {
809 HostNotifier host_notifier
;
810 AddAllAssociatedHostsToNotifier(&host_notifier
);
811 host_notifier
.SendErrorNotifications(error_message
);
814 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
815 HostNotifier
* host_notifier
) {
816 // Collect hosts so we only send one notification per frontend.
817 // A host can only be associated with a single cache so no need to worry
818 // about duplicate hosts being added to the notifier.
819 if (inprogress_cache_
.get()) {
820 DCHECK(internal_state_
== DOWNLOADING
|| internal_state_
== CACHE_FAILURE
);
821 host_notifier
->AddHosts(inprogress_cache_
->associated_hosts());
824 AppCacheGroup::Caches old_caches
= group_
->old_caches();
825 for (AppCacheGroup::Caches::const_iterator it
= old_caches
.begin();
826 it
!= old_caches
.end(); ++it
) {
827 host_notifier
->AddHosts((*it
)->associated_hosts());
830 AppCache
* newest_cache
= group_
->newest_complete_cache();
832 host_notifier
->AddHosts(newest_cache
->associated_hosts());
835 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost
* host
) {
836 // The host is about to be deleted; remove from our collection.
837 PendingMasters::iterator found
=
838 pending_master_entries_
.find(host
->pending_master_entry_url());
839 DCHECK(found
!= pending_master_entries_
.end());
840 PendingHosts
& hosts
= found
->second
;
841 PendingHosts::iterator it
= std::find(hosts
.begin(), hosts
.end(), host
);
842 DCHECK(it
!= hosts
.end());
846 void AppCacheUpdateJob::CheckIfManifestChanged() {
847 DCHECK(update_type_
== UPGRADE_ATTEMPT
);
848 AppCacheEntry
* entry
=
849 group_
->newest_complete_cache()->GetEntry(manifest_url_
);
851 // TODO(michaeln): This is just a bandaid to avoid a crash.
852 // http://code.google.com/p/chromium/issues/detail?id=95101
853 HandleCacheFailure("Manifest entry not found in existing cache");
854 AppCacheHistograms::AddMissingManifestEntrySample();
855 service_
->DeleteAppCacheGroup(manifest_url_
, net::CompletionCallback());
859 // Load manifest data from storage to compare against fetched manifest.
860 manifest_response_reader_
.reset(
861 service_
->storage()->CreateResponseReader(manifest_url_
,
863 entry
->response_id()));
864 read_manifest_buffer_
= new net::IOBuffer(kBufferSize
);
865 manifest_response_reader_
->ReadData(
866 read_manifest_buffer_
.get(),
868 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete
,
869 base::Unretained(this))); // async read
872 void AppCacheUpdateJob::OnManifestDataReadComplete(int result
) {
874 loaded_manifest_data_
.append(read_manifest_buffer_
->data(), result
);
875 manifest_response_reader_
->ReadData(
876 read_manifest_buffer_
.get(),
878 base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete
,
879 base::Unretained(this))); // read more
881 read_manifest_buffer_
= NULL
;
882 manifest_response_reader_
.reset();
883 ContinueHandleManifestFetchCompleted(
884 result
< 0 || manifest_data_
!= loaded_manifest_data_
);
888 void AppCacheUpdateJob::BuildUrlFileList(const Manifest
& manifest
) {
889 for (base::hash_set
<std::string
>::const_iterator it
=
890 manifest
.explicit_urls
.begin();
891 it
!= manifest
.explicit_urls
.end(); ++it
) {
892 AddUrlToFileList(GURL(*it
), AppCacheEntry::EXPLICIT
);
895 const std::vector
<Namespace
>& intercepts
=
896 manifest
.intercept_namespaces
;
897 for (std::vector
<Namespace
>::const_iterator it
= intercepts
.begin();
898 it
!= intercepts
.end(); ++it
) {
899 int flags
= AppCacheEntry::INTERCEPT
;
900 if (it
->is_executable
)
901 flags
|= AppCacheEntry::EXECUTABLE
;
902 AddUrlToFileList(it
->target_url
, flags
);
905 const std::vector
<Namespace
>& fallbacks
=
906 manifest
.fallback_namespaces
;
907 for (std::vector
<Namespace
>::const_iterator it
= fallbacks
.begin();
908 it
!= fallbacks
.end(); ++it
) {
909 AddUrlToFileList(it
->target_url
, AppCacheEntry::FALLBACK
);
912 // Add all master entries from newest complete cache.
913 if (update_type_
== UPGRADE_ATTEMPT
) {
914 const AppCache::EntryMap
& entries
=
915 group_
->newest_complete_cache()->entries();
916 for (AppCache::EntryMap::const_iterator it
= entries
.begin();
917 it
!= entries
.end(); ++it
) {
918 const AppCacheEntry
& entry
= it
->second
;
919 if (entry
.IsMaster())
920 AddUrlToFileList(it
->first
, AppCacheEntry::MASTER
);
925 void AppCacheUpdateJob::AddUrlToFileList(const GURL
& url
, int type
) {
926 std::pair
<AppCache::EntryMap::iterator
, bool> ret
= url_file_list_
.insert(
927 AppCache::EntryMap::value_type(url
, AppCacheEntry(type
)));
930 urls_to_fetch_
.push_back(UrlToFetch(url
, false, NULL
));
932 ret
.first
->second
.add_types(type
); // URL already exists. Merge types.
935 void AppCacheUpdateJob::FetchUrls() {
936 DCHECK(internal_state_
== DOWNLOADING
);
938 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
939 // Fetch up to the concurrent limit. Other fetches will be triggered as each
940 // each fetch completes.
941 while (pending_url_fetches_
.size() < kMaxConcurrentUrlFetches
&&
942 !urls_to_fetch_
.empty()) {
943 UrlToFetch url_to_fetch
= urls_to_fetch_
.front();
944 urls_to_fetch_
.pop_front();
946 AppCache::EntryMap::iterator it
= url_file_list_
.find(url_to_fetch
.url
);
947 DCHECK(it
!= url_file_list_
.end());
948 AppCacheEntry
& entry
= it
->second
;
949 if (ShouldSkipUrlFetch(entry
)) {
950 NotifyAllProgress(url_to_fetch
.url
);
951 ++url_fetches_completed_
;
952 } else if (AlreadyFetchedEntry(url_to_fetch
.url
, entry
.types())) {
953 NotifyAllProgress(url_to_fetch
.url
);
954 ++url_fetches_completed_
; // saved a URL request
955 } else if (!url_to_fetch
.storage_checked
&&
956 MaybeLoadFromNewestCache(url_to_fetch
.url
, entry
)) {
957 // Continues asynchronously after data is loaded from newest cache.
959 URLFetcher
* fetcher
= new URLFetcher(
960 url_to_fetch
.url
, URLFetcher::URL_FETCH
, this);
961 if (url_to_fetch
.existing_response_info
.get()) {
962 DCHECK(group_
->newest_complete_cache());
963 AppCacheEntry
* existing_entry
=
964 group_
->newest_complete_cache()->GetEntry(url_to_fetch
.url
);
965 DCHECK(existing_entry
);
966 DCHECK(existing_entry
->response_id() ==
967 url_to_fetch
.existing_response_info
->response_id());
968 fetcher
->set_existing_response_headers(
969 url_to_fetch
.existing_response_info
->http_response_info()->headers
971 fetcher
->set_existing_entry(*existing_entry
);
974 pending_url_fetches_
.insert(
975 PendingUrlFetches::value_type(url_to_fetch
.url
, fetcher
));
980 void AppCacheUpdateJob::CancelAllUrlFetches() {
981 // Cancel any pending URL requests.
982 for (PendingUrlFetches::iterator it
= pending_url_fetches_
.begin();
983 it
!= pending_url_fetches_
.end(); ++it
) {
987 url_fetches_completed_
+=
988 pending_url_fetches_
.size() + urls_to_fetch_
.size();
989 pending_url_fetches_
.clear();
990 urls_to_fetch_
.clear();
993 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry
& entry
) {
995 // If the resource URL being processed was flagged as neither an
996 // "explicit entry" nor or a "fallback entry", then the user agent
997 // may skip this URL.
998 if (entry
.IsExplicit() || entry
.IsFallback() || entry
.IsIntercept())
1001 // TODO(jennb): decide if entry should be skipped to expire it from cache
1005 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL
& url
,
1007 DCHECK(internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
);
1008 AppCacheEntry
* existing
=
1009 inprogress_cache_
.get() ? inprogress_cache_
->GetEntry(url
)
1010 : group_
->newest_complete_cache()->GetEntry(url
);
1012 existing
->add_types(entry_type
);
1018 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost
* host
,
1021 DCHECK(!IsTerminating());
1023 if (internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
) {
1025 if (inprogress_cache_
.get()) {
1027 host
->AssociateIncompleteCache(inprogress_cache_
.get(), manifest_url_
);
1028 cache
= inprogress_cache_
.get();
1030 cache
= group_
->newest_complete_cache();
1033 // Update existing entry if it has already been fetched.
1034 AppCacheEntry
* entry
= cache
->GetEntry(url
);
1036 entry
->add_types(AppCacheEntry::MASTER
);
1037 if (internal_state_
== NO_UPDATE
&& !inprogress_cache_
.get()) {
1038 // only associate if have entry
1039 host
->AssociateCompleteCache(cache
);
1042 ++master_entries_completed_
; // pretend fetching completed
1047 // Add to fetch list if not already fetching.
1048 if (master_entry_fetches_
.find(url
) == master_entry_fetches_
.end()) {
1049 master_entries_to_fetch_
.insert(url
);
1050 if (internal_state_
== DOWNLOADING
|| internal_state_
== NO_UPDATE
)
1051 FetchMasterEntries();
1055 void AppCacheUpdateJob::FetchMasterEntries() {
1056 DCHECK(internal_state_
== NO_UPDATE
|| internal_state_
== DOWNLOADING
);
1058 // Fetch each master entry in the list, up to the concurrent limit.
1059 // Additional fetches will be triggered as each fetch completes.
1060 while (master_entry_fetches_
.size() < kMaxConcurrentUrlFetches
&&
1061 !master_entries_to_fetch_
.empty()) {
1062 const GURL
& url
= *master_entries_to_fetch_
.begin();
1064 if (AlreadyFetchedEntry(url
, AppCacheEntry::MASTER
)) {
1065 ++master_entries_completed_
; // saved a URL request
1067 // In no update case, associate hosts to newest cache in group
1068 // now that master entry has been "successfully downloaded".
1069 if (internal_state_
== NO_UPDATE
) {
1070 // TODO(michaeln): defer until the updated cache has been stored.
1071 DCHECK(!inprogress_cache_
.get());
1072 AppCache
* cache
= group_
->newest_complete_cache();
1073 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
1074 DCHECK(found
!= pending_master_entries_
.end());
1075 PendingHosts
& hosts
= found
->second
;
1076 for (PendingHosts::iterator host_it
= hosts
.begin();
1077 host_it
!= hosts
.end(); ++host_it
) {
1078 (*host_it
)->AssociateCompleteCache(cache
);
1082 URLFetcher
* fetcher
= new URLFetcher(
1083 url
, URLFetcher::MASTER_ENTRY_FETCH
, this);
1085 master_entry_fetches_
.insert(PendingUrlFetches::value_type(url
, fetcher
));
1088 master_entries_to_fetch_
.erase(master_entries_to_fetch_
.begin());
1092 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
1093 const std::string
& error_message
) {
1094 // For now, cancel all in-progress fetches for master entries and pretend
1095 // all master entries fetches have completed.
1096 // TODO(jennb): Delete this when update no longer fetches master entries
1099 // Cancel all in-progress fetches.
1100 for (PendingUrlFetches::iterator it
= master_entry_fetches_
.begin();
1101 it
!= master_entry_fetches_
.end(); ++it
) {
1103 master_entries_to_fetch_
.insert(it
->first
); // back in unfetched list
1105 master_entry_fetches_
.clear();
1107 master_entries_completed_
+= master_entries_to_fetch_
.size();
1109 // Cache failure steps, step 2.
1110 // Pretend all master entries that have not yet been fetched have completed
1111 // downloading. Unassociate hosts from any appcache and send ERROR event.
1112 HostNotifier host_notifier
;
1113 while (!master_entries_to_fetch_
.empty()) {
1114 const GURL
& url
= *master_entries_to_fetch_
.begin();
1115 PendingMasters::iterator found
= pending_master_entries_
.find(url
);
1116 DCHECK(found
!= pending_master_entries_
.end());
1117 PendingHosts
& hosts
= found
->second
;
1118 for (PendingHosts::iterator host_it
= hosts
.begin();
1119 host_it
!= hosts
.end(); ++host_it
) {
1120 AppCacheHost
* host
= *host_it
;
1121 host
->AssociateNoCache(GURL());
1122 host_notifier
.AddHost(host
);
1123 host
->RemoveObserver(this);
1127 master_entries_to_fetch_
.erase(master_entries_to_fetch_
.begin());
1129 host_notifier
.SendErrorNotifications(error_message
);
1132 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL
& url
,
1133 AppCacheEntry
& entry
) {
1134 if (update_type_
!= UPGRADE_ATTEMPT
)
1137 AppCache
* newest
= group_
->newest_complete_cache();
1138 AppCacheEntry
* copy_me
= newest
->GetEntry(url
);
1139 if (!copy_me
|| !copy_me
->has_response_id())
1142 // Load HTTP headers for entry from newest cache.
1143 loading_responses_
.insert(
1144 LoadingResponses::value_type(copy_me
->response_id(), url
));
1145 service_
->storage()->LoadResponseInfo(manifest_url_
, group_
->group_id(),
1146 copy_me
->response_id(),
1148 // Async: wait for OnResponseInfoLoaded to complete.
1152 void AppCacheUpdateJob::OnResponseInfoLoaded(
1153 AppCacheResponseInfo
* response_info
, int64 response_id
) {
1154 const net::HttpResponseInfo
* http_info
= response_info
?
1155 response_info
->http_response_info() : NULL
;
1157 // Needed response info for a manifest fetch request.
1158 if (internal_state_
== FETCH_MANIFEST
) {
1160 manifest_fetcher_
->set_existing_response_headers(
1161 http_info
->headers
.get());
1162 manifest_fetcher_
->Start();
1166 LoadingResponses::iterator found
= loading_responses_
.find(response_id
);
1167 DCHECK(found
!= loading_responses_
.end());
1168 const GURL
& url
= found
->second
;
1171 LoadFromNewestCacheFailed(url
, NULL
); // no response found
1173 // Check if response can be re-used according to HTTP caching semantics.
1174 // Responses with a "vary" header get treated as expired.
1175 const std::string name
= "vary";
1178 if (!http_info
->headers
.get() ||
1179 http_info
->headers
->RequiresValidation(http_info
->request_time
,
1180 http_info
->response_time
,
1181 base::Time::Now()) ||
1182 http_info
->headers
->EnumerateHeader(&iter
, name
, &value
)) {
1183 LoadFromNewestCacheFailed(url
, response_info
);
1185 DCHECK(group_
->newest_complete_cache());
1186 AppCacheEntry
* copy_me
= group_
->newest_complete_cache()->GetEntry(url
);
1188 DCHECK(copy_me
->response_id() == response_id
);
1190 AppCache::EntryMap::iterator it
= url_file_list_
.find(url
);
1191 DCHECK(it
!= url_file_list_
.end());
1192 AppCacheEntry
& entry
= it
->second
;
1193 entry
.set_response_id(response_id
);
1194 entry
.set_response_size(copy_me
->response_size());
1195 inprogress_cache_
->AddOrModifyEntry(url
, entry
);
1196 NotifyAllProgress(url
);
1197 ++url_fetches_completed_
;
1200 loading_responses_
.erase(found
);
1202 MaybeCompleteUpdate();
1205 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
1206 const GURL
& url
, AppCacheResponseInfo
* response_info
) {
1207 if (internal_state_
== CACHE_FAILURE
)
1210 // Re-insert url at front of fetch list. Indicate storage has been checked.
1211 urls_to_fetch_
.push_front(UrlToFetch(url
, true, response_info
));
1215 void AppCacheUpdateJob::MaybeCompleteUpdate() {
1216 DCHECK(internal_state_
!= CACHE_FAILURE
);
1218 // Must wait for any pending master entries or url fetches to complete.
1219 if (master_entries_completed_
!= pending_master_entries_
.size() ||
1220 url_fetches_completed_
!= url_file_list_
.size()) {
1221 DCHECK(internal_state_
!= COMPLETED
);
1225 switch (internal_state_
) {
1227 if (master_entries_completed_
> 0) {
1228 switch (stored_state_
) {
1230 StoreGroupAndCache();
1238 // 6.9.4 steps 7.3-7.7.
1239 NotifyAllAssociatedHosts(NO_UPDATE_EVENT
);
1240 DiscardDuplicateResponses();
1241 internal_state_
= COMPLETED
;
1244 internal_state_
= REFETCH_MANIFEST
;
1245 FetchManifest(false);
1247 case REFETCH_MANIFEST
:
1248 DCHECK(stored_state_
== STORED
);
1249 NotifyAllFinalProgress();
1250 if (update_type_
== CACHE_ATTEMPT
)
1251 NotifyAllAssociatedHosts(CACHED_EVENT
);
1253 NotifyAllAssociatedHosts(UPDATE_READY_EVENT
);
1254 DiscardDuplicateResponses();
1255 internal_state_
= COMPLETED
;
1258 NOTREACHED(); // See HandleCacheFailure
1264 // Let the stack unwind before deletion to make it less risky as this
1265 // method is called from multiple places in this file.
1266 if (internal_state_
== COMPLETED
)
1270 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms
) {
1271 // TODO(jennb): post a delayed task with the "same parameters" as this job
1272 // to retry the update at a later time. Need group, URLs of pending master
1273 // entries and their hosts.
1276 void AppCacheUpdateJob::Cancel() {
1277 internal_state_
= CANCELLED
;
1279 if (manifest_fetcher_
) {
1280 delete manifest_fetcher_
;
1281 manifest_fetcher_
= NULL
;
1284 for (PendingUrlFetches::iterator it
= pending_url_fetches_
.begin();
1285 it
!= pending_url_fetches_
.end(); ++it
) {
1288 pending_url_fetches_
.clear();
1290 for (PendingUrlFetches::iterator it
= master_entry_fetches_
.begin();
1291 it
!= master_entry_fetches_
.end(); ++it
) {
1294 master_entry_fetches_
.clear();
1296 ClearPendingMasterEntries();
1297 DiscardInprogressCache();
1299 // Delete response writer to avoid any callbacks.
1300 if (manifest_response_writer_
)
1301 manifest_response_writer_
.reset();
1303 service_
->storage()->CancelDelegateCallbacks(this);
1306 void AppCacheUpdateJob::ClearPendingMasterEntries() {
1307 for (PendingMasters::iterator it
= pending_master_entries_
.begin();
1308 it
!= pending_master_entries_
.end(); ++it
) {
1309 PendingHosts
& hosts
= it
->second
;
1310 for (PendingHosts::iterator host_it
= hosts
.begin();
1311 host_it
!= hosts
.end(); ++host_it
) {
1312 (*host_it
)->RemoveObserver(this);
1316 pending_master_entries_
.clear();
1319 void AppCacheUpdateJob::DiscardInprogressCache() {
1320 service_
->storage()->DoomResponses(manifest_url_
, stored_response_ids_
);
1322 if (!inprogress_cache_
.get()) {
1323 // We have to undo the changes we made, if any, to the existing cache.
1324 for (std::vector
<GURL
>::iterator iter
= added_master_entries_
.begin();
1325 iter
!= added_master_entries_
.end(); ++iter
) {
1326 DCHECK(group_
->newest_complete_cache());
1327 group_
->newest_complete_cache()->RemoveEntry(*iter
);
1332 AppCache::AppCacheHosts
& hosts
= inprogress_cache_
->associated_hosts();
1333 while (!hosts
.empty())
1334 (*hosts
.begin())->AssociateNoCache(GURL());
1336 inprogress_cache_
= NULL
;
1339 void AppCacheUpdateJob::DiscardDuplicateResponses() {
1340 service_
->storage()->DoomResponses(manifest_url_
, duplicate_response_ids_
);
1343 void AppCacheUpdateJob::DeleteSoon() {
1344 ClearPendingMasterEntries();
1345 manifest_response_writer_
.reset();
1346 service_
->storage()->CancelDelegateCallbacks(this);
1348 // Break the connection with the group so the group cannot call delete
1349 // on this object after we've posted a task to delete ourselves.
1350 group_
->SetUpdateStatus(AppCacheGroup::IDLE
);
1353 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
1356 } // namespace appcache