When Retrier succeeds, record errors it encountered.
[chromium-blink-merge.git] / webkit / appcache / appcache_host.cc
blobb5401124e67abe53c35b62fd691b17cccb530490
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/appcache/appcache_host.h"
7 #include "base/logging.h"
8 #include "base/string_util.h"
9 #include "base/stringprintf.h"
10 #include "net/url_request/url_request.h"
11 #include "webkit/appcache/appcache.h"
12 #include "webkit/appcache/appcache_backend_impl.h"
13 #include "webkit/appcache/appcache_policy.h"
14 #include "webkit/appcache/appcache_request_handler.h"
15 #include "webkit/quota/quota_manager.h"
17 namespace appcache {
19 namespace {
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;
27 if (!cache)
28 return;
30 info->cache_id = cache->cache_id();
32 if (!cache->is_complete())
33 return;
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)
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) {
59 AppCacheHost::~AppCacheHost() {
60 FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this));
61 if (associated_cache_)
62 associated_cache_->UnassociateHost(this);
63 if (group_being_updated_)
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_);
70 void AppCacheHost::AddObserver(Observer* observer) {
71 observers_.AddObserver(observer);
74 void AppCacheHost::RemoveObserver(Observer* observer) {
75 observers_.RemoveObserver(observer);
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());
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_);
90 if (main_resource_blocked_)
91 frontend_->OnContentBlocked(host_id_,
92 blocked_manifest_url_);
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
101 if (cache_document_was_loaded_from != kNoCacheId) {
102 LoadSelectedCache(cache_document_was_loaded_from);
103 return;
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;
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;
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);
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());
142 parent_process_id_ = parent_process_id;
143 parent_host_id_ = parent_host_id;
144 FinishCacheSelection(NULL, NULL);
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());
153 if (appcache_id != kNoCacheId) {
154 LoadSelectedCache(appcache_id);
155 return;
157 FinishCacheSelection(NULL, NULL);
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());
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());
176 pending_get_status_callback_ = callback;
177 pending_callback_param_ = callback_param;
178 if (is_selection_pending())
179 return;
181 DoPendingGetStatus();
184 void AppCacheHost::DoPendingGetStatus() {
185 DCHECK_EQ(false, pending_get_status_callback_.is_null());
187 pending_get_status_callback_.Run(GetStatus(), pending_callback_param_);
188 pending_get_status_callback_.Reset();
189 pending_callback_param_ = NULL;
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());
198 pending_start_update_callback_ = callback;
199 pending_callback_param_ = callback_param;
200 if (is_selection_pending())
201 return;
203 DoPendingStartUpdate();
206 void AppCacheHost::DoPendingStartUpdate() {
207 DCHECK_EQ(false, pending_start_update_callback_.is_null());
209 // 6.9.8 Application cache API
210 bool success = false;
211 if (associated_cache_ && 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();
219 pending_start_update_callback_.Run(success, pending_callback_param_);
220 pending_start_update_callback_.Reset();
221 pending_callback_param_ = NULL;
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());
230 pending_swap_cache_callback_ = callback;
231 pending_callback_param_ = callback_param;
232 if (is_selection_pending())
233 return;
235 DoPendingSwapCache();
238 void AppCacheHost::DoPendingSwapCache() {
239 DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
241 // 6.9.8 Application cache API
242 bool success = false;
243 if (associated_cache_ && associated_cache_->owning_group()) {
244 if (associated_cache_->owning_group()->is_obsolete()) {
245 success = true;
246 AssociateNoCache(GURL());
247 } else if (swappable_cache_) {
248 DCHECK(swappable_cache_.get() ==
249 swappable_cache_->owning_group()->newest_complete_cache());
250 success = true;
251 AssociateCompleteCache(swappable_cache_);
255 pending_swap_cache_callback_.Run(success, pending_callback_param_);
256 pending_swap_cache_callback_.Reset();
257 pending_callback_param_ = NULL;
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;
266 const AppCacheHost* AppCacheHost::GetSpawningHost() const {
267 AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_);
268 return backend ? backend->GetHost(spawning_host_id_) : NULL;
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;
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;
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);
294 if ((associated_cache() && associated_cache()->is_complete()) ||
295 is_selection_pending()) {
296 return new AppCacheRequestHandler(this, resource_type);
298 return NULL;
301 void AppCacheHost::GetResourceList(
302 AppCacheResourceInfoVector* resource_infos) {
303 if (associated_cache_.get() && associated_cache_->is_complete())
304 associated_cache_->ToResourceInfoVector(resource_infos);
307 Status AppCacheHost::GetStatus() {
308 // 6.9.8 Application cache API
309 AppCache* cache = associated_cache();
310 if (!cache)
311 return UNCACHED;
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;
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_)
325 return UPDATE_READY;
326 return IDLE;
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);
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);
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);
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);
358 void AppCacheHost::FinishCacheSelection(
359 AppCache *cache, AppCacheGroup* group) {
360 DCHECK(!associated_cache());
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);
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());
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();
417 FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this));
420 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
421 DCHECK(!group_being_updated_);
422 group_being_updated_ = group;
423 newest_cache_of_group_being_updated_ = group->newest_complete_cache();
424 group->AddUpdateObserver(this);
427 void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
428 DCHECK_EQ(group, group_being_updated_);
429 group->RemoveUpdateObserver(this);
431 // Add a reference to the newest complete cache.
432 SetSwappableCache(group);
434 group_being_updated_ = NULL;
435 newest_cache_of_group_being_updated_ = NULL;
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);
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_)
453 swappable_cache_ = new_cache;
454 else
455 swappable_cache_ = NULL;
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_ && main_resource_cache_->cache_id() == cache_id)) {
463 return;
465 pending_main_resource_cache_id_ = cache_id;
466 service_->storage()->LoadCache(cache_id, this);
469 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
470 const GURL& namespace_entry_url) {
471 main_resource_was_namespace_entry_ = true;
472 namespace_entry_url_ = namespace_entry_url;
475 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
476 main_resource_blocked_ = true;
477 blocked_manifest_url_ = manifest_url;
480 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
481 // manifest url can be empty.
482 AssociateCacheHelper(NULL, manifest_url);
485 void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
486 const GURL& manifest_url) {
487 DCHECK(cache && !cache->is_complete());
488 DCHECK(!manifest_url.is_empty());
489 AssociateCacheHelper(cache, manifest_url);
492 void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
493 DCHECK(cache && cache->is_complete());
494 AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
497 void AppCacheHost::AssociateCacheHelper(AppCache* cache,
498 const GURL& manifest_url) {
499 if (associated_cache_) {
500 associated_cache_->UnassociateHost(this);
503 associated_cache_ = cache;
504 SetSwappableCache(cache ? cache->owning_group() : NULL);
505 associated_cache_info_pending_ = cache && !cache->is_complete();
506 AppCacheInfo info;
507 if (cache)
508 cache->AssociateHost(this);
510 FillCacheInfo(cache, manifest_url, GetStatus(), &info);
511 frontend_->OnCacheSelected(host_id_, info);
514 } // namespace appcache