| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |