blob: 1f1b5da8549839758939a4be50d973abe4e0170a [file] [log] [blame]
// Copyright (c) 2010 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 "webkit/appcache/appcache.h"
#include "webkit/appcache/appcache_backend_impl.h"
#include "webkit/appcache/appcache_request_handler.h"
namespace appcache {
namespace {
void FillCacheInfo(
const AppCache* cache, Status status, AppCacheInfo* info) {
DCHECK(cache);
info->cache_id = cache->cache_id();
info->status = status;
info->is_complete = cache->is_complete();
if (info->is_complete) {
info->manifest_url = cache->owning_group()->manifest_url();
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), parent_host_id_(kNoHostId), parent_process_id_(0),
pending_main_resource_cache_id_(kNoCacheId),
pending_selected_cache_id_(kNoCacheId),
frontend_(frontend), service_(service),
pending_get_status_callback_(NULL), pending_start_update_callback_(NULL),
pending_swap_cache_callback_(NULL), pending_callback_param_(NULL),
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);
}
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_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_);
if (main_resource_blocked_)
frontend_->OnContentBlocked(host_id_,
blocked_manifest_url_);
// First we handle an unusual case of SelectCache being called a second
// time. Generally this shouldn't happen, but with bad content I think
// this can occur... <html manifest=foo> <html manifest=bar></html></html>
// We handle this by killing whatever loading we have initiated, and by
// unassociating any hosts we currently have associated... and starting
// anew with the inputs to this SelectCache call.
// TODO(michaeln): at some point determine what behavior the algorithms
// described in the HTML5 draft produce and have our impl produce those
// results (or suggest changes to the algorihtms described in the spec
// if the resulting behavior is just too insane).
if (is_selection_pending()) {
service_->storage()->CancelDelegateCallbacks(this);
pending_selected_manifest_url_ = GURL();
pending_selected_cache_id_ = kNoCacheId;
} else if (associated_cache()) {
AssociateCache(NULL);
}
new_master_entry_url_ = GURL();
// 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())) {
// 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
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_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_ &&
!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_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_ &&
!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) {
service_->storage()->MarkEntryAsForeign(
document_url, cache_document_was_loaded_from);
SelectCache(document_url, kNoCacheId, GURL());
}
void AppCacheHost::GetStatusWithCallback(GetStatusCallback* callback,
void* callback_param) {
DCHECK(!pending_start_update_callback_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_);
pending_get_status_callback_ = callback;
pending_callback_param_ = callback_param;
if (is_selection_pending())
return;
DoPendingGetStatus();
}
void AppCacheHost::DoPendingGetStatus() {
DCHECK(pending_get_status_callback_);
pending_get_status_callback_->Run(
GetStatus(), pending_callback_param_);
pending_get_status_callback_ = NULL;
pending_callback_param_ = NULL;
}
void AppCacheHost::StartUpdateWithCallback(StartUpdateCallback* callback,
void* callback_param) {
DCHECK(!pending_start_update_callback_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_);
pending_start_update_callback_ = callback;
pending_callback_param_ = callback_param;
if (is_selection_pending())
return;
DoPendingStartUpdate();
}
void AppCacheHost::DoPendingStartUpdate() {
DCHECK(pending_start_update_callback_);
// 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_ = NULL;
pending_callback_param_ = NULL;
}
void AppCacheHost::SwapCacheWithCallback(SwapCacheCallback* callback,
void* callback_param) {
DCHECK(!pending_start_update_callback_ &&
!pending_swap_cache_callback_ &&
!pending_get_status_callback_);
pending_swap_cache_callback_ = callback;
pending_callback_param_ = callback_param;
if (is_selection_pending())
return;
DoPendingSwapCache();
}
void AppCacheHost::DoPendingSwapCache() {
DCHECK(pending_swap_cache_callback_);
// 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;
AssociateCache(NULL);
} else if (swappable_cache_) {
DCHECK(swappable_cache_.get() ==
swappable_cache_->owning_group()->newest_complete_cache());
success = true;
AssociateCache(swappable_cache_);
}
}
pending_swap_cache_callback_->Run(
success, pending_callback_param_);
pending_swap_cache_callback_ = NULL;
pending_callback_param_ = 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(
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))
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(
std::vector<AppCacheResourceInfo>* resource_infos) {
if (associated_cache_.get() && associated_cache_->is_complete()) {
for (AppCache::EntryMap::const_iterator it =
associated_cache_->entries().begin();
it != associated_cache_->entries().end(); ++it) {
AppCacheResourceInfo info;
info.url = it->first;
info.is_master = it->second.IsMaster();
info.is_manifest = it->second.IsManifest();
info.is_fallback = it->second.IsFallback();
info.is_foreign = it->second.IsForeign();
info.is_explicit = it->second.IsExplicit();
info.size = it->second.response_size();
resource_infos->push_back(info);
}
}
}
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());
AppCacheGroup* owing_group = cache->owning_group();
const char* kFormatString =
"Document was loaded from Application Cache with manifest %s";
frontend_->OnLogMessage(
host_id_, LOG_INFO,
StringPrintf(
kFormatString, owing_group->manifest_url().spec().c_str()));
AssociateCache(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());
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,
StringPrintf(kFormatString, group->manifest_url().spec().c_str()));
AssociateCache(NULL); // The UpdateJob may produce one for us later.
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();
AssociateCache(NULL);
}
// Respond to pending callbacks now that we have a selection.
if (pending_get_status_callback_)
DoPendingGetStatus();
else if (pending_start_update_callback_)
DoPendingStartUpdate();
else if (pending_swap_cache_callback_)
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(), GetStatus(), &info);
associated_cache_info_pending_ = false;
frontend_->OnCacheSelected(host_id_, info);
}
}
void AppCacheHost::OnContentBlocked(AppCacheGroup* group) {
frontend_->OnContentBlocked(host_id_, group->manifest_url());
}
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::NotifyMainResourceBlocked(const GURL& manifest_url) {
main_resource_blocked_ = true;
blocked_manifest_url_ = manifest_url;
}
void AppCacheHost::AssociateCache(AppCache* cache) {
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, GetStatus(), &info);
}
frontend_->OnCacheSelected(host_id_, info);
}
} // namespace appcache