|  | // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "webkit/appcache/appcache_host.h" | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/string_util.h" | 
|  | #include "base/stringprintf.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "webkit/appcache/appcache.h" | 
|  | #include "webkit/appcache/appcache_backend_impl.h" | 
|  | #include "webkit/appcache/appcache_policy.h" | 
|  | #include "webkit/appcache/appcache_request_handler.h" | 
|  | #include "webkit/quota/quota_manager.h" | 
|  |  | 
|  | namespace appcache { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | void FillCacheInfo(const AppCache* cache, | 
|  | const GURL& manifest_url, | 
|  | Status status, AppCacheInfo* info) { | 
|  | info->manifest_url = manifest_url; | 
|  | info->status = status; | 
|  |  | 
|  | if (!cache) | 
|  | return; | 
|  |  | 
|  | info->cache_id = cache->cache_id(); | 
|  |  | 
|  | if (!cache->is_complete()) | 
|  | return; | 
|  |  | 
|  | DCHECK(cache->owning_group()); | 
|  | info->is_complete = true; | 
|  | info->group_id = cache->owning_group()->group_id(); | 
|  | info->last_update_time = cache->update_time(); | 
|  | info->creation_time = cache->owning_group()->creation_time(); | 
|  | info->size = cache->cache_size(); | 
|  | } | 
|  |  | 
|  | }  // Anonymous namespace | 
|  |  | 
|  | AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend, | 
|  | AppCacheService* service) | 
|  | : host_id_(host_id), | 
|  | spawning_host_id_(kNoHostId), spawning_process_id_(0), | 
|  | parent_host_id_(kNoHostId), parent_process_id_(0), | 
|  | pending_main_resource_cache_id_(kNoCacheId), | 
|  | pending_selected_cache_id_(kNoCacheId), | 
|  | frontend_(frontend), service_(service), | 
|  | pending_callback_param_(NULL), | 
|  | main_resource_was_namespace_entry_(false), | 
|  | main_resource_blocked_(false), | 
|  | associated_cache_info_pending_(false) { | 
|  | } | 
|  |  | 
|  | AppCacheHost::~AppCacheHost() { | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this)); | 
|  | if (associated_cache_.get()) | 
|  | associated_cache_->UnassociateHost(this); | 
|  | if (group_being_updated_.get()) | 
|  | group_being_updated_->RemoveUpdateObserver(this); | 
|  | service_->storage()->CancelDelegateCallbacks(this); | 
|  | if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) | 
|  | service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::AddObserver(Observer* observer) { | 
|  | observers_.AddObserver(observer); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::RemoveObserver(Observer* observer) { | 
|  | observers_.RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SelectCache(const GURL& document_url, | 
|  | const int64 cache_document_was_loaded_from, | 
|  | const GURL& manifest_url) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null() && | 
|  | !is_selection_pending()); | 
|  |  | 
|  | origin_in_use_ = document_url.GetOrigin(); | 
|  | if (service()->quota_manager_proxy() && !origin_in_use_.is_empty()) | 
|  | service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_); | 
|  |  | 
|  | if (main_resource_blocked_) | 
|  | frontend_->OnContentBlocked(host_id_, | 
|  | blocked_manifest_url_); | 
|  |  | 
|  | // 6.9.6 The application cache selection algorithm. | 
|  | // The algorithm is started here and continues in FinishCacheSelection, | 
|  | // after cache or group loading is complete. | 
|  | // Note: Foreign entries are detected on the client side and | 
|  | // MarkAsForeignEntry is called in that case, so that detection | 
|  | // step is skipped here. See WebApplicationCacheHostImpl.cc | 
|  |  | 
|  | if (cache_document_was_loaded_from != kNoCacheId) { | 
|  | LoadSelectedCache(cache_document_was_loaded_from); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!manifest_url.is_empty() && | 
|  | (manifest_url.GetOrigin() == document_url.GetOrigin())) { | 
|  | DCHECK(!first_party_url_.is_empty()); | 
|  | AppCachePolicy* policy = service()->appcache_policy(); | 
|  | if (policy && | 
|  | !policy->CanCreateAppCache(manifest_url, first_party_url_)) { | 
|  | FinishCacheSelection(NULL, NULL); | 
|  | std::vector<int> host_ids(1, host_id_); | 
|  | frontend_->OnEventRaised(host_ids, CHECKING_EVENT); | 
|  | frontend_->OnErrorEventRaised( | 
|  | host_ids, "Cache creation was blocked by the content policy"); | 
|  | frontend_->OnContentBlocked(host_id_, manifest_url); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Note: The client detects if the document was not loaded using HTTP GET | 
|  | // and invokes SelectCache without a manifest url, so that detection step | 
|  | // is also skipped here. See WebApplicationCacheHostImpl.cc | 
|  | set_preferred_manifest_url(manifest_url); | 
|  | new_master_entry_url_ = document_url; | 
|  | LoadOrCreateGroup(manifest_url); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(michaeln): If there was a manifest URL, the user agent may report | 
|  | // to the user that it was ignored, to aid in application development. | 
|  | FinishCacheSelection(NULL, NULL); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SelectCacheForWorker(int parent_process_id, | 
|  | int parent_host_id) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null() && | 
|  | !is_selection_pending()); | 
|  |  | 
|  | parent_process_id_ = parent_process_id; | 
|  | parent_host_id_ = parent_host_id; | 
|  | FinishCacheSelection(NULL, NULL); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null() && | 
|  | !is_selection_pending()); | 
|  |  | 
|  | if (appcache_id != kNoCacheId) { | 
|  | LoadSelectedCache(appcache_id); | 
|  | return; | 
|  | } | 
|  | FinishCacheSelection(NULL, NULL); | 
|  | } | 
|  |  | 
|  | // TODO(michaeln): change method name to MarkEntryAsForeign for consistency | 
|  | void AppCacheHost::MarkAsForeignEntry(const GURL& document_url, | 
|  | int64 cache_document_was_loaded_from) { | 
|  | // The document url is not the resource url in the fallback case. | 
|  | service_->storage()->MarkEntryAsForeign( | 
|  | main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url, | 
|  | cache_document_was_loaded_from); | 
|  | SelectCache(document_url, kNoCacheId, GURL()); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback, | 
|  | void* callback_param) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null()); | 
|  |  | 
|  | pending_get_status_callback_ = callback; | 
|  | pending_callback_param_ = callback_param; | 
|  | if (is_selection_pending()) | 
|  | return; | 
|  |  | 
|  | DoPendingGetStatus(); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::DoPendingGetStatus() { | 
|  | DCHECK_EQ(false, pending_get_status_callback_.is_null()); | 
|  |  | 
|  | pending_get_status_callback_.Run(GetStatus(), pending_callback_param_); | 
|  | pending_get_status_callback_.Reset(); | 
|  | pending_callback_param_ = NULL; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback, | 
|  | void* callback_param) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null()); | 
|  |  | 
|  | pending_start_update_callback_ = callback; | 
|  | pending_callback_param_ = callback_param; | 
|  | if (is_selection_pending()) | 
|  | return; | 
|  |  | 
|  | DoPendingStartUpdate(); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::DoPendingStartUpdate() { | 
|  | DCHECK_EQ(false, pending_start_update_callback_.is_null()); | 
|  |  | 
|  | // 6.9.8 Application cache API | 
|  | bool success = false; | 
|  | if (associated_cache_ && associated_cache_->owning_group()) { | 
|  | AppCacheGroup* group = associated_cache_->owning_group(); | 
|  | if (!group->is_obsolete() && !group->is_being_deleted()) { | 
|  | success = true; | 
|  | group->StartUpdate(); | 
|  | } | 
|  | } | 
|  |  | 
|  | pending_start_update_callback_.Run(success, pending_callback_param_); | 
|  | pending_start_update_callback_.Reset(); | 
|  | pending_callback_param_ = NULL; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback, | 
|  | void* callback_param) { | 
|  | DCHECK(pending_start_update_callback_.is_null() && | 
|  | pending_swap_cache_callback_.is_null() && | 
|  | pending_get_status_callback_.is_null()); | 
|  |  | 
|  | pending_swap_cache_callback_ = callback; | 
|  | pending_callback_param_ = callback_param; | 
|  | if (is_selection_pending()) | 
|  | return; | 
|  |  | 
|  | DoPendingSwapCache(); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::DoPendingSwapCache() { | 
|  | DCHECK_EQ(false, pending_swap_cache_callback_.is_null()); | 
|  |  | 
|  | // 6.9.8 Application cache API | 
|  | bool success = false; | 
|  | if (associated_cache_ && associated_cache_->owning_group()) { | 
|  | if (associated_cache_->owning_group()->is_obsolete()) { | 
|  | success = true; | 
|  | AssociateNoCache(GURL()); | 
|  | } else if (swappable_cache_) { | 
|  | DCHECK(swappable_cache_.get() == | 
|  | swappable_cache_->owning_group()->newest_complete_cache()); | 
|  | success = true; | 
|  | AssociateCompleteCache(swappable_cache_); | 
|  | } | 
|  | } | 
|  |  | 
|  | pending_swap_cache_callback_.Run(success, pending_callback_param_); | 
|  | pending_swap_cache_callback_.Reset(); | 
|  | pending_callback_param_ = NULL; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SetSpawningHostId( | 
|  | int spawning_process_id, int spawning_host_id) { | 
|  | spawning_process_id_ = spawning_process_id; | 
|  | spawning_host_id_ = spawning_host_id; | 
|  | } | 
|  |  | 
|  | const AppCacheHost* AppCacheHost::GetSpawningHost() const { | 
|  | AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_); | 
|  | return backend ? backend->GetHost(spawning_host_id_) : NULL; | 
|  | } | 
|  |  | 
|  | AppCacheHost* AppCacheHost::GetParentAppCacheHost() const { | 
|  | DCHECK(is_for_dedicated_worker()); | 
|  | AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_); | 
|  | return backend ? backend->GetHost(parent_host_id_) : NULL; | 
|  | } | 
|  |  | 
|  | AppCacheRequestHandler* AppCacheHost::CreateRequestHandler( | 
|  | net::URLRequest* request, | 
|  | ResourceType::Type resource_type) { | 
|  | if (is_for_dedicated_worker()) { | 
|  | AppCacheHost* parent_host = GetParentAppCacheHost(); | 
|  | if (parent_host) | 
|  | return parent_host->CreateRequestHandler(request, resource_type); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (AppCacheRequestHandler::IsMainResourceType(resource_type)) { | 
|  | // Store the first party origin so that it can be used later in SelectCache | 
|  | // for checking whether the creation of the appcache is allowed. | 
|  | first_party_url_ = request->first_party_for_cookies(); | 
|  | return new AppCacheRequestHandler(this, resource_type); | 
|  | } | 
|  |  | 
|  | if ((associated_cache() && associated_cache()->is_complete()) || | 
|  | is_selection_pending()) { | 
|  | return new AppCacheRequestHandler(this, resource_type); | 
|  | } | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::GetResourceList( | 
|  | AppCacheResourceInfoVector* resource_infos) { | 
|  | if (associated_cache_.get() && associated_cache_->is_complete()) | 
|  | associated_cache_->ToResourceInfoVector(resource_infos); | 
|  | } | 
|  |  | 
|  | Status AppCacheHost::GetStatus() { | 
|  | // 6.9.8 Application cache API | 
|  | AppCache* cache = associated_cache(); | 
|  | if (!cache) | 
|  | return UNCACHED; | 
|  |  | 
|  | // A cache without an owning group represents the cache being constructed | 
|  | // during the application cache update process. | 
|  | if (!cache->owning_group()) | 
|  | return DOWNLOADING; | 
|  |  | 
|  | if (cache->owning_group()->is_obsolete()) | 
|  | return OBSOLETE; | 
|  | if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING) | 
|  | return CHECKING; | 
|  | if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING) | 
|  | return DOWNLOADING; | 
|  | if (swappable_cache_) | 
|  | return UPDATE_READY; | 
|  | return IDLE; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) { | 
|  | DCHECK(manifest_url.is_valid()); | 
|  | pending_selected_manifest_url_ = manifest_url; | 
|  | service_->storage()->LoadOrCreateGroup(manifest_url, this); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::OnGroupLoaded(AppCacheGroup* group, | 
|  | const GURL& manifest_url) { | 
|  | DCHECK(manifest_url == pending_selected_manifest_url_); | 
|  | pending_selected_manifest_url_ = GURL(); | 
|  | FinishCacheSelection(NULL, group); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::LoadSelectedCache(int64 cache_id) { | 
|  | DCHECK(cache_id != kNoCacheId); | 
|  | pending_selected_cache_id_ = cache_id; | 
|  | service_->storage()->LoadCache(cache_id, this); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) { | 
|  | if (cache_id == pending_main_resource_cache_id_) { | 
|  | pending_main_resource_cache_id_ = kNoCacheId; | 
|  | main_resource_cache_ = cache; | 
|  | } else if (cache_id == pending_selected_cache_id_) { | 
|  | pending_selected_cache_id_ = kNoCacheId; | 
|  | FinishCacheSelection(cache, NULL); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppCacheHost::FinishCacheSelection( | 
|  | AppCache *cache, AppCacheGroup* group) { | 
|  | DCHECK(!associated_cache()); | 
|  |  | 
|  | // 6.9.6 The application cache selection algorithm | 
|  | if (cache) { | 
|  | // If document was loaded from an application cache, Associate document | 
|  | // with the application cache from which it was loaded. Invoke the | 
|  | // application cache update process for that cache and with the browsing | 
|  | // context being navigated. | 
|  | DCHECK(cache->owning_group()); | 
|  | DCHECK(new_master_entry_url_.is_empty()); | 
|  | DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_); | 
|  | AppCacheGroup* owing_group = cache->owning_group(); | 
|  | const char* kFormatString = | 
|  | "Document was loaded from Application Cache with manifest %s"; | 
|  | frontend_->OnLogMessage( | 
|  | host_id_, LOG_INFO, | 
|  | base::StringPrintf( | 
|  | kFormatString, owing_group->manifest_url().spec().c_str())); | 
|  | AssociateCompleteCache(cache); | 
|  | if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) { | 
|  | owing_group->StartUpdateWithHost(this); | 
|  | ObserveGroupBeingUpdated(owing_group); | 
|  | } | 
|  | } else if (group && !group->is_being_deleted()) { | 
|  | // If document was loaded using HTTP GET or equivalent, and, there is a | 
|  | // manifest URL, and manifest URL has the same origin as document. | 
|  | // Invoke the application cache update process for manifest URL, with | 
|  | // the browsing context being navigated, and with document and the | 
|  | // resource from which document was loaded as the new master resourse. | 
|  | DCHECK(!group->is_obsolete()); | 
|  | DCHECK(new_master_entry_url_.is_valid()); | 
|  | DCHECK_EQ(group->manifest_url(), preferred_manifest_url_); | 
|  | const char* kFormatString = group->HasCache() ? | 
|  | "Adding master entry to Application Cache with manifest %s" : | 
|  | "Creating Application Cache with manifest %s"; | 
|  | frontend_->OnLogMessage( | 
|  | host_id_, LOG_INFO, | 
|  | base::StringPrintf(kFormatString, | 
|  | group->manifest_url().spec().c_str())); | 
|  | // The UpdateJob may produce one for us later. | 
|  | AssociateNoCache(preferred_manifest_url_); | 
|  | group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_); | 
|  | ObserveGroupBeingUpdated(group); | 
|  | } else { | 
|  | // Otherwise, the Document is not associated with any application cache. | 
|  | new_master_entry_url_ = GURL(); | 
|  | AssociateNoCache(GURL()); | 
|  | } | 
|  |  | 
|  | // Respond to pending callbacks now that we have a selection. | 
|  | if (!pending_get_status_callback_.is_null()) | 
|  | DoPendingGetStatus(); | 
|  | else if (!pending_start_update_callback_.is_null()) | 
|  | DoPendingStartUpdate(); | 
|  | else if (!pending_swap_cache_callback_.is_null()) | 
|  | DoPendingSwapCache(); | 
|  |  | 
|  | FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this)); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) { | 
|  | DCHECK(!group_being_updated_); | 
|  | group_being_updated_ = group; | 
|  | newest_cache_of_group_being_updated_ = group->newest_complete_cache(); | 
|  | group->AddUpdateObserver(this); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) { | 
|  | DCHECK_EQ(group, group_being_updated_); | 
|  | group->RemoveUpdateObserver(this); | 
|  |  | 
|  | // Add a reference to the newest complete cache. | 
|  | SetSwappableCache(group); | 
|  |  | 
|  | group_being_updated_ = NULL; | 
|  | newest_cache_of_group_being_updated_ = NULL; | 
|  |  | 
|  | if (associated_cache_info_pending_ && associated_cache_.get() && | 
|  | associated_cache_->is_complete()) { | 
|  | AppCacheInfo info; | 
|  | FillCacheInfo( | 
|  | associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info); | 
|  | associated_cache_info_pending_ = false; | 
|  | frontend_->OnCacheSelected(host_id_, info); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppCacheHost::SetSwappableCache(AppCacheGroup* group) { | 
|  | if (!group) { | 
|  | swappable_cache_ = NULL; | 
|  | } else { | 
|  | AppCache* new_cache = group->newest_complete_cache(); | 
|  | if (new_cache != associated_cache_) | 
|  | swappable_cache_ = new_cache; | 
|  | else | 
|  | swappable_cache_ = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | void AppCacheHost::LoadMainResourceCache(int64 cache_id) { | 
|  | DCHECK(cache_id != kNoCacheId); | 
|  | if (pending_main_resource_cache_id_ == cache_id || | 
|  | (main_resource_cache_ && main_resource_cache_->cache_id() == cache_id)) { | 
|  | return; | 
|  | } | 
|  | pending_main_resource_cache_id_ = cache_id; | 
|  | service_->storage()->LoadCache(cache_id, this); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::NotifyMainResourceIsNamespaceEntry( | 
|  | const GURL& namespace_entry_url) { | 
|  | main_resource_was_namespace_entry_ = true; | 
|  | namespace_entry_url_ = namespace_entry_url; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) { | 
|  | main_resource_blocked_ = true; | 
|  | blocked_manifest_url_ = manifest_url; | 
|  | } | 
|  |  | 
|  | void AppCacheHost::AssociateNoCache(const GURL& manifest_url) { | 
|  | // manifest url can be empty. | 
|  | AssociateCacheHelper(NULL, manifest_url); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::AssociateIncompleteCache(AppCache* cache, | 
|  | const GURL& manifest_url) { | 
|  | DCHECK(cache && !cache->is_complete()); | 
|  | DCHECK(!manifest_url.is_empty()); | 
|  | AssociateCacheHelper(cache, manifest_url); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::AssociateCompleteCache(AppCache* cache) { | 
|  | DCHECK(cache && cache->is_complete()); | 
|  | AssociateCacheHelper(cache, cache->owning_group()->manifest_url()); | 
|  | } | 
|  |  | 
|  | void AppCacheHost::AssociateCacheHelper(AppCache* cache, | 
|  | const GURL& manifest_url) { | 
|  | if (associated_cache_.get()) { | 
|  | associated_cache_->UnassociateHost(this); | 
|  | } | 
|  |  | 
|  | associated_cache_ = cache; | 
|  | SetSwappableCache(cache ? cache->owning_group() : NULL); | 
|  | associated_cache_info_pending_ = cache && !cache->is_complete(); | 
|  | AppCacheInfo info; | 
|  | if (cache) | 
|  | cache->AssociateHost(this); | 
|  |  | 
|  | FillCacheInfo(cache, manifest_url, GetStatus(), &info); | 
|  | frontend_->OnCacheSelected(host_id_, info); | 
|  | } | 
|  |  | 
|  | }  // namespace appcache |