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