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