Use multiline attribute to check for IA2_STATE_MULTILINE.
[chromium-blink-merge.git] / content / browser / appcache / appcache_host.cc
blob4ec19ffe62430ea6028e307f27c09a6c606c5d8a
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 "content/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 "content/browser/appcache/appcache.h"
11 #include "content/browser/appcache/appcache_backend_impl.h"
12 #include "content/browser/appcache/appcache_policy.h"
13 #include "content/browser/appcache/appcache_request_handler.h"
14 #include "net/url_request/url_request.h"
15 #include "storage/browser/quota/quota_manager_proxy.h"
17 namespace content {
19 namespace {
21 void FillCacheInfo(const AppCache* cache,
22 const GURL& manifest_url,
23 AppCacheStatus 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 AppCacheServiceImpl* service)
47 : host_id_(host_id),
48 spawning_host_id_(kAppCacheNoHostId), spawning_process_id_(0),
49 parent_host_id_(kAppCacheNoHostId), parent_process_id_(0),
50 pending_main_resource_cache_id_(kAppCacheNoCacheId),
51 pending_selected_cache_id_(kAppCacheNoCacheId),
52 is_cache_selection_enabled_(true),
53 frontend_(frontend), service_(service),
54 storage_(service->storage()),
55 pending_callback_param_(NULL),
56 main_resource_was_namespace_entry_(false),
57 main_resource_blocked_(false),
58 associated_cache_info_pending_(false) {
59 service_->AddObserver(this);
62 AppCacheHost::~AppCacheHost() {
63 service_->RemoveObserver(this);
64 FOR_EACH_OBSERVER(Observer, observers_, OnDestructionImminent(this));
65 if (associated_cache_.get())
66 associated_cache_->UnassociateHost(this);
67 if (group_being_updated_.get())
68 group_being_updated_->RemoveUpdateObserver(this);
69 storage()->CancelDelegateCallbacks(this);
70 if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
71 service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_);
74 void AppCacheHost::AddObserver(Observer* observer) {
75 observers_.AddObserver(observer);
78 void AppCacheHost::RemoveObserver(Observer* observer) {
79 observers_.RemoveObserver(observer);
82 void AppCacheHost::SelectCache(const GURL& document_url,
83 const int64 cache_document_was_loaded_from,
84 const GURL& manifest_url) {
85 DCHECK(pending_start_update_callback_.is_null() &&
86 pending_swap_cache_callback_.is_null() &&
87 pending_get_status_callback_.is_null() &&
88 !is_selection_pending());
90 if (!is_cache_selection_enabled_) {
91 FinishCacheSelection(NULL, NULL);
92 return;
95 origin_in_use_ = document_url.GetOrigin();
96 if (service()->quota_manager_proxy() && !origin_in_use_.is_empty())
97 service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_);
99 if (main_resource_blocked_)
100 frontend_->OnContentBlocked(host_id_,
101 blocked_manifest_url_);
103 // 6.9.6 The application cache selection algorithm.
104 // The algorithm is started here and continues in FinishCacheSelection,
105 // after cache or group loading is complete.
106 // Note: Foreign entries are detected on the client side and
107 // MarkAsForeignEntry is called in that case, so that detection
108 // step is skipped here. See WebApplicationCacheHostImpl.cc
110 if (cache_document_was_loaded_from != kAppCacheNoCacheId) {
111 LoadSelectedCache(cache_document_was_loaded_from);
112 return;
115 if (!manifest_url.is_empty() &&
116 (manifest_url.GetOrigin() == document_url.GetOrigin())) {
117 DCHECK(!first_party_url_.is_empty());
118 AppCachePolicy* policy = service()->appcache_policy();
119 if (policy &&
120 !policy->CanCreateAppCache(manifest_url, first_party_url_)) {
121 FinishCacheSelection(NULL, NULL);
122 std::vector<int> host_ids(1, host_id_);
123 frontend_->OnEventRaised(host_ids, APPCACHE_CHECKING_EVENT);
124 frontend_->OnErrorEventRaised(
125 host_ids,
126 AppCacheErrorDetails(
127 "Cache creation was blocked by the content policy",
128 APPCACHE_POLICY_ERROR,
129 GURL(),
131 false /*is_cross_origin*/));
132 frontend_->OnContentBlocked(host_id_, manifest_url);
133 return;
136 // Note: The client detects if the document was not loaded using HTTP GET
137 // and invokes SelectCache without a manifest url, so that detection step
138 // is also skipped here. See WebApplicationCacheHostImpl.cc
139 set_preferred_manifest_url(manifest_url);
140 new_master_entry_url_ = document_url;
141 LoadOrCreateGroup(manifest_url);
142 return;
145 // TODO(michaeln): If there was a manifest URL, the user agent may report
146 // to the user that it was ignored, to aid in application development.
147 FinishCacheSelection(NULL, NULL);
150 void AppCacheHost::SelectCacheForWorker(int parent_process_id,
151 int parent_host_id) {
152 DCHECK(pending_start_update_callback_.is_null() &&
153 pending_swap_cache_callback_.is_null() &&
154 pending_get_status_callback_.is_null() &&
155 !is_selection_pending());
157 parent_process_id_ = parent_process_id;
158 parent_host_id_ = parent_host_id;
159 FinishCacheSelection(NULL, NULL);
162 void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id) {
163 DCHECK(pending_start_update_callback_.is_null() &&
164 pending_swap_cache_callback_.is_null() &&
165 pending_get_status_callback_.is_null() &&
166 !is_selection_pending());
168 if (appcache_id != kAppCacheNoCacheId) {
169 LoadSelectedCache(appcache_id);
170 return;
172 FinishCacheSelection(NULL, NULL);
175 // TODO(michaeln): change method name to MarkEntryAsForeign for consistency
176 void AppCacheHost::MarkAsForeignEntry(const GURL& document_url,
177 int64 cache_document_was_loaded_from) {
178 // The document url is not the resource url in the fallback case.
179 storage()->MarkEntryAsForeign(
180 main_resource_was_namespace_entry_ ? namespace_entry_url_ : document_url,
181 cache_document_was_loaded_from);
182 SelectCache(document_url, kAppCacheNoCacheId, GURL());
185 void AppCacheHost::GetStatusWithCallback(const GetStatusCallback& callback,
186 void* callback_param) {
187 DCHECK(pending_start_update_callback_.is_null() &&
188 pending_swap_cache_callback_.is_null() &&
189 pending_get_status_callback_.is_null());
191 pending_get_status_callback_ = callback;
192 pending_callback_param_ = callback_param;
193 if (is_selection_pending())
194 return;
196 DoPendingGetStatus();
199 void AppCacheHost::DoPendingGetStatus() {
200 DCHECK_EQ(false, pending_get_status_callback_.is_null());
202 pending_get_status_callback_.Run(GetStatus(), pending_callback_param_);
203 pending_get_status_callback_.Reset();
204 pending_callback_param_ = NULL;
207 void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback& callback,
208 void* callback_param) {
209 DCHECK(pending_start_update_callback_.is_null() &&
210 pending_swap_cache_callback_.is_null() &&
211 pending_get_status_callback_.is_null());
213 pending_start_update_callback_ = callback;
214 pending_callback_param_ = callback_param;
215 if (is_selection_pending())
216 return;
218 DoPendingStartUpdate();
221 void AppCacheHost::DoPendingStartUpdate() {
222 DCHECK_EQ(false, pending_start_update_callback_.is_null());
224 // 6.9.8 Application cache API
225 bool success = false;
226 if (associated_cache_.get() && associated_cache_->owning_group()) {
227 AppCacheGroup* group = associated_cache_->owning_group();
228 if (!group->is_obsolete() && !group->is_being_deleted()) {
229 success = true;
230 group->StartUpdate();
234 pending_start_update_callback_.Run(success, pending_callback_param_);
235 pending_start_update_callback_.Reset();
236 pending_callback_param_ = NULL;
239 void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback& callback,
240 void* callback_param) {
241 DCHECK(pending_start_update_callback_.is_null() &&
242 pending_swap_cache_callback_.is_null() &&
243 pending_get_status_callback_.is_null());
245 pending_swap_cache_callback_ = callback;
246 pending_callback_param_ = callback_param;
247 if (is_selection_pending())
248 return;
250 DoPendingSwapCache();
253 void AppCacheHost::DoPendingSwapCache() {
254 DCHECK_EQ(false, pending_swap_cache_callback_.is_null());
256 // 6.9.8 Application cache API
257 bool success = false;
258 if (associated_cache_.get() && associated_cache_->owning_group()) {
259 if (associated_cache_->owning_group()->is_obsolete()) {
260 success = true;
261 AssociateNoCache(GURL());
262 } else if (swappable_cache_.get()) {
263 DCHECK(swappable_cache_.get() ==
264 swappable_cache_->owning_group()->newest_complete_cache());
265 success = true;
266 AssociateCompleteCache(swappable_cache_.get());
270 pending_swap_cache_callback_.Run(success, pending_callback_param_);
271 pending_swap_cache_callback_.Reset();
272 pending_callback_param_ = NULL;
275 void AppCacheHost::SetSpawningHostId(
276 int spawning_process_id, int spawning_host_id) {
277 spawning_process_id_ = spawning_process_id;
278 spawning_host_id_ = spawning_host_id;
281 const AppCacheHost* AppCacheHost::GetSpawningHost() const {
282 AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_);
283 return backend ? backend->GetHost(spawning_host_id_) : NULL;
286 AppCacheHost* AppCacheHost::GetParentAppCacheHost() const {
287 DCHECK(is_for_dedicated_worker());
288 AppCacheBackendImpl* backend = service_->GetBackend(parent_process_id_);
289 return backend ? backend->GetHost(parent_host_id_) : NULL;
292 AppCacheRequestHandler* AppCacheHost::CreateRequestHandler(
293 net::URLRequest* request,
294 ResourceType resource_type,
295 bool should_reset_appcache) {
296 if (is_for_dedicated_worker()) {
297 AppCacheHost* parent_host = GetParentAppCacheHost();
298 if (parent_host)
299 return parent_host->CreateRequestHandler(
300 request, resource_type, should_reset_appcache);
301 return NULL;
304 if (AppCacheRequestHandler::IsMainResourceType(resource_type)) {
305 // Store the first party origin so that it can be used later in SelectCache
306 // for checking whether the creation of the appcache is allowed.
307 first_party_url_ = request->first_party_for_cookies();
308 return new AppCacheRequestHandler(
309 this, resource_type, should_reset_appcache);
312 if ((associated_cache() && associated_cache()->is_complete()) ||
313 is_selection_pending()) {
314 return new AppCacheRequestHandler(
315 this, resource_type, should_reset_appcache);
317 return NULL;
320 void AppCacheHost::GetResourceList(
321 AppCacheResourceInfoVector* resource_infos) {
322 if (associated_cache_.get() && associated_cache_->is_complete())
323 associated_cache_->ToResourceInfoVector(resource_infos);
326 AppCacheStatus AppCacheHost::GetStatus() {
327 // 6.9.8 Application cache API
328 AppCache* cache = associated_cache();
329 if (!cache)
330 return APPCACHE_STATUS_UNCACHED;
332 // A cache without an owning group represents the cache being constructed
333 // during the application cache update process.
334 if (!cache->owning_group())
335 return APPCACHE_STATUS_DOWNLOADING;
337 if (cache->owning_group()->is_obsolete())
338 return APPCACHE_STATUS_OBSOLETE;
339 if (cache->owning_group()->update_status() == AppCacheGroup::CHECKING)
340 return APPCACHE_STATUS_CHECKING;
341 if (cache->owning_group()->update_status() == AppCacheGroup::DOWNLOADING)
342 return APPCACHE_STATUS_DOWNLOADING;
343 if (swappable_cache_.get())
344 return APPCACHE_STATUS_UPDATE_READY;
345 return APPCACHE_STATUS_IDLE;
348 void AppCacheHost::LoadOrCreateGroup(const GURL& manifest_url) {
349 DCHECK(manifest_url.is_valid());
350 pending_selected_manifest_url_ = manifest_url;
351 storage()->LoadOrCreateGroup(manifest_url, this);
354 void AppCacheHost::OnGroupLoaded(AppCacheGroup* group,
355 const GURL& manifest_url) {
356 DCHECK(manifest_url == pending_selected_manifest_url_);
357 pending_selected_manifest_url_ = GURL();
358 FinishCacheSelection(NULL, group);
361 void AppCacheHost::LoadSelectedCache(int64 cache_id) {
362 DCHECK(cache_id != kAppCacheNoCacheId);
363 pending_selected_cache_id_ = cache_id;
364 storage()->LoadCache(cache_id, this);
367 void AppCacheHost::OnCacheLoaded(AppCache* cache, int64 cache_id) {
368 if (cache_id == pending_main_resource_cache_id_) {
369 pending_main_resource_cache_id_ = kAppCacheNoCacheId;
370 main_resource_cache_ = cache;
371 } else if (cache_id == pending_selected_cache_id_) {
372 pending_selected_cache_id_ = kAppCacheNoCacheId;
373 FinishCacheSelection(cache, NULL);
377 void AppCacheHost::FinishCacheSelection(
378 AppCache *cache, AppCacheGroup* group) {
379 DCHECK(!associated_cache());
381 // 6.9.6 The application cache selection algorithm
382 if (cache) {
383 // If document was loaded from an application cache, Associate document
384 // with the application cache from which it was loaded. Invoke the
385 // application cache update process for that cache and with the browsing
386 // context being navigated.
387 DCHECK(cache->owning_group());
388 DCHECK(new_master_entry_url_.is_empty());
389 DCHECK_EQ(cache->owning_group()->manifest_url(), preferred_manifest_url_);
390 AppCacheGroup* owing_group = cache->owning_group();
391 const char* kFormatString =
392 "Document was loaded from Application Cache with manifest %s";
393 frontend_->OnLogMessage(
394 host_id_, APPCACHE_LOG_INFO,
395 base::StringPrintf(
396 kFormatString, owing_group->manifest_url().spec().c_str()));
397 AssociateCompleteCache(cache);
398 if (!owing_group->is_obsolete() && !owing_group->is_being_deleted()) {
399 owing_group->StartUpdateWithHost(this);
400 ObserveGroupBeingUpdated(owing_group);
402 } else if (group && !group->is_being_deleted()) {
403 // If document was loaded using HTTP GET or equivalent, and, there is a
404 // manifest URL, and manifest URL has the same origin as document.
405 // Invoke the application cache update process for manifest URL, with
406 // the browsing context being navigated, and with document and the
407 // resource from which document was loaded as the new master resourse.
408 DCHECK(!group->is_obsolete());
409 DCHECK(new_master_entry_url_.is_valid());
410 DCHECK_EQ(group->manifest_url(), preferred_manifest_url_);
411 const char* kFormatString = group->HasCache() ?
412 "Adding master entry to Application Cache with manifest %s" :
413 "Creating Application Cache with manifest %s";
414 frontend_->OnLogMessage(
415 host_id_, APPCACHE_LOG_INFO,
416 base::StringPrintf(kFormatString,
417 group->manifest_url().spec().c_str()));
418 // The UpdateJob may produce one for us later.
419 AssociateNoCache(preferred_manifest_url_);
420 group->StartUpdateWithNewMasterEntry(this, new_master_entry_url_);
421 ObserveGroupBeingUpdated(group);
422 } else {
423 // Otherwise, the Document is not associated with any application cache.
424 new_master_entry_url_ = GURL();
425 AssociateNoCache(GURL());
428 // Respond to pending callbacks now that we have a selection.
429 if (!pending_get_status_callback_.is_null())
430 DoPendingGetStatus();
431 else if (!pending_start_update_callback_.is_null())
432 DoPendingStartUpdate();
433 else if (!pending_swap_cache_callback_.is_null())
434 DoPendingSwapCache();
436 FOR_EACH_OBSERVER(Observer, observers_, OnCacheSelectionComplete(this));
439 void AppCacheHost::OnServiceReinitialized(
440 AppCacheStorageReference* old_storage_ref) {
441 // We continue to use the disabled instance, but arrange for its
442 // deletion when its no longer needed.
443 if (old_storage_ref->storage() == storage())
444 disabled_storage_reference_ = old_storage_ref;
447 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup* group) {
448 DCHECK(!group_being_updated_.get());
449 group_being_updated_ = group;
450 newest_cache_of_group_being_updated_ = group->newest_complete_cache();
451 group->AddUpdateObserver(this);
454 void AppCacheHost::OnUpdateComplete(AppCacheGroup* group) {
455 DCHECK_EQ(group, group_being_updated_.get());
456 group->RemoveUpdateObserver(this);
458 // Add a reference to the newest complete cache.
459 SetSwappableCache(group);
461 group_being_updated_ = NULL;
462 newest_cache_of_group_being_updated_ = NULL;
464 if (associated_cache_info_pending_ && associated_cache_.get() &&
465 associated_cache_->is_complete()) {
466 AppCacheInfo info;
467 FillCacheInfo(
468 associated_cache_.get(), preferred_manifest_url_, GetStatus(), &info);
469 associated_cache_info_pending_ = false;
470 frontend_->OnCacheSelected(host_id_, info);
474 void AppCacheHost::SetSwappableCache(AppCacheGroup* group) {
475 if (!group) {
476 swappable_cache_ = NULL;
477 } else {
478 AppCache* new_cache = group->newest_complete_cache();
479 if (new_cache != associated_cache_.get())
480 swappable_cache_ = new_cache;
481 else
482 swappable_cache_ = NULL;
486 void AppCacheHost::LoadMainResourceCache(int64 cache_id) {
487 DCHECK(cache_id != kAppCacheNoCacheId);
488 if (pending_main_resource_cache_id_ == cache_id ||
489 (main_resource_cache_.get() &&
490 main_resource_cache_->cache_id() == cache_id)) {
491 return;
493 pending_main_resource_cache_id_ = cache_id;
494 storage()->LoadCache(cache_id, this);
497 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
498 const GURL& namespace_entry_url) {
499 main_resource_was_namespace_entry_ = true;
500 namespace_entry_url_ = namespace_entry_url;
503 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) {
504 main_resource_blocked_ = true;
505 blocked_manifest_url_ = manifest_url;
508 void AppCacheHost::PrepareForTransfer() {
509 // This can only happen prior to the document having been loaded.
510 DCHECK(!associated_cache());
511 DCHECK(!is_selection_pending());
512 DCHECK(!group_being_updated_.get());
513 host_id_ = kAppCacheNoHostId;
514 frontend_ = NULL;
517 void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) {
518 host_id_ = host_id;
519 frontend_ = frontend;
522 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) {
523 // manifest url can be empty.
524 AssociateCacheHelper(NULL, manifest_url);
527 void AppCacheHost::AssociateIncompleteCache(AppCache* cache,
528 const GURL& manifest_url) {
529 DCHECK(cache && !cache->is_complete());
530 DCHECK(!manifest_url.is_empty());
531 AssociateCacheHelper(cache, manifest_url);
534 void AppCacheHost::AssociateCompleteCache(AppCache* cache) {
535 DCHECK(cache && cache->is_complete());
536 AssociateCacheHelper(cache, cache->owning_group()->manifest_url());
539 void AppCacheHost::AssociateCacheHelper(AppCache* cache,
540 const GURL& manifest_url) {
541 if (associated_cache_.get()) {
542 associated_cache_->UnassociateHost(this);
545 associated_cache_ = cache;
546 SetSwappableCache(cache ? cache->owning_group() : NULL);
547 associated_cache_info_pending_ = cache && !cache->is_complete();
548 AppCacheInfo info;
549 if (cache)
550 cache->AssociateHost(this);
552 FillCacheInfo(cache, manifest_url, GetStatus(), &info);
553 frontend_->OnCacheSelected(host_id_, info);
556 } // namespace content