1// Copyright (c) 2011 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_host.h" 6 7#include "base/logging.h" 8#include "base/strings/string_util.h" 9#include "base/strings/stringprintf.h" 10#include "net/url_request/url_request.h" 11#include "webkit/browser/appcache/appcache.h" 12#include "webkit/browser/appcache/appcache_backend_impl.h" 13#include "webkit/browser/appcache/appcache_policy.h" 14#include "webkit/browser/appcache/appcache_request_handler.h" 15#include "webkit/browser/quota/quota_manager_proxy.h" 16 17namespace appcache { 18 19namespace { 20 21void FillCacheInfo(const AppCache* cache, 22 const GURL& manifest_url, 23 AppCacheStatus status, AppCacheInfo* info) { 24 info->manifest_url = manifest_url; 25 info->status = status; 26 27 if (!cache) 28 return; 29 30 info->cache_id = cache->cache_id(); 31 32 if (!cache->is_complete()) 33 return; 34 35 DCHECK(cache->owning_group()); 36 info->is_complete = true; 37 info->group_id = cache->owning_group()->group_id(); 38 info->last_update_time = cache->update_time(); 39 info->creation_time = cache->owning_group()->creation_time(); 40 info->size = cache->cache_size(); 41} 42 43} // Anonymous namespace 44 45AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend, 46 AppCacheServiceImpl* service) 47 : host_id_(host_id), 48 spawning_host_id_(kAppCacheNoHostId), spawning_process_id_(0), 49 parent_host_id_(kAppCacheNoHostId), parent_process_id_(0), 50 pending_main_resource_cache_id_(kAppCacheNoCacheId), 51 pending_selected_cache_id_(kAppCacheNoCacheId), 52 frontend_(frontend), service_(service), 53 storage_(service->storage()), 54 pending_callback_param_(NULL), 55 main_resource_was_namespace_entry_(false), 56 main_resource_blocked_(false), 57 associated_cache_info_pending_(false) { 58 service_->AddObserver(this); 59} 60 61AppCacheHost::~AppCacheHost() { 62 service_->RemoveObserver(this); 63 FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this)); 64 if (associated_cache_.get()) 65 associated_cache_->UnassociateHost(this); 66 if (group_being_updated_.get()) 67 group_being_updated_->RemoveUpdateObserver(this); 68 storage()->CancelDelegateCallbacks(this); 69 if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) 70 service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_); 71} 72 73void AppCacheHost::AddObserver(Observer* observer) { 74 observers_.AddObserver(observer); 75} 76 77void AppCacheHost::RemoveObserver(Observer* observer) { 78 observers_.RemoveObserver(observer); 79} 80 81void AppCacheHost::SelectCache(const GURL& document_url, 82 const int64 cache_document_was_loaded_from, 83 const GURL& manifest_url) { 84 DCHECK(pending_start_update_callback_.is_null() && 85 pending_swap_cache_callback_.is_null() && 86 pending_get_status_callback_.is_null() && 87 !is_selection_pending()); 88 89 origin_in_use_ = document_url.GetOrigin(); 90 if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) 91 service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_); 92 93 if (main_resource_blocked_) 94 frontend_->OnContentBlocked(host_id_, 95 blocked_manifest_url_); 96 97 // 6.9.6 The application cache selection algorithm. 98 // The algorithm is started here and continues in FinishCacheSelection, 99 // after cache or group loading is complete. 100 // Note: Foreign entries are detected on the client side and 101 // MarkAsForeignEntry is called in that case, so that detection 102 // step is skipped here. See WebApplicationCacheHostImpl.cc 103 104 if (cache_document_was_loaded_from != kAppCacheNoCacheId) { 105 LoadSelectedCache(cache_document_was_loaded_from); 106 return; 107 } 108 109 if (!manifest_url.is_empty() && 110 (manifest_url.GetOrigin() == document_url.GetOrigin())) { 111 DCHECK(!first_party_url_.is_empty()); 112 AppCachePolicy* policy = service()->appcache_policy(); 113 if (policy && 114 !policy->CanCreateAppCache(manifest_url, first_party_url_)) { 115 FinishCacheSelection(NULL, NULL); 116 std::vector<int> host_ids(1, host_id_); 117 frontend_->OnEventRaised(host_ids, APPCACHE_CHECKING_EVENT); 118 frontend_->OnErrorEventRaised( 119 host_ids, 120 AppCacheErrorDetails( 121 "Cache creation was blocked by the content policy", 122 APPCACHE_POLICY_ERROR, 123 GURL(), 124 0, 125 false /*is_cross_origin*/)); 126 frontend_->OnContentBlocked(host_id_, manifest_url); 127 return; 128 } 129 130 // Note: The client detects if the document was not loaded using HTTP GET 131 // and invokes SelectCache without a manifest url, so that detection step 132 // is also skipped here. See WebApplicationCacheHostImpl.cc 133 set_preferred_manifest_url(manifest_url); 134 new_master_entry_url_ = document_url; 135 LoadOrCreateGroup(manifest_url); 136 return; 137 } 138 139 // TODO(michaeln): If there was a manifest URL, the user agent may report 140 // to the user that it was ignored, to aid in application development. 141 FinishCacheSelection(NULL, NULL); 142} 143 144void AppCacheHost::SelectCacheForWorker(int parent_process_id, 145 int parent_host_id) { 146 DCHECK(pending_start_update_callback_.is_null() && 147 pending_swap_cache_callback_.is_null() && 148 pending_get_status_callback_.is_null() && 149 !is_selection_pending()); 150 151 parent_process_id_ = parent_process_id; 152 parent_host_id_ = parent_host_id; 153 FinishCacheSelection(NULL, NULL); 154} 155 156void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) { 157 DCHECK(pending_start_update_callback_.is_null() && 158 pending_swap_cache_callback_.is_null() && 159 pending_get_status_callback_.is_null() && 160 !is_selection_pending()); 161 162 if (appcache_id != kAppCacheNoCacheId) { 163 LoadSelectedCache(appcache_id); 164 return; 165 } 166 FinishCacheSelection(NULL, NULL); 167} 168 169// TODO(michaeln): change method name to MarkEntryAsForeign for consistency 170void AppCacheHost::MarkAsForeignEntry(const GURL& document_url, 171 int64 cache_document_was_loaded_from) { 172 // The document url is not the resource url in the fallback case. 173 storage()->MarkEntryAsForeign( 174 main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url, 175 cache_document_was_loaded_from); 176 SelectCache(document_url, kAppCacheNoCacheId, GURL()); 177} 178 179void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback, 180 void* callback_param) { 181 DCHECK(pending_start_update_callback_.is_null() && 182 pending_swap_cache_callback_.is_null() && 183 pending_get_status_callback_.is_null()); 184 185 pending_get_status_callback_ = callback; 186 pending_callback_param_ = callback_param; 187 if (is_selection_pending()) 188 return; 189 190 DoPendingGetStatus(); 191} 192 193void AppCacheHost::DoPendingGetStatus() { 194 DCHECK_EQ(false, pending_get_status_callback_.is_null()); 195 196 pending_get_status_callback_.Run(GetStatus(), pending_callback_param_); 197 pending_get_status_callback_.Reset(); 198 pending_callback_param_ = NULL; 199} 200 201void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback, 202 void* callback_param) { 203 DCHECK(pending_start_update_callback_.is_null() && 204 pending_swap_cache_callback_.is_null() && 205 pending_get_status_callback_.is_null()); 206 207 pending_start_update_callback_ = callback; 208 pending_callback_param_ = callback_param; 209 if (is_selection_pending()) 210 return; 211 212 DoPendingStartUpdate(); 213} 214 215void AppCacheHost::DoPendingStartUpdate() { 216 DCHECK_EQ(false, pending_start_update_callback_.is_null()); 217 218 // 6.9.8 Application cache API 219 bool success = false; 220 if (associated_cache_.get() && associated_cache_->owning_group()) { 221 AppCacheGroup* group = associated_cache_->owning_group(); 222 if (!group->is_obsolete() && !group->is_being_deleted()) { 223 success = true; 224 group->StartUpdate(); 225 } 226 } 227 228 pending_start_update_callback_.Run(success, pending_callback_param_); 229 pending_start_update_callback_.Reset(); 230 pending_callback_param_ = NULL; 231} 232 233void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback, 234 void* callback_param) { 235 DCHECK(pending_start_update_callback_.is_null() && 236 pending_swap_cache_callback_.is_null() && 237 pending_get_status_callback_.is_null()); 238 239 pending_swap_cache_callback_ = callback; 240 pending_callback_param_ = callback_param; 241 if (is_selection_pending()) 242 return; 243 244 DoPendingSwapCache(); 245} 246 247void AppCacheHost::DoPendingSwapCache() { 248 DCHECK_EQ(false, pending_swap_cache_callback_.is_null()); 249 250 // 6.9.8 Application cache API 251 bool success = false; 252 if (associated_cache_.get() && associated_cache_->owning_group()) { 253 if (associated_cache_->owning_group()->is_obsolete()) { 254 success = true; 255 AssociateNoCache(GURL()); 256 } else if (swappable_cache_.get()) { 257 DCHECK(swappable_cache_.get() == 258 swappable_cache_->owning_group()->newest_complete_cache()); 259 success = true; 260 AssociateCompleteCache(swappable_cache_.get()); 261 } 262 } 263 264 pending_swap_cache_callback_.Run(success, pending_callback_param_); 265 pending_swap_cache_callback_.Reset(); 266 pending_callback_param_ = NULL; 267} 268 269void AppCacheHost::SetSpawningHostId( 270 int spawning_process_id, int spawning_host_id) { 271 spawning_process_id_ = spawning_process_id; 272 spawning_host_id_ = spawning_host_id; 273} 274 275const AppCacheHost* AppCacheHost::GetSpawningHost() const { 276 AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_); 277 return backend ? backend->GetHost(spawning_host_id_) : NULL; 278} 279 280AppCacheHost* AppCacheHost::GetParentAppCacheHost() const { 281 DCHECK(is_for_dedicated_worker()); 282 AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_); 283 return backend ? backend->GetHost(parent_host_id_) : NULL; 284} 285 286AppCacheRequestHandler* AppCacheHost::CreateRequestHandler( 287 net::URLRequest* request, 288 ResourceType::Type resource_type) { 289 if (is_for_dedicated_worker()) { 290 AppCacheHost* parent_host = GetParentAppCacheHost(); 291 if (parent_host) 292 return parent_host->CreateRequestHandler(request, resource_type); 293 return NULL; 294 } 295 296 if (AppCacheRequestHandler::IsMainResourceType(resource_type)) { 297 // Store the first party origin so that it can be used later in SelectCache 298 // for checking whether the creation of the appcache is allowed. 299 first_party_url_ = request->first_party_for_cookies(); 300 return new AppCacheRequestHandler(this, resource_type); 301 } 302 303 if ((associated_cache() && associated_cache()->is_complete()) || 304 is_selection_pending()) { 305 return new AppCacheRequestHandler(this, resource_type); 306 } 307 return NULL; 308} 309 310void AppCacheHost::GetResourceList( 311 AppCacheResourceInfoVector* resource_infos) { 312 if (associated_cache_.get() && associated_cache_->is_complete()) 313 associated_cache_->ToResourceInfoVector(resource_infos); 314} 315 316AppCacheStatus AppCacheHost::GetStatus() { 317 // 6.9.8 Application cache API 318 AppCache* cache = associated_cache(); 319 if (!cache) 320 return APPCACHE_STATUS_UNCACHED; 321 322 // A cache without an owning group represents the cache being constructed 323 // during the application cache update process. 324 if (!cache->owning_group()) 325 return APPCACHE_STATUS_DOWNLOADING; 326 327 if (cache->owning_group()->is_obsolete()) 328 return APPCACHE_STATUS_OBSOLETE; 329 if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING) 330 return APPCACHE_STATUS_CHECKING; 331 if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING) 332 return APPCACHE_STATUS_DOWNLOADING; 333 if (swappable_cache_.get()) 334 return APPCACHE_STATUS_UPDATE_READY; 335 return APPCACHE_STATUS_IDLE; 336} 337 338void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) { 339 DCHECK(manifest_url.is_valid()); 340 pending_selected_manifest_url_ = manifest_url; 341 storage()->LoadOrCreateGroup(manifest_url, this); 342} 343 344void AppCacheHost::OnGroupLoaded(AppCacheGroup* group, 345 const GURL& manifest_url) { 346 DCHECK(manifest_url == pending_selected_manifest_url_); 347 pending_selected_manifest_url_ = GURL(); 348 FinishCacheSelection(NULL, group); 349} 350 351void AppCacheHost::LoadSelectedCache(int64 cache_id) { 352 DCHECK(cache_id != kAppCacheNoCacheId); 353 pending_selected_cache_id_ = cache_id; 354 storage()->LoadCache(cache_id, this); 355} 356 357void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) { 358 if (cache_id == pending_main_resource_cache_id_) { 359 pending_main_resource_cache_id_ = kAppCacheNoCacheId; 360 main_resource_cache_ = cache; 361 } else if (cache_id == pending_selected_cache_id_) { 362 pending_selected_cache_id_ = kAppCacheNoCacheId; 363 FinishCacheSelection(cache, NULL); 364 } 365} 366 367void AppCacheHost::FinishCacheSelection( 368 AppCache *cache, AppCacheGroup* group) { 369 DCHECK(!associated_cache()); 370 371 // 6.9.6 The application cache selection algorithm 372 if (cache) { 373 // If document was loaded from an application cache, Associate document 374 // with the application cache from which it was loaded. Invoke the 375 // application cache update process for that cache and with the browsing 376 // context being navigated. 377 DCHECK(cache->owning_group()); 378 DCHECK(new_master_entry_url_.is_empty()); 379 DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_); 380 AppCacheGroup* owing_group = cache->owning_group(); 381 const char* kFormatString = 382 "Document was loaded from Application Cache with manifest %s"; 383 frontend_->OnLogMessage( 384 host_id_, APPCACHE_LOG_INFO, 385 base::StringPrintf( 386 kFormatString, owing_group->manifest_url().spec().c_str())); 387 AssociateCompleteCache(cache); 388 if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) { 389 owing_group->StartUpdateWithHost(this); 390 ObserveGroupBeingUpdated(owing_group); 391 } 392 } else if (group && !group->is_being_deleted()) { 393 // If document was loaded using HTTP GET or equivalent, and, there is a 394 // manifest URL, and manifest URL has the same origin as document. 395 // Invoke the application cache update process for manifest URL, with 396 // the browsing context being navigated, and with document and the 397 // resource from which document was loaded as the new master resourse. 398 DCHECK(!group->is_obsolete()); 399 DCHECK(new_master_entry_url_.is_valid()); 400 DCHECK_EQ(group->manifest_url(), preferred_manifest_url_); 401 const char* kFormatString = group->HasCache() ? 402 "Adding master entry to Application Cache with manifest %s" : 403 "Creating Application Cache with manifest %s"; 404 frontend_->OnLogMessage( 405 host_id_, APPCACHE_LOG_INFO, 406 base::StringPrintf(kFormatString, 407 group->manifest_url().spec().c_str())); 408 // The UpdateJob may produce one for us later. 409 AssociateNoCache(preferred_manifest_url_); 410 group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_); 411 ObserveGroupBeingUpdated(group); 412 } else { 413 // Otherwise, the Document is not associated with any application cache. 414 new_master_entry_url_ = GURL(); 415 AssociateNoCache(GURL()); 416 } 417 418 // Respond to pending callbacks now that we have a selection. 419 if (!pending_get_status_callback_.is_null()) 420 DoPendingGetStatus(); 421 else if (!pending_start_update_callback_.is_null()) 422 DoPendingStartUpdate(); 423 else if (!pending_swap_cache_callback_.is_null()) 424 DoPendingSwapCache(); 425 426 FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this)); 427} 428 429void AppCacheHost::OnServiceReinitialized( 430 AppCacheStorageReference* old_storage_ref) { 431 // We continue to use the disabled instance, but arrange for its 432 // deletion when its no longer needed. 433 if (old_storage_ref->storage() == storage()) 434 disabled_storage_reference_ = old_storage_ref; 435} 436 437void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) { 438 DCHECK(!group_being_updated_.get()); 439 group_being_updated_ = group; 440 newest_cache_of_group_being_updated_ = group->newest_complete_cache(); 441 group->AddUpdateObserver(this); 442} 443 444void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) { 445 DCHECK_EQ(group, group_being_updated_); 446 group->RemoveUpdateObserver(this); 447 448 // Add a reference to the newest complete cache. 449 SetSwappableCache(group); 450 451 group_being_updated_ = NULL; 452 newest_cache_of_group_being_updated_ = NULL; 453 454 if (associated_cache_info_pending_ && associated_cache_.get() && 455 associated_cache_->is_complete()) { 456 AppCacheInfo info; 457 FillCacheInfo( 458 associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info); 459 associated_cache_info_pending_ = false; 460 frontend_->OnCacheSelected(host_id_, info); 461 } 462} 463 464void AppCacheHost::SetSwappableCache(AppCacheGroup* group) { 465 if (!group) { 466 swappable_cache_ = NULL; 467 } else { 468 AppCache* new_cache = group->newest_complete_cache(); 469 if (new_cache != associated_cache_.get()) 470 swappable_cache_ = new_cache; 471 else 472 swappable_cache_ = NULL; 473 } 474} 475 476void AppCacheHost::LoadMainResourceCache(int64 cache_id) { 477 DCHECK(cache_id != kAppCacheNoCacheId); 478 if (pending_main_resource_cache_id_ == cache_id || 479 (main_resource_cache_.get() && 480 main_resource_cache_->cache_id() == cache_id)) { 481 return; 482 } 483 pending_main_resource_cache_id_ = cache_id; 484 storage()->LoadCache(cache_id, this); 485} 486 487void AppCacheHost::NotifyMainResourceIsNamespaceEntry( 488 const GURL& namespace_entry_url) { 489 main_resource_was_namespace_entry_ = true; 490 namespace_entry_url_ = namespace_entry_url; 491} 492 493void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) { 494 main_resource_blocked_ = true; 495 blocked_manifest_url_ = manifest_url; 496} 497 498void AppCacheHost::PrepareForTransfer() { 499 // This can only happen prior to the document having been loaded. 500 DCHECK(!associated_cache()); 501 DCHECK(!is_selection_pending()); 502 DCHECK(!group_being_updated_); 503 host_id_ = kAppCacheNoHostId; 504 frontend_ = NULL; 505} 506 507void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) { 508 host_id_ = host_id; 509 frontend_ = frontend; 510} 511 512void AppCacheHost::AssociateNoCache(const GURL& manifest_url) { 513 // manifest url can be empty. 514 AssociateCacheHelper(NULL, manifest_url); 515} 516 517void AppCacheHost::AssociateIncompleteCache(AppCache* cache, 518 const GURL& manifest_url) { 519 DCHECK(cache && !cache->is_complete()); 520 DCHECK(!manifest_url.is_empty()); 521 AssociateCacheHelper(cache, manifest_url); 522} 523 524void AppCacheHost::AssociateCompleteCache(AppCache* cache) { 525 DCHECK(cache && cache->is_complete()); 526 AssociateCacheHelper(cache, cache->owning_group()->manifest_url()); 527} 528 529void AppCacheHost::AssociateCacheHelper(AppCache* cache, 530 const GURL& manifest_url) { 531 if (associated_cache_.get()) { 532 associated_cache_->UnassociateHost(this); 533 } 534 535 associated_cache_ = cache; 536 SetSwappableCache(cache ? cache->owning_group() : NULL); 537 associated_cache_info_pending_ = cache && !cache->is_complete(); 538 AppCacheInfo info; 539 if (cache) 540 cache->AssociateHost(this); 541 542 FillCacheInfo(cache, manifest_url, GetStatus(), &info); 543 frontend_->OnCacheSelected(host_id_, info); 544} 545 546} // namespace appcache 547