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.
5 #include "webkit/browser/appcache/appcache_host.h"
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"
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;
30 info->cache_id = cache->cache_id();
32 if (!cache->is_complete())
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();
43 } // Anonymous namespace
45 AppCacheHost::AppCacheHost(int host_id, AppCacheFrontend* frontend,
46 AppCacheService* service)
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);
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_);
73 void AppCacheHost::AddObserver(Observer* observer) {
74 observers_.AddObserver(observer);
77 void AppCacheHost::RemoveObserver(Observer* observer) {
78 observers_.RemoveObserver(observer);
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());
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_);
93 if (main_resource_blocked_)
94 frontend_->OnContentBlocked(host_id_,
95 blocked_manifest_url_);
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
104 if (cache_document_was_loaded_from != kNoCacheId) {
105 LoadSelectedCache(cache_document_was_loaded_from);
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();
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(
120 ErrorDetails("Cache creation was blocked by the content policy",
124 false /*is_cross_origin*/));
125 frontend_->OnContentBlocked(host_id_, manifest_url);
129 // Note: The client detects if the document was not loaded using HTTP GET
130 // and invokes SelectCache without a manifest url, so that detection step
131 // is also skipped here. See WebApplicationCacheHostImpl.cc
132 set_preferred_manifest_url(manifest_url);
133 new_master_entry_url_ = document_url;
134 LoadOrCreateGroup(manifest_url);
138 // TODO(michaeln): If there was a manifest URL, the user agent may report
139 // to the user that it was ignored, to aid in application development.
140 FinishCacheSelection(NULL, NULL);
143 void AppCacheHost::SelectCacheForWorker(int parent_process_id,
144 int parent_host_id) {
145 DCHECK(pending_start_update_callback_.is_null() &&
146 pending_swap_cache_callback_.is_null() &&
147 pending_get_status_callback_.is_null() &&
148 !is_selection_pending());
150 parent_process_id_ = parent_process_id;
151 parent_host_id_ = parent_host_id;
152 FinishCacheSelection(NULL, NULL);
155 void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) {
156 DCHECK(pending_start_update_callback_.is_null() &&
157 pending_swap_cache_callback_.is_null() &&
158 pending_get_status_callback_.is_null() &&
159 !is_selection_pending());
161 if (appcache_id != kNoCacheId) {
162 LoadSelectedCache(appcache_id);
165 FinishCacheSelection(NULL, NULL);
168 // TODO(michaeln): change method name to MarkEntryAsForeign for consistency
169 void AppCacheHost::MarkAsForeignEntry(const GURL& document_url,
170 int64 cache_document_was_loaded_from) {
171 // The document url is not the resource url in the fallback case.
172 storage()->MarkEntryAsForeign(
173 main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url,
174 cache_document_was_loaded_from);
175 SelectCache(document_url, kNoCacheId, GURL());
178 void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback,
179 void* callback_param) {
180 DCHECK(pending_start_update_callback_.is_null() &&
181 pending_swap_cache_callback_.is_null() &&
182 pending_get_status_callback_.is_null());
184 pending_get_status_callback_ = callback;
185 pending_callback_param_ = callback_param;
186 if (is_selection_pending())
189 DoPendingGetStatus();
192 void AppCacheHost::DoPendingGetStatus() {
193 DCHECK_EQ(false, pending_get_status_callback_.is_null());
195 pending_get_status_callback_.Run(GetStatus(), pending_callback_param_);
196 pending_get_status_callback_.Reset();
197 pending_callback_param_ = NULL;
200 void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback,
201 void* callback_param) {
202 DCHECK(pending_start_update_callback_.is_null() &&
203 pending_swap_cache_callback_.is_null() &&
204 pending_get_status_callback_.is_null());
206 pending_start_update_callback_ = callback;
207 pending_callback_param_ = callback_param;
208 if (is_selection_pending())
211 DoPendingStartUpdate();
214 void AppCacheHost::DoPendingStartUpdate() {
215 DCHECK_EQ(false, pending_start_update_callback_.is_null());
217 // 6.9.8 Application cache API
218 bool success = false;
219 if (associated_cache_.get() && associated_cache_->owning_group()) {
220 AppCacheGroup* group = associated_cache_->owning_group();
221 if (!group->is_obsolete() && !group->is_being_deleted()) {
223 group->StartUpdate();
227 pending_start_update_callback_.Run(success, pending_callback_param_);
228 pending_start_update_callback_.Reset();
229 pending_callback_param_ = NULL;
232 void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback,
233 void* callback_param) {
234 DCHECK(pending_start_update_callback_.is_null() &&
235 pending_swap_cache_callback_.is_null() &&
236 pending_get_status_callback_.is_null());
238 pending_swap_cache_callback_ = callback;
239 pending_callback_param_ = callback_param;
240 if (is_selection_pending())
243 DoPendingSwapCache();
246 void AppCacheHost::DoPendingSwapCache() {
247 DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
249 // 6.9.8 Application cache API
250 bool success = false;
251 if (associated_cache_.get() && associated_cache_->owning_group()) {
252 if (associated_cache_->owning_group()->is_obsolete()) {
254 AssociateNoCache(GURL());
255 } else if (swappable_cache_.get()) {
256 DCHECK(swappable_cache_.get() ==
257 swappable_cache_->owning_group()->newest_complete_cache());
259 AssociateCompleteCache(swappable_cache_.get());
263 pending_swap_cache_callback_.Run(success, pending_callback_param_);
264 pending_swap_cache_callback_.Reset();
265 pending_callback_param_ = NULL;
268 void AppCacheHost::SetSpawningHostId(
269 int spawning_process_id, int spawning_host_id) {
270 spawning_process_id_ = spawning_process_id;
271 spawning_host_id_ = spawning_host_id;
274 const AppCacheHost* AppCacheHost::GetSpawningHost() const {
275 AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_);
276 return backend ? backend->GetHost(spawning_host_id_) : NULL;
279 AppCacheHost* AppCacheHost::GetParentAppCacheHost() const {
280 DCHECK(is_for_dedicated_worker());
281 AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_);
282 return backend ? backend->GetHost(parent_host_id_) : NULL;
285 AppCacheRequestHandler* AppCacheHost::CreateRequestHandler(
286 net::URLRequest* request,
287 ResourceType::Type resource_type) {
288 if (is_for_dedicated_worker()) {
289 AppCacheHost* parent_host = GetParentAppCacheHost();
291 return parent_host->CreateRequestHandler(request, resource_type);
295 if (AppCacheRequestHandler::IsMainResourceType(resource_type)) {
296 // Store the first party origin so that it can be used later in SelectCache
297 // for checking whether the creation of the appcache is allowed.
298 first_party_url_ = request->first_party_for_cookies();
299 return new AppCacheRequestHandler(this, resource_type);
302 if ((associated_cache() && associated_cache()->is_complete()) ||
303 is_selection_pending()) {
304 return new AppCacheRequestHandler(this, resource_type);
309 void AppCacheHost::GetResourceList(
310 AppCacheResourceInfoVector* resource_infos) {
311 if (associated_cache_.get() && associated_cache_->is_complete())
312 associated_cache_->ToResourceInfoVector(resource_infos);
315 Status AppCacheHost::GetStatus() {
316 // 6.9.8 Application cache API
317 AppCache* cache = associated_cache();
321 // A cache without an owning group represents the cache being constructed
322 // during the application cache update process.
323 if (!cache->owning_group())
326 if (cache->owning_group()->is_obsolete())
328 if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING)
330 if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING)
332 if (swappable_cache_.get())
337 void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) {
338 DCHECK(manifest_url.is_valid());
339 pending_selected_manifest_url_ = manifest_url;
340 storage()->LoadOrCreateGroup(manifest_url, this);
343 void AppCacheHost::OnGroupLoaded(AppCacheGroup* group,
344 const GURL& manifest_url) {
345 DCHECK(manifest_url == pending_selected_manifest_url_);
346 pending_selected_manifest_url_ = GURL();
347 FinishCacheSelection(NULL, group);
350 void AppCacheHost::LoadSelectedCache(int64 cache_id) {
351 DCHECK(cache_id != kNoCacheId);
352 pending_selected_cache_id_ = cache_id;
353 storage()->LoadCache(cache_id, this);
356 void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) {
357 if (cache_id == pending_main_resource_cache_id_) {
358 pending_main_resource_cache_id_ = kNoCacheId;
359 main_resource_cache_ = cache;
360 } else if (cache_id == pending_selected_cache_id_) {
361 pending_selected_cache_id_ = kNoCacheId;
362 FinishCacheSelection(cache, NULL);
366 void AppCacheHost::FinishCacheSelection(
367 AppCache *cache, AppCacheGroup* group) {
368 DCHECK(!associated_cache());
370 // 6.9.6 The application cache selection algorithm
372 // If document was loaded from an application cache, Associate document
373 // with the application cache from which it was loaded. Invoke the
374 // application cache update process for that cache and with the browsing
375 // context being navigated.
376 DCHECK(cache->owning_group());
377 DCHECK(new_master_entry_url_.is_empty());
378 DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_);
379 AppCacheGroup* owing_group = cache->owning_group();
380 const char* kFormatString =
381 "Document was loaded from Application Cache with manifest %s";
382 frontend_->OnLogMessage(
385 kFormatString, owing_group->manifest_url().spec().c_str()));
386 AssociateCompleteCache(cache);
387 if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) {
388 owing_group->StartUpdateWithHost(this);
389 ObserveGroupBeingUpdated(owing_group);
391 } else if (group && !group->is_being_deleted()) {
392 // If document was loaded using HTTP GET or equivalent, and, there is a
393 // manifest URL, and manifest URL has the same origin as document.
394 // Invoke the application cache update process for manifest URL, with
395 // the browsing context being navigated, and with document and the
396 // resource from which document was loaded as the new master resourse.
397 DCHECK(!group->is_obsolete());
398 DCHECK(new_master_entry_url_.is_valid());
399 DCHECK_EQ(group->manifest_url(), preferred_manifest_url_);
400 const char* kFormatString = group->HasCache() ?
401 "Adding master entry to Application Cache with manifest %s" :
402 "Creating Application Cache with manifest %s";
403 frontend_->OnLogMessage(
405 base::StringPrintf(kFormatString,
406 group->manifest_url().spec().c_str()));
407 // The UpdateJob may produce one for us later.
408 AssociateNoCache(preferred_manifest_url_);
409 group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_);
410 ObserveGroupBeingUpdated(group);
412 // Otherwise, the Document is not associated with any application cache.
413 new_master_entry_url_ = GURL();
414 AssociateNoCache(GURL());
417 // Respond to pending callbacks now that we have a selection.
418 if (!pending_get_status_callback_.is_null())
419 DoPendingGetStatus();
420 else if (!pending_start_update_callback_.is_null())
421 DoPendingStartUpdate();
422 else if (!pending_swap_cache_callback_.is_null())
423 DoPendingSwapCache();
425 FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this));
428 void AppCacheHost::OnServiceReinitialized(
429 AppCacheStorageReference* old_storage_ref) {
430 // We continue to use the disabled instance, but arrange for its
431 // deletion when its no longer needed.
432 if (old_storage_ref->storage() == storage())
433 disabled_storage_reference_ = old_storage_ref;
436 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
437 DCHECK(!group_being_updated_.get());
438 group_being_updated_ = group;
439 newest_cache_of_group_being_updated_ = group->newest_complete_cache();
440 group->AddUpdateObserver(this);
443 void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
444 DCHECK_EQ(group, group_being_updated_);
445 group->RemoveUpdateObserver(this);
447 // Add a reference to the newest complete cache.
448 SetSwappableCache(group);
450 group_being_updated_ = NULL;
451 newest_cache_of_group_being_updated_ = NULL;
453 if (associated_cache_info_pending_ && associated_cache_.get() &&
454 associated_cache_->is_complete()) {
457 associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info);
458 associated_cache_info_pending_ = false;
459 frontend_->OnCacheSelected(host_id_, info);
463 void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
465 swappable_cache_ = NULL;
467 AppCache* new_cache = group->newest_complete_cache();
468 if (new_cache != associated_cache_.get())
469 swappable_cache_ = new_cache;
471 swappable_cache_ = NULL;
475 void AppCacheHost::LoadMainResourceCache(int64 cache_id) {
476 DCHECK(cache_id != kNoCacheId);
477 if (pending_main_resource_cache_id_ == cache_id ||
478 (main_resource_cache_.get() &&
479 main_resource_cache_->cache_id() == cache_id)) {
482 pending_main_resource_cache_id_ = cache_id;
483 storage()->LoadCache(cache_id, this);
486 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
487 const GURL& namespace_entry_url) {
488 main_resource_was_namespace_entry_ = true;
489 namespace_entry_url_ = namespace_entry_url;
492 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
493 main_resource_blocked_ = true;
494 blocked_manifest_url_ = manifest_url;
497 void AppCacheHost::PrepareForTransfer() {
498 // This can only happen prior to the document having been loaded.
499 DCHECK(!associated_cache());
500 DCHECK(!is_selection_pending());
501 DCHECK(!group_being_updated_);
502 host_id_ = kNoHostId;
506 void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) {
508 frontend_ = frontend;
511 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
512 // manifest url can be empty.
513 AssociateCacheHelper(NULL, manifest_url);
516 void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
517 const GURL& manifest_url) {
518 DCHECK(cache && !cache->is_complete());
519 DCHECK(!manifest_url.is_empty());
520 AssociateCacheHelper(cache, manifest_url);
523 void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
524 DCHECK(cache && cache->is_complete());
525 AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
528 void AppCacheHost::AssociateCacheHelper(AppCache* cache,
529 const GURL& manifest_url) {
530 if (associated_cache_.get()) {
531 associated_cache_->UnassociateHost(this);
534 associated_cache_ = cache;
535 SetSwappableCache(cache ? cache->owning_group() : NULL);
536 associated_cache_info_pending_ = cache && !cache->is_complete();
539 cache->AssociateHost(this);
541 FillCacheInfo(cache, manifest_url, GetStatus(), &info);
542 frontend_->OnCacheSelected(host_id_, info);
545 } // namespace appcache