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