Home | History | Annotate | Download | only in appcache
      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.
      4 
      5 #include "webkit/browser/appcache/appcache_update_job.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/compiler_specific.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/strings/stringprintf.h"
     13 #include "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"
     22 
     23 namespace appcache {
     24 
     25 static const int kBufferSize = 32768;
     26 static const size_t kMaxConcurrentUrlFetches = 2;
     27 static const int kMax503Retries = 3;
     28 
     29 // Helper class for collecting hosts per frontend when sending notifications
     30 // so that only one notification is sent for all hosts using the same frontend.
     31 class HostNotifier {
     32  public:
     33   typedef std::vector<int> HostIds;
     34   typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap;
     35 
     36   // Caller is responsible for ensuring there will be no duplicate hosts.
     37   void AddHost(AppCacheHost* host) {
     38     std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert(
     39         NotifyHostMap::value_type(host->frontend(), HostIds()));
     40     ret.first->second.push_back(host->host_id());
     41   }
     42 
     43   void AddHosts(const std::set<AppCacheHost*>& hosts) {
     44     for (std::set<AppCacheHost*>::const_iterator it = hosts.begin();
     45          it != hosts.end(); ++it) {
     46       AddHost(*it);
     47     }
     48   }
     49 
     50   void SendNotifications(EventID event_id) {
     51     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     52          it != hosts_to_notify.end(); ++it) {
     53       AppCacheFrontend* frontend = it->first;
     54       frontend->OnEventRaised(it->second, event_id);
     55     }
     56   }
     57 
     58   void SendProgressNotifications(
     59       const GURL& url, int num_total, int num_complete) {
     60     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     61          it != hosts_to_notify.end(); ++it) {
     62       AppCacheFrontend* frontend = it->first;
     63       frontend->OnProgressEventRaised(it->second, url,
     64                                       num_total, num_complete);
     65     }
     66   }
     67 
     68   void SendErrorNotifications(const std::string& error_message) {
     69     DCHECK(!error_message.empty());
     70     for (NotifyHostMap::iterator it = hosts_to_notify.begin();
     71          it != hosts_to_notify.end(); ++it) {
     72       AppCacheFrontend* frontend = it->first;
     73       frontend->OnErrorEventRaised(it->second, error_message);
     74     }
     75   }
     76 
     77  private:
     78   NotifyHostMap hosts_to_notify;
     79 };
     80 
     81 AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
     82                                           bool checked,
     83                                           AppCacheResponseInfo* info)
     84     : url(url),
     85       storage_checked(checked),
     86       existing_response_info(info) {
     87 }
     88 
     89 AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
     90 }
     91 
     92 // Helper class to fetch resources. Depending on the fetch type,
     93 // can either fetch to an in-memory string or write the response
     94 // data out to the disk cache.
     95 AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
     96                                           FetchType fetch_type,
     97                                           AppCacheUpdateJob* job)
     98     : url_(url),
     99       job_(job),
    100       fetch_type_(fetch_type),
    101       retry_503_attempts_(0),
    102       buffer_(new net::IOBuffer(kBufferSize)),
    103       request_(job->service_->request_context()
    104                    ->CreateRequest(url, net::DEFAULT_PRIORITY, this)) {}
    105 
    106 AppCacheUpdateJob::URLFetcher::~URLFetcher() {
    107 }
    108 
    109 void AppCacheUpdateJob::URLFetcher::Start() {
    110   request_->set_first_party_for_cookies(job_->manifest_url_);
    111   request_->SetLoadFlags(request_->load_flags() |
    112                          net::LOAD_DISABLE_INTERCEPT);
    113   if (existing_response_headers_.get())
    114     AddConditionalHeaders(existing_response_headers_.get());
    115   request_->Start();
    116 }
    117 
    118 void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
    119     net::URLRequest* request, const GURL& new_url,  bool* defer_redirect) {
    120   DCHECK(request_ == request);
    121   // Redirect is not allowed by the update process.
    122   request->Cancel();
    123   OnResponseCompleted();
    124 }
    125 
    126 void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
    127     net::URLRequest *request) {
    128   DCHECK(request == request_);
    129   if (request->status().is_success() &&
    130       (request->GetResponseCode() / 100) == 2) {
    131 
    132     // See http://code.google.com/p/chromium/issues/detail?id=69594
    133     // We willfully violate the HTML5 spec at this point in order
    134     // to support the appcaching of cross-origin HTTPS resources.
    135     // We've opted for a milder constraint and allow caching unless
    136     // the resource has a "no-store" header. A spec change has been
    137     // requested on the whatwg list.
    138     // TODO(michaeln): Consider doing this for cross-origin HTTP resources too.
    139     if (url_.SchemeIsSecure() &&
    140         url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
    141       if (request->response_headers()->
    142               HasHeaderValue("cache-control", "no-store")) {
    143         request->Cancel();
    144         OnResponseCompleted();
    145         return;
    146       }
    147     }
    148 
    149     // Write response info to storage for URL fetches. Wait for async write
    150     // completion before reading any response data.
    151     if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
    152       response_writer_.reset(job_->CreateResponseWriter());
    153       scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
    154           new HttpResponseInfoIOBuffer(
    155               new net::HttpResponseInfo(request->response_info())));
    156       response_writer_->WriteInfo(
    157           io_buffer.get(),
    158           base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
    159     } else {
    160       ReadResponseData();
    161     }
    162   } else {
    163     OnResponseCompleted();
    164   }
    165 }
    166 
    167 void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
    168     net::URLRequest* request, int bytes_read) {
    169   DCHECK(request_ == request);
    170   bool data_consumed = true;
    171   if (request->status().is_success() && bytes_read > 0) {
    172     data_consumed = ConsumeResponseData(bytes_read);
    173     if (data_consumed) {
    174       bytes_read = 0;
    175       while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
    176         if (bytes_read > 0) {
    177           data_consumed = ConsumeResponseData(bytes_read);
    178           if (!data_consumed)
    179             break;  // wait for async data processing, then read more
    180         } else {
    181           break;
    182         }
    183       }
    184     }
    185   }
    186   if (data_consumed && !request->status().is_io_pending())
    187     OnResponseCompleted();
    188 }
    189 
    190 void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
    191     const net::HttpResponseHeaders* headers) {
    192   DCHECK(request_.get() && headers);
    193   net::HttpRequestHeaders extra_headers;
    194 
    195   // Add If-Modified-Since header if response info has Last-Modified header.
    196   const std::string last_modified = "Last-Modified";
    197   std::string last_modified_value;
    198   headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
    199   if (!last_modified_value.empty()) {
    200     extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
    201                             last_modified_value);
    202   }
    203 
    204   // Add If-None-Match header if response info has ETag header.
    205   const std::string etag = "ETag";
    206   std::string etag_value;
    207   headers->EnumerateHeader(NULL, etag, &etag_value);
    208   if (!etag_value.empty()) {
    209     extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
    210                             etag_value);
    211   }
    212   if (!extra_headers.IsEmpty())
    213     request_->SetExtraRequestHeaders(extra_headers);
    214 }
    215 
    216 void  AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
    217   if (result < 0) {
    218     request_->Cancel();
    219     OnResponseCompleted();
    220     return;
    221   }
    222   ReadResponseData();
    223 }
    224 
    225 void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
    226   InternalUpdateState state = job_->internal_state_;
    227   if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
    228     return;
    229   int bytes_read = 0;
    230   request_->Read(buffer_.get(), kBufferSize, &bytes_read);
    231   OnReadCompleted(request_.get(), bytes_read);
    232 }
    233 
    234 // Returns false if response data is processed asynchronously, in which
    235 // case ReadResponseData will be invoked when it is safe to continue
    236 // reading more response data from the request.
    237 bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
    238   DCHECK_GT(bytes_read, 0);
    239   switch (fetch_type_) {
    240     case MANIFEST_FETCH:
    241     case MANIFEST_REFETCH:
    242       manifest_data_.append(buffer_->data(), bytes_read);
    243       break;
    244     case URL_FETCH:
    245     case MASTER_ENTRY_FETCH:
    246       DCHECK(response_writer_.get());
    247       response_writer_->WriteData(
    248           buffer_.get(),
    249           bytes_read,
    250           base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
    251       return false;  // wait for async write completion to continue reading
    252     default:
    253       NOTREACHED();
    254   }
    255   return true;
    256 }
    257 
    258 void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
    259   // Retry for 503s where retry-after is 0.
    260   if (request_->status().is_success() &&
    261       request_->GetResponseCode() == 503 &&
    262       MaybeRetryRequest()) {
    263     return;
    264   }
    265 
    266   switch (fetch_type_) {
    267     case MANIFEST_FETCH:
    268       job_->HandleManifestFetchCompleted(this);
    269       break;
    270     case URL_FETCH:
    271       job_->HandleUrlFetchCompleted(this);
    272       break;
    273     case MASTER_ENTRY_FETCH:
    274       job_->HandleMasterEntryFetchCompleted(this);
    275       break;
    276     case MANIFEST_REFETCH:
    277       job_->HandleManifestRefetchCompleted(this);
    278       break;
    279     default:
    280       NOTREACHED();
    281   }
    282 
    283   delete this;
    284 }
    285 
    286 bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
    287   if (retry_503_attempts_ >= kMax503Retries ||
    288       !request_->response_headers()->HasHeaderValue("retry-after", "0")) {
    289     return false;
    290   }
    291   ++retry_503_attempts_;
    292   request_ = job_->service_->request_context()->CreateRequest(
    293       url_, net::DEFAULT_PRIORITY, this);
    294   Start();
    295   return true;
    296 }
    297 
    298 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service,
    299                                      AppCacheGroup* group)
    300     : service_(service),
    301       manifest_url_(group->manifest_url()),
    302       group_(group),
    303       update_type_(UNKNOWN_TYPE),
    304       internal_state_(FETCH_MANIFEST),
    305       master_entries_completed_(0),
    306       url_fetches_completed_(0),
    307       manifest_fetcher_(NULL),
    308       stored_state_(UNSTORED),
    309       storage_(service->storage()) {
    310     service_->AddObserver(this);
    311 }
    312 
    313 AppCacheUpdateJob::~AppCacheUpdateJob() {
    314   if (service_)
    315     service_->RemoveObserver(this);
    316   if (internal_state_ != COMPLETED)
    317     Cancel();
    318 
    319   DCHECK(!manifest_fetcher_);
    320   DCHECK(pending_url_fetches_.empty());
    321   DCHECK(!inprogress_cache_.get());
    322   DCHECK(pending_master_entries_.empty());
    323   DCHECK(master_entry_fetches_.empty());
    324 
    325   if (group_)
    326     group_->SetUpdateStatus(AppCacheGroup::IDLE);
    327 }
    328 
    329 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
    330                                     const GURL& new_master_resource) {
    331   DCHECK(group_->update_job() == this);
    332   DCHECK(!group_->is_obsolete());
    333 
    334   bool is_new_pending_master_entry = false;
    335   if (!new_master_resource.is_empty()) {
    336     DCHECK(new_master_resource == host->pending_master_entry_url());
    337     DCHECK(!new_master_resource.has_ref());
    338     DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
    339 
    340     // Cannot add more to this update if already terminating.
    341     if (IsTerminating()) {
    342       group_->QueueUpdate(host, new_master_resource);
    343       return;
    344     }
    345 
    346     std::pair<PendingMasters::iterator, bool> ret =
    347         pending_master_entries_.insert(
    348             PendingMasters::value_type(new_master_resource, PendingHosts()));
    349     is_new_pending_master_entry = ret.second;
    350     ret.first->second.push_back(host);
    351     host->AddObserver(this);
    352   }
    353 
    354   // Notify host (if any) if already checking or downloading.
    355   AppCacheGroup::UpdateStatus update_status = group_->update_status();
    356   if (update_status == AppCacheGroup::CHECKING ||
    357       update_status == AppCacheGroup::DOWNLOADING) {
    358     if (host) {
    359       NotifySingleHost(host, CHECKING_EVENT);
    360       if (update_status == AppCacheGroup::DOWNLOADING)
    361         NotifySingleHost(host, DOWNLOADING_EVENT);
    362 
    363       // Add to fetch list or an existing entry if already fetched.
    364       if (!new_master_resource.is_empty()) {
    365         AddMasterEntryToFetchList(host, new_master_resource,
    366                                   is_new_pending_master_entry);
    367       }
    368     }
    369     return;
    370   }
    371 
    372   // Begin update process for the group.
    373   group_->SetUpdateStatus(AppCacheGroup::CHECKING);
    374   if (group_->HasCache()) {
    375     update_type_ = UPGRADE_ATTEMPT;
    376     NotifyAllAssociatedHosts(CHECKING_EVENT);
    377   } else {
    378     update_type_ = CACHE_ATTEMPT;
    379     DCHECK(host);
    380     NotifySingleHost(host, CHECKING_EVENT);
    381   }
    382 
    383   if (!new_master_resource.is_empty()) {
    384     AddMasterEntryToFetchList(host, new_master_resource,
    385                               is_new_pending_master_entry);
    386   }
    387 
    388   FetchManifest(true);
    389 }
    390 
    391 AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
    392   AppCacheResponseWriter* writer =
    393       storage_->CreateResponseWriter(manifest_url_,
    394                                                 group_->group_id());
    395   stored_response_ids_.push_back(writer->response_id());
    396   return writer;
    397 }
    398 
    399 void AppCacheUpdateJob::HandleCacheFailure(const std::string& error_message) {
    400   // 6.9.4 cache failure steps 2-8.
    401   DCHECK(internal_state_ != CACHE_FAILURE);
    402   DCHECK(!error_message.empty());
    403   internal_state_ = CACHE_FAILURE;
    404   CancelAllUrlFetches();
    405   CancelAllMasterEntryFetches(error_message);
    406   NotifyAllError(error_message);
    407   DiscardInprogressCache();
    408   internal_state_ = COMPLETED;
    409   DeleteSoon();  // To unwind the stack prior to deletion.
    410 }
    411 
    412 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
    413   DCHECK(!manifest_fetcher_);
    414   manifest_fetcher_ = new URLFetcher(
    415      manifest_url_,
    416      is_first_fetch ? URLFetcher::MANIFEST_FETCH :
    417                       URLFetcher::MANIFEST_REFETCH,
    418      this);
    419 
    420   // Add any necessary Http headers before sending fetch request.
    421   if (is_first_fetch) {
    422     AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
    423         group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
    424     if (entry) {
    425       // Asynchronously load response info for manifest from newest cache.
    426       storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
    427                                  entry->response_id(), this);
    428     } else {
    429       manifest_fetcher_->Start();
    430     }
    431   } else {
    432     DCHECK(internal_state_ == REFETCH_MANIFEST);
    433     DCHECK(manifest_response_info_.get());
    434     manifest_fetcher_->set_existing_response_headers(
    435         manifest_response_info_->headers.get());
    436     manifest_fetcher_->Start();
    437   }
    438 }
    439 
    440 
    441 void AppCacheUpdateJob::HandleManifestFetchCompleted(
    442     URLFetcher* fetcher) {
    443   DCHECK_EQ(internal_state_, FETCH_MANIFEST);
    444   DCHECK_EQ(manifest_fetcher_, fetcher);
    445   manifest_fetcher_ = NULL;
    446 
    447   net::URLRequest* request = fetcher->request();
    448   int response_code = -1;
    449   bool is_valid_response_code = false;
    450   if (request->status().is_success()) {
    451     response_code = request->GetResponseCode();
    452     is_valid_response_code = (response_code / 100 == 2);
    453   }
    454 
    455   if (is_valid_response_code) {
    456     manifest_data_ = fetcher->manifest_data();
    457     manifest_response_info_.reset(
    458         new net::HttpResponseInfo(request->response_info()));
    459     if (update_type_ == UPGRADE_ATTEMPT)
    460       CheckIfManifestChanged();  // continues asynchronously
    461     else
    462       ContinueHandleManifestFetchCompleted(true);
    463   } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
    464     ContinueHandleManifestFetchCompleted(false);
    465   } else if ((response_code == 404 || response_code == 410) &&
    466              update_type_ == UPGRADE_ATTEMPT) {
    467     storage_->MakeGroupObsolete(group_, this);  // async
    468   } else {
    469     const char* kFormatString = "Manifest fetch failed (%d) %s";
    470     std::string message = base::StringPrintf(kFormatString, response_code,
    471                                              manifest_url_.spec().c_str());
    472     HandleCacheFailure(message);
    473   }
    474 }
    475 
    476 void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
    477                                             bool success) {
    478   DCHECK(master_entry_fetches_.empty());
    479   CancelAllMasterEntryFetches("The cache has been made obsolete, "
    480                               "the manifest file returned 404 or 410");
    481   if (success) {
    482     DCHECK(group->is_obsolete());
    483     NotifyAllAssociatedHosts(OBSOLETE_EVENT);
    484     internal_state_ = COMPLETED;
    485     MaybeCompleteUpdate();
    486   } else {
    487     // Treat failure to mark group obsolete as a cache failure.
    488     HandleCacheFailure("Failed to mark the cache as obsolete");
    489   }
    490 }
    491 
    492 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
    493   DCHECK(internal_state_ == FETCH_MANIFEST);
    494 
    495   if (!changed) {
    496     DCHECK(update_type_ == UPGRADE_ATTEMPT);
    497     internal_state_ = NO_UPDATE;
    498 
    499     // Wait for pending master entries to download.
    500     FetchMasterEntries();
    501     MaybeCompleteUpdate();  // if not done, run async 6.9.4 step 7 substeps
    502     return;
    503   }
    504 
    505   Manifest manifest;
    506   if (!ParseManifest(manifest_url_, manifest_data_.data(),
    507                      manifest_data_.length(), manifest)) {
    508     const char* kFormatString = "Failed to parse manifest %s";
    509     const std::string message = base::StringPrintf(kFormatString,
    510         manifest_url_.spec().c_str());
    511     HandleCacheFailure(message);
    512     VLOG(1) << message;
    513     return;
    514   }
    515 
    516   // Proceed with update process. Section 6.9.4 steps 8-20.
    517   internal_state_ = DOWNLOADING;
    518   inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
    519   BuildUrlFileList(manifest);
    520   inprogress_cache_->InitializeWithManifest(&manifest);
    521 
    522   // Associate all pending master hosts with the newly created cache.
    523   for (PendingMasters::iterator it = pending_master_entries_.begin();
    524        it != pending_master_entries_.end(); ++it) {
    525     PendingHosts& hosts = it->second;
    526     for (PendingHosts::iterator host_it = hosts.begin();
    527          host_it != hosts.end(); ++host_it) {
    528       (*host_it)
    529           ->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
    530     }
    531   }
    532 
    533   group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING);
    534   NotifyAllAssociatedHosts(DOWNLOADING_EVENT);
    535   FetchUrls();
    536   FetchMasterEntries();
    537   MaybeCompleteUpdate();  // if not done, continues when async fetches complete
    538 }
    539 
    540 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
    541   DCHECK(internal_state_ == DOWNLOADING);
    542 
    543   net::URLRequest* request = fetcher->request();
    544   const GURL& url = request->original_url();
    545   pending_url_fetches_.erase(url);
    546   NotifyAllProgress(url);
    547   ++url_fetches_completed_;
    548 
    549   int response_code = request->status().is_success()
    550       ? request->GetResponseCode() : -1;
    551   AppCacheEntry& entry = url_file_list_.find(url)->second;
    552 
    553   if (response_code / 100 == 2) {
    554     // Associate storage with the new entry.
    555     DCHECK(fetcher->response_writer());
    556     entry.set_response_id(fetcher->response_writer()->response_id());
    557     entry.set_response_size(fetcher->response_writer()->amount_written());
    558     if (!inprogress_cache_->AddOrModifyEntry(url, entry))
    559       duplicate_response_ids_.push_back(entry.response_id());
    560 
    561     // TODO(michaeln): Check for <html manifest=xxx>
    562     // See http://code.google.com/p/chromium/issues/detail?id=97930
    563     // if (entry.IsMaster() && !(entry.IsExplicit() || fallback || intercept))
    564     //   if (!manifestAttribute) skip it
    565 
    566     // Foreign entries will be detected during cache selection.
    567     // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML
    568     // file whose root element is an html element with a manifest attribute
    569     // whose value doesn't match the manifest url of the application cache
    570     // being processed, mark the entry as being foreign.
    571   } else {
    572     VLOG(1) << "Request status: " << request->status().status()
    573             << " error: " << request->status().error()
    574             << " response code: " << response_code;
    575     if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
    576       if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
    577         // Keep the existing response.
    578         entry.set_response_id(fetcher->existing_entry().response_id());
    579         entry.set_response_size(fetcher->existing_entry().response_size());
    580         inprogress_cache_->AddOrModifyEntry(url, entry);
    581       } else {
    582         const char* kFormatString = "Resource fetch failed (%d) %s";
    583         const std::string message = base::StringPrintf(kFormatString,
    584             response_code, url.spec().c_str());
    585         HandleCacheFailure(message);
    586         return;
    587       }
    588     } else if (response_code == 404 || response_code == 410) {
    589       // Entry is skipped.  They are dropped from the cache.
    590     } else if (update_type_ == UPGRADE_ATTEMPT &&
    591                fetcher->existing_entry().has_response_id()) {
    592       // Keep the existing response.
    593       // TODO(michaeln): Not sure this is a good idea. This is spec compliant
    594       // but the old resource may or may not be compatible with the new contents
    595       // of the cache. Impossible to know one way or the other.
    596       entry.set_response_id(fetcher->existing_entry().response_id());
    597       entry.set_response_size(fetcher->existing_entry().response_size());
    598       inprogress_cache_->AddOrModifyEntry(url, entry);
    599     }
    600   }
    601 
    602   // Fetch another URL now that one request has completed.
    603   DCHECK(internal_state_ != CACHE_FAILURE);
    604   FetchUrls();
    605   MaybeCompleteUpdate();
    606 }
    607 
    608 void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
    609     URLFetcher* fetcher) {
    610   DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
    611 
    612   // TODO(jennb): Handle downloads completing during cache failure when update
    613   // no longer fetches master entries directly. For now, we cancel all pending
    614   // master entry fetches when entering cache failure state so this will never
    615   // be called in CACHE_FAILURE state.
    616 
    617   net::URLRequest* request = fetcher->request();
    618   const GURL& url = request->original_url();
    619   master_entry_fetches_.erase(url);
    620   ++master_entries_completed_;
    621 
    622   int response_code = request->status().is_success()
    623       ? request->GetResponseCode() : -1;
    624 
    625   PendingMasters::iterator found = pending_master_entries_.find(url);
    626   DCHECK(found != pending_master_entries_.end());
    627   PendingHosts& hosts = found->second;
    628 
    629   // Section 6.9.4. No update case: step 7.3, else step 22.
    630   if (response_code / 100 == 2) {
    631     // Add fetched master entry to the appropriate cache.
    632     AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
    633                                               : group_->newest_complete_cache();
    634     DCHECK(fetcher->response_writer());
    635     AppCacheEntry master_entry(AppCacheEntry::MASTER,
    636                                fetcher->response_writer()->response_id(),
    637                                fetcher->response_writer()->amount_written());
    638     if (cache->AddOrModifyEntry(url, master_entry))
    639       added_master_entries_.push_back(url);
    640     else
    641       duplicate_response_ids_.push_back(master_entry.response_id());
    642 
    643     // In no-update case, associate host with the newest cache.
    644     if (!inprogress_cache_.get()) {
    645       // TODO(michaeln): defer until the updated cache has been stored
    646       DCHECK(cache == group_->newest_complete_cache());
    647       for (PendingHosts::iterator host_it = hosts.begin();
    648            host_it != hosts.end(); ++host_it) {
    649         (*host_it)->AssociateCompleteCache(cache);
    650       }
    651     }
    652   } else {
    653     HostNotifier host_notifier;
    654     for (PendingHosts::iterator host_it = hosts.begin();
    655          host_it != hosts.end(); ++host_it) {
    656       AppCacheHost* host = *host_it;
    657       host_notifier.AddHost(host);
    658 
    659       // In downloading case, disassociate host from inprogress cache.
    660       if (inprogress_cache_.get())
    661         host->AssociateNoCache(GURL());
    662 
    663       host->RemoveObserver(this);
    664     }
    665     hosts.clear();
    666 
    667     const char* kFormatString = "Master entry fetch failed (%d) %s";
    668     const std::string message = base::StringPrintf(kFormatString,
    669         response_code, request->url().spec().c_str());
    670     host_notifier.SendErrorNotifications(message);
    671 
    672     // In downloading case, update result is different if all master entries
    673     // failed vs. only some failing.
    674     if (inprogress_cache_.get()) {
    675       // Only count successful downloads to know if all master entries failed.
    676       pending_master_entries_.erase(found);
    677       --master_entries_completed_;
    678 
    679       // Section 6.9.4, step 22.3.
    680       if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
    681         HandleCacheFailure(message);
    682         return;
    683       }
    684     }
    685   }
    686 
    687   DCHECK(internal_state_ != CACHE_FAILURE);
    688   FetchMasterEntries();
    689   MaybeCompleteUpdate();
    690 }
    691 
    692 void AppCacheUpdateJob::HandleManifestRefetchCompleted(
    693     URLFetcher* fetcher) {
    694   DCHECK(internal_state_ == REFETCH_MANIFEST);
    695   DCHECK(manifest_fetcher_ == fetcher);
    696   manifest_fetcher_ = NULL;
    697 
    698   net::URLRequest* request = fetcher->request();
    699   int response_code = request->status().is_success()
    700       ? request->GetResponseCode() : -1;
    701   if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
    702     // Only need to store response in storage if manifest is not already
    703     // an entry in the cache.
    704     AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
    705     if (entry) {
    706       entry->add_types(AppCacheEntry::MANIFEST);
    707       StoreGroupAndCache();
    708     } else {
    709       manifest_response_writer_.reset(CreateResponseWriter());
    710       scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
    711           new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
    712       manifest_response_writer_->WriteInfo(
    713           io_buffer.get(),
    714           base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
    715                      base::Unretained(this)));
    716     }
    717   } else {
    718     VLOG(1) << "Request status: " << request->status().status()
    719             << " error: " << request->status().error()
    720             << " response code: " << response_code;
    721     ScheduleUpdateRetry(kRerunDelayMs);
    722     HandleCacheFailure("Manifest changed during update, scheduling retry");
    723   }
    724 }
    725 
    726 void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) {
    727   if (result > 0) {
    728     scoped_refptr<net::StringIOBuffer> io_buffer(
    729         new net::StringIOBuffer(manifest_data_));
    730     manifest_response_writer_->WriteData(
    731         io_buffer.get(),
    732         manifest_data_.length(),
    733         base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
    734                    base::Unretained(this)));
    735   } else {
    736     HandleCacheFailure("Failed to write the manifest headers to storage");
    737   }
    738 }
    739 
    740 void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
    741   if (result > 0) {
    742     AppCacheEntry entry(AppCacheEntry::MANIFEST,
    743         manifest_response_writer_->response_id(),
    744         manifest_response_writer_->amount_written());
    745     if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
    746       duplicate_response_ids_.push_back(entry.response_id());
    747     StoreGroupAndCache();
    748   } else {
    749     HandleCacheFailure("Failed to write the manifest data to storage");
    750   }
    751 }
    752 
    753 void AppCacheUpdateJob::StoreGroupAndCache() {
    754   DCHECK(stored_state_ == UNSTORED);
    755   stored_state_ = STORING;
    756   scoped_refptr<AppCache> newest_cache;
    757   if (inprogress_cache_.get())
    758     newest_cache.swap(inprogress_cache_);
    759   else
    760     newest_cache = group_->newest_complete_cache();
    761   newest_cache->set_update_time(base::Time::Now());
    762 
    763   // TODO(michaeln): dcheck is fishing for clues to crbug/95101
    764   DCHECK_EQ(manifest_url_, group_->manifest_url());
    765   storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
    766 }
    767 
    768 void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
    769                                                     AppCache* newest_cache,
    770                                                     bool success,
    771                                                     bool would_exceed_quota) {
    772   DCHECK(stored_state_ == STORING);
    773   if (success) {
    774     stored_state_ = STORED;
    775     MaybeCompleteUpdate();  // will definitely complete
    776   } else {
    777     // Restore inprogress_cache_ to get the proper events delivered
    778     // and the proper cleanup to occur.
    779     if (newest_cache != group->newest_complete_cache())
    780       inprogress_cache_ = newest_cache;
    781 
    782     std::string message("Failed to commit new cache to storage");
    783     if (would_exceed_quota)
    784       message.append(", would exceed quota");
    785     HandleCacheFailure(message);
    786   }
    787 }
    788 
    789 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
    790                                          EventID event_id) {
    791   std::vector<int> ids(1, host->host_id());
    792   host->frontend()->OnEventRaised(ids, event_id);
    793 }
    794 
    795 void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) {
    796   HostNotifier host_notifier;
    797   AddAllAssociatedHostsToNotifier(&host_notifier);
    798   host_notifier.SendNotifications(event_id);
    799 }
    800 
    801 void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
    802   HostNotifier host_notifier;
    803   AddAllAssociatedHostsToNotifier(&host_notifier);
    804   host_notifier.SendProgressNotifications(
    805       url, url_file_list_.size(), url_fetches_completed_);
    806 }
    807 
    808 void AppCacheUpdateJob::NotifyAllFinalProgress() {
    809   DCHECK(url_file_list_.size() == url_fetches_completed_);
    810   NotifyAllProgress(GURL());
    811 }
    812 
    813 void AppCacheUpdateJob::NotifyAllError(const std::string& error_message) {
    814   HostNotifier host_notifier;
    815   AddAllAssociatedHostsToNotifier(&host_notifier);
    816   host_notifier.SendErrorNotifications(error_message);
    817 }
    818 
    819 void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
    820     HostNotifier* host_notifier) {
    821   // Collect hosts so we only send one notification per frontend.
    822   // A host can only be associated with a single cache so no need to worry
    823   // about duplicate hosts being added to the notifier.
    824   if (inprogress_cache_.get()) {
    825     DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
    826     host_notifier->AddHosts(inprogress_cache_->associated_hosts());
    827   }
    828 
    829   AppCacheGroup::Caches old_caches = group_->old_caches();
    830   for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
    831        it != old_caches.end(); ++it) {
    832     host_notifier->AddHosts((*it)->associated_hosts());
    833   }
    834 
    835   AppCache* newest_cache = group_->newest_complete_cache();
    836   if (newest_cache)
    837     host_notifier->AddHosts(newest_cache->associated_hosts());
    838 }
    839 
    840 void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
    841   // The host is about to be deleted; remove from our collection.
    842   PendingMasters::iterator found =
    843       pending_master_entries_.find(host->pending_master_entry_url());
    844   DCHECK(found != pending_master_entries_.end());
    845   PendingHosts& hosts = found->second;
    846   PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
    847   DCHECK(it != hosts.end());
    848   hosts.erase(it);
    849 }
    850 
    851 void AppCacheUpdateJob::OnServiceReinitialized(
    852     AppCacheStorageReference* old_storage_ref) {
    853   // We continue to use the disabled instance, but arrange for its
    854   // deletion when its no longer needed.
    855   if (old_storage_ref->storage() == storage_)
    856     disabled_storage_reference_ = old_storage_ref;
    857 }
    858 
    859 void AppCacheUpdateJob::CheckIfManifestChanged() {
    860   DCHECK(update_type_ == UPGRADE_ATTEMPT);
    861   AppCacheEntry* entry = NULL;
    862   if (group_->newest_complete_cache())
    863     entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
    864   if (!entry) {
    865     // TODO(michaeln): This is just a bandaid to avoid a crash.
    866     // http://code.google.com/p/chromium/issues/detail?id=95101
    867     if (service_->storage() == storage_) {
    868       // Use a local variable because service_ is reset in HandleCacheFailure.
    869       AppCacheService* service = service_;
    870       HandleCacheFailure("Manifest entry not found in existing cache");
    871       AppCacheHistograms::AddMissingManifestEntrySample();
    872       service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
    873     }
    874     return;
    875   }
    876 
    877   // Load manifest data from storage to compare against fetched manifest.
    878   manifest_response_reader_.reset(
    879       storage_->CreateResponseReader(manifest_url_,
    880                                      group_->group_id(),
    881                                      entry->response_id()));
    882   read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
    883   manifest_response_reader_->ReadData(
    884       read_manifest_buffer_.get(),
    885       kBufferSize,
    886       base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
    887                  base::Unretained(this)));  // async read
    888 }
    889 
    890 void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
    891   if (result > 0) {
    892     loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
    893     manifest_response_reader_->ReadData(
    894         read_manifest_buffer_.get(),
    895         kBufferSize,
    896         base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
    897                    base::Unretained(this)));  // read more
    898   } else {
    899     read_manifest_buffer_ = NULL;
    900     manifest_response_reader_.reset();
    901     ContinueHandleManifestFetchCompleted(
    902         result < 0 || manifest_data_ != loaded_manifest_data_);
    903   }
    904 }
    905 
    906 void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) {
    907   for (base::hash_set<std::string>::const_iterator it =
    908            manifest.explicit_urls.begin();
    909        it != manifest.explicit_urls.end(); ++it) {
    910     AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
    911   }
    912 
    913   const std::vector<Namespace>& intercepts =
    914       manifest.intercept_namespaces;
    915   for (std::vector<Namespace>::const_iterator it = intercepts.begin();
    916        it != intercepts.end(); ++it) {
    917     int flags = AppCacheEntry::INTERCEPT;
    918     if (it->is_executable)
    919       flags |= AppCacheEntry::EXECUTABLE;
    920     AddUrlToFileList(it->target_url, flags);
    921   }
    922 
    923   const std::vector<Namespace>& fallbacks =
    924       manifest.fallback_namespaces;
    925   for (std::vector<Namespace>::const_iterator it = fallbacks.begin();
    926        it != fallbacks.end(); ++it) {
    927      AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
    928   }
    929 
    930   // Add all master entries from newest complete cache.
    931   if (update_type_ == UPGRADE_ATTEMPT) {
    932     const AppCache::EntryMap& entries =
    933         group_->newest_complete_cache()->entries();
    934     for (AppCache::EntryMap::const_iterator it = entries.begin();
    935          it != entries.end(); ++it) {
    936       const AppCacheEntry& entry = it->second;
    937       if (entry.IsMaster())
    938         AddUrlToFileList(it->first, AppCacheEntry::MASTER);
    939     }
    940   }
    941 }
    942 
    943 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
    944   std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
    945       AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
    946 
    947   if (ret.second)
    948     urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
    949   else
    950     ret.first->second.add_types(type);  // URL already exists. Merge types.
    951 }
    952 
    953 void AppCacheUpdateJob::FetchUrls() {
    954   DCHECK(internal_state_ == DOWNLOADING);
    955 
    956   // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3.
    957   // Fetch up to the concurrent limit. Other fetches will be triggered as each
    958   // each fetch completes.
    959   while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
    960          !urls_to_fetch_.empty()) {
    961     UrlToFetch url_to_fetch = urls_to_fetch_.front();
    962     urls_to_fetch_.pop_front();
    963 
    964     AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
    965     DCHECK(it != url_file_list_.end());
    966     AppCacheEntry& entry = it->second;
    967     if (ShouldSkipUrlFetch(entry)) {
    968       NotifyAllProgress(url_to_fetch.url);
    969       ++url_fetches_completed_;
    970     } else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
    971       NotifyAllProgress(url_to_fetch.url);
    972       ++url_fetches_completed_;  // saved a URL request
    973     } else if (!url_to_fetch.storage_checked &&
    974                MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
    975       // Continues asynchronously after data is loaded from newest cache.
    976     } else {
    977       URLFetcher* fetcher = new URLFetcher(
    978           url_to_fetch.url, URLFetcher::URL_FETCH, this);
    979       if (url_to_fetch.existing_response_info.get()) {
    980         DCHECK(group_->newest_complete_cache());
    981         AppCacheEntry* existing_entry =
    982             group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
    983         DCHECK(existing_entry);
    984         DCHECK(existing_entry->response_id() ==
    985                url_to_fetch.existing_response_info->response_id());
    986         fetcher->set_existing_response_headers(
    987             url_to_fetch.existing_response_info->http_response_info()->headers
    988                 .get());
    989         fetcher->set_existing_entry(*existing_entry);
    990       }
    991       fetcher->Start();
    992       pending_url_fetches_.insert(
    993           PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
    994     }
    995   }
    996 }
    997 
    998 void AppCacheUpdateJob::CancelAllUrlFetches() {
    999   // Cancel any pending URL requests.
   1000   for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
   1001        it != pending_url_fetches_.end(); ++it) {
   1002     delete it->second;
   1003   }
   1004 
   1005   url_fetches_completed_ +=
   1006       pending_url_fetches_.size() + urls_to_fetch_.size();
   1007   pending_url_fetches_.clear();
   1008   urls_to_fetch_.clear();
   1009 }
   1010 
   1011 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
   1012   // 6.6.4 Step 17
   1013   // If the resource URL being processed was flagged as neither an
   1014   // "explicit entry" nor or a "fallback entry", then the user agent
   1015   // may skip this URL.
   1016   if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
   1017     return false;
   1018 
   1019   // TODO(jennb): decide if entry should be skipped to expire it from cache
   1020   return false;
   1021 }
   1022 
   1023 bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
   1024                                             int entry_type) {
   1025   DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
   1026   AppCacheEntry* existing =
   1027       inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
   1028                               : group_->newest_complete_cache()->GetEntry(url);
   1029   if (existing) {
   1030     existing->add_types(entry_type);
   1031     return true;
   1032   }
   1033   return false;
   1034 }
   1035 
   1036 void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
   1037                                                   const GURL& url,
   1038                                                   bool is_new) {
   1039   DCHECK(!IsTerminating());
   1040 
   1041   if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
   1042     AppCache* cache;
   1043     if (inprogress_cache_.get()) {
   1044       // always associate
   1045       host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
   1046       cache = inprogress_cache_.get();
   1047     } else {
   1048       cache = group_->newest_complete_cache();
   1049     }
   1050 
   1051     // Update existing entry if it has already been fetched.
   1052     AppCacheEntry* entry = cache->GetEntry(url);
   1053     if (entry) {
   1054       entry->add_types(AppCacheEntry::MASTER);
   1055       if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
   1056         // only associate if have entry
   1057         host->AssociateCompleteCache(cache);
   1058       }
   1059       if (is_new)
   1060         ++master_entries_completed_;  // pretend fetching completed
   1061       return;
   1062     }
   1063   }
   1064 
   1065   // Add to fetch list if not already fetching.
   1066   if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
   1067     master_entries_to_fetch_.insert(url);
   1068     if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
   1069       FetchMasterEntries();
   1070   }
   1071 }
   1072 
   1073 void AppCacheUpdateJob::FetchMasterEntries() {
   1074   DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
   1075 
   1076   // Fetch each master entry in the list, up to the concurrent limit.
   1077   // Additional fetches will be triggered as each fetch completes.
   1078   while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
   1079          !master_entries_to_fetch_.empty()) {
   1080     const GURL& url = *master_entries_to_fetch_.begin();
   1081 
   1082     if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
   1083       ++master_entries_completed_;  // saved a URL request
   1084 
   1085       // In no update case, associate hosts to newest cache in group
   1086       // now that master entry has been "successfully downloaded".
   1087       if (internal_state_ == NO_UPDATE) {
   1088         // TODO(michaeln): defer until the updated cache has been stored.
   1089         DCHECK(!inprogress_cache_.get());
   1090         AppCache* cache = group_->newest_complete_cache();
   1091         PendingMasters::iterator found = pending_master_entries_.find(url);
   1092         DCHECK(found != pending_master_entries_.end());
   1093         PendingHosts& hosts = found->second;
   1094         for (PendingHosts::iterator host_it = hosts.begin();
   1095              host_it != hosts.end(); ++host_it) {
   1096           (*host_it)->AssociateCompleteCache(cache);
   1097         }
   1098       }
   1099     } else {
   1100       URLFetcher* fetcher = new URLFetcher(
   1101           url, URLFetcher::MASTER_ENTRY_FETCH, this);
   1102       fetcher->Start();
   1103       master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
   1104     }
   1105 
   1106     master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
   1107   }
   1108 }
   1109 
   1110 void AppCacheUpdateJob::CancelAllMasterEntryFetches(
   1111     const std::string& error_message) {
   1112   // For now, cancel all in-progress fetches for master entries and pretend
   1113   // all master entries fetches have completed.
   1114   // TODO(jennb): Delete this when update no longer fetches master entries
   1115   // directly.
   1116 
   1117   // Cancel all in-progress fetches.
   1118   for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
   1119        it != master_entry_fetches_.end(); ++it) {
   1120     delete it->second;
   1121     master_entries_to_fetch_.insert(it->first);  // back in unfetched list
   1122   }
   1123   master_entry_fetches_.clear();
   1124 
   1125   master_entries_completed_ += master_entries_to_fetch_.size();
   1126 
   1127   // Cache failure steps, step 2.
   1128   // Pretend all master entries that have not yet been fetched have completed
   1129   // downloading. Unassociate hosts from any appcache and send ERROR event.
   1130   HostNotifier host_notifier;
   1131   while (!master_entries_to_fetch_.empty()) {
   1132     const GURL& url = *master_entries_to_fetch_.begin();
   1133     PendingMasters::iterator found = pending_master_entries_.find(url);
   1134     DCHECK(found != pending_master_entries_.end());
   1135     PendingHosts& hosts = found->second;
   1136     for (PendingHosts::iterator host_it = hosts.begin();
   1137          host_it != hosts.end(); ++host_it) {
   1138       AppCacheHost* host = *host_it;
   1139       host->AssociateNoCache(GURL());
   1140       host_notifier.AddHost(host);
   1141       host->RemoveObserver(this);
   1142     }
   1143     hosts.clear();
   1144 
   1145     master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
   1146   }
   1147   host_notifier.SendErrorNotifications(error_message);
   1148 }
   1149 
   1150 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
   1151                                                  AppCacheEntry& entry) {
   1152   if (update_type_ != UPGRADE_ATTEMPT)
   1153     return false;
   1154 
   1155   AppCache* newest = group_->newest_complete_cache();
   1156   AppCacheEntry* copy_me = newest->GetEntry(url);
   1157   if (!copy_me || !copy_me->has_response_id())
   1158     return false;
   1159 
   1160   // Load HTTP headers for entry from newest cache.
   1161   loading_responses_.insert(
   1162       LoadingResponses::value_type(copy_me->response_id(), url));
   1163   storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
   1164                              copy_me->response_id(),
   1165                              this);
   1166   // Async: wait for OnResponseInfoLoaded to complete.
   1167   return true;
   1168 }
   1169 
   1170 void AppCacheUpdateJob::OnResponseInfoLoaded(
   1171     AppCacheResponseInfo* response_info, int64 response_id) {
   1172   const net::HttpResponseInfo* http_info = response_info ?
   1173       response_info->http_response_info() : NULL;
   1174 
   1175   // Needed response info for a manifest fetch request.
   1176   if (internal_state_ == FETCH_MANIFEST) {
   1177     if (http_info)
   1178       manifest_fetcher_->set_existing_response_headers(
   1179           http_info->headers.get());
   1180     manifest_fetcher_->Start();
   1181     return;
   1182   }
   1183 
   1184   LoadingResponses::iterator found = loading_responses_.find(response_id);
   1185   DCHECK(found != loading_responses_.end());
   1186   const GURL& url = found->second;
   1187 
   1188   if (!http_info) {
   1189     LoadFromNewestCacheFailed(url, NULL);  // no response found
   1190   } else {
   1191     // Check if response can be re-used according to HTTP caching semantics.
   1192     // Responses with a "vary" header get treated as expired.
   1193     const std::string name = "vary";
   1194     std::string value;
   1195     void* iter = NULL;
   1196     if (!http_info->headers.get() ||
   1197         http_info->headers->RequiresValidation(http_info->request_time,
   1198                                                http_info->response_time,
   1199                                                base::Time::Now()) ||
   1200         http_info->headers->EnumerateHeader(&iter, name, &value)) {
   1201       LoadFromNewestCacheFailed(url, response_info);
   1202     } else {
   1203       DCHECK(group_->newest_complete_cache());
   1204       AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
   1205       DCHECK(copy_me);
   1206       DCHECK(copy_me->response_id() == response_id);
   1207 
   1208       AppCache::EntryMap::iterator it = url_file_list_.find(url);
   1209       DCHECK(it != url_file_list_.end());
   1210       AppCacheEntry& entry = it->second;
   1211       entry.set_response_id(response_id);
   1212       entry.set_response_size(copy_me->response_size());
   1213       inprogress_cache_->AddOrModifyEntry(url, entry);
   1214       NotifyAllProgress(url);
   1215       ++url_fetches_completed_;
   1216     }
   1217   }
   1218   loading_responses_.erase(found);
   1219 
   1220   MaybeCompleteUpdate();
   1221 }
   1222 
   1223 void AppCacheUpdateJob::LoadFromNewestCacheFailed(
   1224     const GURL& url, AppCacheResponseInfo* response_info) {
   1225   if (internal_state_ == CACHE_FAILURE)
   1226     return;
   1227 
   1228   // Re-insert url at front of fetch list. Indicate storage has been checked.
   1229   urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
   1230   FetchUrls();
   1231 }
   1232 
   1233 void AppCacheUpdateJob::MaybeCompleteUpdate() {
   1234   DCHECK(internal_state_ != CACHE_FAILURE);
   1235 
   1236   // Must wait for any pending master entries or url fetches to complete.
   1237   if (master_entries_completed_ != pending_master_entries_.size() ||
   1238       url_fetches_completed_ != url_file_list_.size()) {
   1239     DCHECK(internal_state_ != COMPLETED);
   1240     return;
   1241   }
   1242 
   1243   switch (internal_state_) {
   1244     case NO_UPDATE:
   1245       if (master_entries_completed_ > 0) {
   1246         switch (stored_state_) {
   1247           case UNSTORED:
   1248             StoreGroupAndCache();
   1249             return;
   1250           case STORING:
   1251             return;
   1252           case STORED:
   1253             break;
   1254         }
   1255       }
   1256       // 6.9.4 steps 7.3-7.7.
   1257       NotifyAllAssociatedHosts(NO_UPDATE_EVENT);
   1258       DiscardDuplicateResponses();
   1259       internal_state_ = COMPLETED;
   1260       break;
   1261     case DOWNLOADING:
   1262       internal_state_ = REFETCH_MANIFEST;
   1263       FetchManifest(false);
   1264       break;
   1265     case REFETCH_MANIFEST:
   1266       DCHECK(stored_state_ == STORED);
   1267       NotifyAllFinalProgress();
   1268       if (update_type_ == CACHE_ATTEMPT)
   1269         NotifyAllAssociatedHosts(CACHED_EVENT);
   1270       else
   1271         NotifyAllAssociatedHosts(UPDATE_READY_EVENT);
   1272       DiscardDuplicateResponses();
   1273       internal_state_ = COMPLETED;
   1274       break;
   1275     case CACHE_FAILURE:
   1276       NOTREACHED();  // See HandleCacheFailure
   1277       break;
   1278     default:
   1279       break;
   1280   }
   1281 
   1282   // Let the stack unwind before deletion to make it less risky as this
   1283   // method is called from multiple places in this file.
   1284   if (internal_state_ == COMPLETED)
   1285     DeleteSoon();
   1286 }
   1287 
   1288 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
   1289   // TODO(jennb): post a delayed task with the "same parameters" as this job
   1290   // to retry the update at a later time. Need group, URLs of pending master
   1291   // entries and their hosts.
   1292 }
   1293 
   1294 void AppCacheUpdateJob::Cancel() {
   1295   internal_state_ = CANCELLED;
   1296 
   1297   if (manifest_fetcher_) {
   1298     delete manifest_fetcher_;
   1299     manifest_fetcher_ = NULL;
   1300   }
   1301 
   1302   for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
   1303        it != pending_url_fetches_.end(); ++it) {
   1304     delete it->second;
   1305   }
   1306   pending_url_fetches_.clear();
   1307 
   1308   for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
   1309        it != master_entry_fetches_.end(); ++it) {
   1310     delete it->second;
   1311   }
   1312   master_entry_fetches_.clear();
   1313 
   1314   ClearPendingMasterEntries();
   1315   DiscardInprogressCache();
   1316 
   1317   // Delete response writer to avoid any callbacks.
   1318   if (manifest_response_writer_)
   1319     manifest_response_writer_.reset();
   1320 
   1321   storage_->CancelDelegateCallbacks(this);
   1322 }
   1323 
   1324 void AppCacheUpdateJob::ClearPendingMasterEntries() {
   1325   for (PendingMasters::iterator it = pending_master_entries_.begin();
   1326        it != pending_master_entries_.end(); ++it) {
   1327     PendingHosts& hosts = it->second;
   1328     for (PendingHosts::iterator host_it = hosts.begin();
   1329          host_it != hosts.end(); ++host_it) {
   1330       (*host_it)->RemoveObserver(this);
   1331     }
   1332   }
   1333 
   1334   pending_master_entries_.clear();
   1335 }
   1336 
   1337 void AppCacheUpdateJob::DiscardInprogressCache() {
   1338   storage_->DoomResponses(manifest_url_, stored_response_ids_);
   1339 
   1340   if (!inprogress_cache_.get()) {
   1341     // We have to undo the changes we made, if any, to the existing cache.
   1342     for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
   1343          iter != added_master_entries_.end(); ++iter) {
   1344       DCHECK(group_->newest_complete_cache());
   1345       group_->newest_complete_cache()->RemoveEntry(*iter);
   1346     }
   1347     return;
   1348   }
   1349 
   1350   AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
   1351   while (!hosts.empty())
   1352     (*hosts.begin())->AssociateNoCache(GURL());
   1353 
   1354   inprogress_cache_ = NULL;
   1355 }
   1356 
   1357 void AppCacheUpdateJob::DiscardDuplicateResponses() {
   1358   storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
   1359 }
   1360 
   1361 void AppCacheUpdateJob::DeleteSoon() {
   1362   ClearPendingMasterEntries();
   1363   manifest_response_writer_.reset();
   1364   storage_->CancelDelegateCallbacks(this);
   1365   service_->RemoveObserver(this);
   1366   service_ = NULL;
   1367 
   1368   // Break the connection with the group so the group cannot call delete
   1369   // on this object after we've posted a task to delete ourselves.
   1370   group_->SetUpdateStatus(AppCacheGroup::IDLE);
   1371   group_ = NULL;
   1372 
   1373   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
   1374 }
   1375 
   1376 }  // namespace appcache
   1377