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"
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
;
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 AppCacheServiceImpl
* service
)
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 was_select_cache_called_(false),
53 is_cache_selection_enabled_(true),
54 frontend_(frontend
), service_(service
),
55 storage_(service
->storage()),
56 pending_callback_param_(NULL
),
57 main_resource_was_namespace_entry_(false),
58 main_resource_blocked_(false),
59 associated_cache_info_pending_(false) {
60 service_
->AddObserver(this);
63 AppCacheHost::~AppCacheHost() {
64 service_
->RemoveObserver(this);
65 FOR_EACH_OBSERVER(Observer
, observers_
, OnDestructionImminent(this));
66 if (associated_cache_
.get())
67 associated_cache_
->UnassociateHost(this);
68 if (group_being_updated_
.get())
69 group_being_updated_
->RemoveUpdateObserver(this);
70 storage()->CancelDelegateCallbacks(this);
71 if (service()->quota_manager_proxy() && !origin_in_use_
.is_empty())
72 service()->quota_manager_proxy()->NotifyOriginNoLongerInUse(origin_in_use_
);
75 void AppCacheHost::AddObserver(Observer
* observer
) {
76 observers_
.AddObserver(observer
);
79 void AppCacheHost::RemoveObserver(Observer
* observer
) {
80 observers_
.RemoveObserver(observer
);
83 void AppCacheHost::SelectCache(const GURL
& document_url
,
84 const int64 cache_document_was_loaded_from
,
85 const GURL
& manifest_url
) {
86 DCHECK(pending_start_update_callback_
.is_null() &&
87 pending_swap_cache_callback_
.is_null() &&
88 pending_get_status_callback_
.is_null() &&
89 !is_selection_pending() && !was_select_cache_called_
);
91 was_select_cache_called_
= true;
92 if (!is_cache_selection_enabled_
) {
93 FinishCacheSelection(NULL
, NULL
);
97 origin_in_use_
= document_url
.GetOrigin();
98 if (service()->quota_manager_proxy() && !origin_in_use_
.is_empty())
99 service()->quota_manager_proxy()->NotifyOriginInUse(origin_in_use_
);
101 if (main_resource_blocked_
)
102 frontend_
->OnContentBlocked(host_id_
,
103 blocked_manifest_url_
);
105 // 6.9.6 The application cache selection algorithm.
106 // The algorithm is started here and continues in FinishCacheSelection,
107 // after cache or group loading is complete.
108 // Note: Foreign entries are detected on the client side and
109 // MarkAsForeignEntry is called in that case, so that detection
110 // step is skipped here. See WebApplicationCacheHostImpl.cc
112 if (cache_document_was_loaded_from
!= kAppCacheNoCacheId
) {
113 LoadSelectedCache(cache_document_was_loaded_from
);
117 if (!manifest_url
.is_empty() &&
118 (manifest_url
.GetOrigin() == document_url
.GetOrigin())) {
119 DCHECK(!first_party_url_
.is_empty());
120 AppCachePolicy
* policy
= service()->appcache_policy();
122 !policy
->CanCreateAppCache(manifest_url
, first_party_url_
)) {
123 FinishCacheSelection(NULL
, NULL
);
124 std::vector
<int> host_ids(1, host_id_
);
125 frontend_
->OnEventRaised(host_ids
, APPCACHE_CHECKING_EVENT
);
126 frontend_
->OnErrorEventRaised(
128 AppCacheErrorDetails(
129 "Cache creation was blocked by the content policy",
130 APPCACHE_POLICY_ERROR
,
133 false /*is_cross_origin*/));
134 frontend_
->OnContentBlocked(host_id_
, manifest_url
);
138 // Note: The client detects if the document was not loaded using HTTP GET
139 // and invokes SelectCache without a manifest url, so that detection step
140 // is also skipped here. See WebApplicationCacheHostImpl.cc
141 set_preferred_manifest_url(manifest_url
);
142 new_master_entry_url_
= document_url
;
143 LoadOrCreateGroup(manifest_url
);
147 // TODO(michaeln): If there was a manifest URL, the user agent may report
148 // to the user that it was ignored, to aid in application development.
149 FinishCacheSelection(NULL
, NULL
);
152 void AppCacheHost::SelectCacheForWorker(int parent_process_id
,
153 int parent_host_id
) {
154 DCHECK(pending_start_update_callback_
.is_null() &&
155 pending_swap_cache_callback_
.is_null() &&
156 pending_get_status_callback_
.is_null() &&
157 !is_selection_pending() && !was_select_cache_called_
);
159 was_select_cache_called_
= true;
160 parent_process_id_
= parent_process_id
;
161 parent_host_id_
= parent_host_id
;
162 FinishCacheSelection(NULL
, NULL
);
165 void AppCacheHost::SelectCacheForSharedWorker(int64 appcache_id
) {
166 DCHECK(pending_start_update_callback_
.is_null() &&
167 pending_swap_cache_callback_
.is_null() &&
168 pending_get_status_callback_
.is_null() &&
169 !is_selection_pending() && !was_select_cache_called_
);
171 was_select_cache_called_
= true;
172 if (appcache_id
!= kAppCacheNoCacheId
) {
173 LoadSelectedCache(appcache_id
);
176 FinishCacheSelection(NULL
, NULL
);
179 // TODO(michaeln): change method name to MarkEntryAsForeign for consistency
180 void AppCacheHost::MarkAsForeignEntry(const GURL
& document_url
,
181 int64 cache_document_was_loaded_from
) {
182 // The document url is not the resource url in the fallback case.
183 storage()->MarkEntryAsForeign(
184 main_resource_was_namespace_entry_
? namespace_entry_url_
: document_url
,
185 cache_document_was_loaded_from
);
186 SelectCache(document_url
, kAppCacheNoCacheId
, GURL());
189 void AppCacheHost::GetStatusWithCallback(const GetStatusCallback
& callback
,
190 void* callback_param
) {
191 DCHECK(pending_start_update_callback_
.is_null() &&
192 pending_swap_cache_callback_
.is_null() &&
193 pending_get_status_callback_
.is_null());
195 pending_get_status_callback_
= callback
;
196 pending_callback_param_
= callback_param
;
197 if (is_selection_pending())
200 DoPendingGetStatus();
203 void AppCacheHost::DoPendingGetStatus() {
204 DCHECK_EQ(false, pending_get_status_callback_
.is_null());
206 pending_get_status_callback_
.Run(GetStatus(), pending_callback_param_
);
207 pending_get_status_callback_
.Reset();
208 pending_callback_param_
= NULL
;
211 void AppCacheHost::StartUpdateWithCallback(const StartUpdateCallback
& callback
,
212 void* callback_param
) {
213 DCHECK(pending_start_update_callback_
.is_null() &&
214 pending_swap_cache_callback_
.is_null() &&
215 pending_get_status_callback_
.is_null());
217 pending_start_update_callback_
= callback
;
218 pending_callback_param_
= callback_param
;
219 if (is_selection_pending())
222 DoPendingStartUpdate();
225 void AppCacheHost::DoPendingStartUpdate() {
226 DCHECK_EQ(false, pending_start_update_callback_
.is_null());
228 // 6.9.8 Application cache API
229 bool success
= false;
230 if (associated_cache_
.get() && associated_cache_
->owning_group()) {
231 AppCacheGroup
* group
= associated_cache_
->owning_group();
232 if (!group
->is_obsolete() && !group
->is_being_deleted()) {
234 group
->StartUpdate();
238 pending_start_update_callback_
.Run(success
, pending_callback_param_
);
239 pending_start_update_callback_
.Reset();
240 pending_callback_param_
= NULL
;
243 void AppCacheHost::SwapCacheWithCallback(const SwapCacheCallback
& callback
,
244 void* callback_param
) {
245 DCHECK(pending_start_update_callback_
.is_null() &&
246 pending_swap_cache_callback_
.is_null() &&
247 pending_get_status_callback_
.is_null());
249 pending_swap_cache_callback_
= callback
;
250 pending_callback_param_
= callback_param
;
251 if (is_selection_pending())
254 DoPendingSwapCache();
257 void AppCacheHost::DoPendingSwapCache() {
258 DCHECK_EQ(false, pending_swap_cache_callback_
.is_null());
260 // 6.9.8 Application cache API
261 bool success
= false;
262 if (associated_cache_
.get() && associated_cache_
->owning_group()) {
263 if (associated_cache_
->owning_group()->is_obsolete()) {
265 AssociateNoCache(GURL());
266 } else if (swappable_cache_
.get()) {
267 DCHECK(swappable_cache_
.get() ==
268 swappable_cache_
->owning_group()->newest_complete_cache());
270 AssociateCompleteCache(swappable_cache_
.get());
274 pending_swap_cache_callback_
.Run(success
, pending_callback_param_
);
275 pending_swap_cache_callback_
.Reset();
276 pending_callback_param_
= NULL
;
279 void AppCacheHost::SetSpawningHostId(
280 int spawning_process_id
, int spawning_host_id
) {
281 spawning_process_id_
= spawning_process_id
;
282 spawning_host_id_
= spawning_host_id
;
285 const AppCacheHost
* AppCacheHost::GetSpawningHost() const {
286 AppCacheBackendImpl
* backend
= service_
->GetBackend(spawning_process_id_
);
287 return backend
? backend
->GetHost(spawning_host_id_
) : NULL
;
290 AppCacheHost
* AppCacheHost::GetParentAppCacheHost() const {
291 DCHECK(is_for_dedicated_worker());
292 AppCacheBackendImpl
* backend
= service_
->GetBackend(parent_process_id_
);
293 return backend
? backend
->GetHost(parent_host_id_
) : NULL
;
296 AppCacheRequestHandler
* AppCacheHost::CreateRequestHandler(
297 net::URLRequest
* request
,
298 ResourceType resource_type
,
299 bool should_reset_appcache
) {
300 if (is_for_dedicated_worker()) {
301 AppCacheHost
* parent_host
= GetParentAppCacheHost();
303 return parent_host
->CreateRequestHandler(
304 request
, resource_type
, should_reset_appcache
);
308 if (AppCacheRequestHandler::IsMainResourceType(resource_type
)) {
309 // Store the first party origin so that it can be used later in SelectCache
310 // for checking whether the creation of the appcache is allowed.
311 first_party_url_
= request
->first_party_for_cookies();
312 return new AppCacheRequestHandler(
313 this, resource_type
, should_reset_appcache
);
316 if ((associated_cache() && associated_cache()->is_complete()) ||
317 is_selection_pending()) {
318 return new AppCacheRequestHandler(
319 this, resource_type
, should_reset_appcache
);
324 void AppCacheHost::GetResourceList(
325 AppCacheResourceInfoVector
* resource_infos
) {
326 if (associated_cache_
.get() && associated_cache_
->is_complete())
327 associated_cache_
->ToResourceInfoVector(resource_infos
);
330 AppCacheStatus
AppCacheHost::GetStatus() {
331 // 6.9.8 Application cache API
332 AppCache
* cache
= associated_cache();
334 return APPCACHE_STATUS_UNCACHED
;
336 // A cache without an owning group represents the cache being constructed
337 // during the application cache update process.
338 if (!cache
->owning_group())
339 return APPCACHE_STATUS_DOWNLOADING
;
341 if (cache
->owning_group()->is_obsolete())
342 return APPCACHE_STATUS_OBSOLETE
;
343 if (cache
->owning_group()->update_status() == AppCacheGroup::CHECKING
)
344 return APPCACHE_STATUS_CHECKING
;
345 if (cache
->owning_group()->update_status() == AppCacheGroup::DOWNLOADING
)
346 return APPCACHE_STATUS_DOWNLOADING
;
347 if (swappable_cache_
.get())
348 return APPCACHE_STATUS_UPDATE_READY
;
349 return APPCACHE_STATUS_IDLE
;
352 void AppCacheHost::LoadOrCreateGroup(const GURL
& manifest_url
) {
353 DCHECK(manifest_url
.is_valid());
354 pending_selected_manifest_url_
= manifest_url
;
355 storage()->LoadOrCreateGroup(manifest_url
, this);
358 void AppCacheHost::OnGroupLoaded(AppCacheGroup
* group
,
359 const GURL
& manifest_url
) {
360 DCHECK(manifest_url
== pending_selected_manifest_url_
);
361 pending_selected_manifest_url_
= GURL();
362 FinishCacheSelection(NULL
, group
);
365 void AppCacheHost::LoadSelectedCache(int64 cache_id
) {
366 DCHECK(cache_id
!= kAppCacheNoCacheId
);
367 pending_selected_cache_id_
= cache_id
;
368 storage()->LoadCache(cache_id
, this);
371 void AppCacheHost::OnCacheLoaded(AppCache
* cache
, int64 cache_id
) {
372 if (cache_id
== pending_main_resource_cache_id_
) {
373 pending_main_resource_cache_id_
= kAppCacheNoCacheId
;
374 main_resource_cache_
= cache
;
375 } else if (cache_id
== pending_selected_cache_id_
) {
376 pending_selected_cache_id_
= kAppCacheNoCacheId
;
377 FinishCacheSelection(cache
, NULL
);
381 void AppCacheHost::FinishCacheSelection(
382 AppCache
*cache
, AppCacheGroup
* group
) {
383 DCHECK(!associated_cache());
385 // 6.9.6 The application cache selection algorithm
387 // If document was loaded from an application cache, Associate document
388 // with the application cache from which it was loaded. Invoke the
389 // application cache update process for that cache and with the browsing
390 // context being navigated.
391 DCHECK(cache
->owning_group());
392 DCHECK(new_master_entry_url_
.is_empty());
393 DCHECK_EQ(cache
->owning_group()->manifest_url(), preferred_manifest_url_
);
394 AppCacheGroup
* owing_group
= cache
->owning_group();
395 const char* kFormatString
=
396 "Document was loaded from Application Cache with manifest %s";
397 frontend_
->OnLogMessage(
398 host_id_
, APPCACHE_LOG_INFO
,
400 kFormatString
, owing_group
->manifest_url().spec().c_str()));
401 AssociateCompleteCache(cache
);
402 if (!owing_group
->is_obsolete() && !owing_group
->is_being_deleted()) {
403 owing_group
->StartUpdateWithHost(this);
404 ObserveGroupBeingUpdated(owing_group
);
406 } else if (group
&& !group
->is_being_deleted()) {
407 // If document was loaded using HTTP GET or equivalent, and, there is a
408 // manifest URL, and manifest URL has the same origin as document.
409 // Invoke the application cache update process for manifest URL, with
410 // the browsing context being navigated, and with document and the
411 // resource from which document was loaded as the new master resourse.
412 DCHECK(!group
->is_obsolete());
413 DCHECK(new_master_entry_url_
.is_valid());
414 DCHECK_EQ(group
->manifest_url(), preferred_manifest_url_
);
415 const char* kFormatString
= group
->HasCache() ?
416 "Adding master entry to Application Cache with manifest %s" :
417 "Creating Application Cache with manifest %s";
418 frontend_
->OnLogMessage(
419 host_id_
, APPCACHE_LOG_INFO
,
420 base::StringPrintf(kFormatString
,
421 group
->manifest_url().spec().c_str()));
422 // The UpdateJob may produce one for us later.
423 AssociateNoCache(preferred_manifest_url_
);
424 group
->StartUpdateWithNewMasterEntry(this, new_master_entry_url_
);
425 ObserveGroupBeingUpdated(group
);
427 // Otherwise, the Document is not associated with any application cache.
428 new_master_entry_url_
= GURL();
429 AssociateNoCache(GURL());
432 // Respond to pending callbacks now that we have a selection.
433 if (!pending_get_status_callback_
.is_null())
434 DoPendingGetStatus();
435 else if (!pending_start_update_callback_
.is_null())
436 DoPendingStartUpdate();
437 else if (!pending_swap_cache_callback_
.is_null())
438 DoPendingSwapCache();
440 FOR_EACH_OBSERVER(Observer
, observers_
, OnCacheSelectionComplete(this));
443 void AppCacheHost::OnServiceReinitialized(
444 AppCacheStorageReference
* old_storage_ref
) {
445 // We continue to use the disabled instance, but arrange for its
446 // deletion when its no longer needed.
447 if (old_storage_ref
->storage() == storage())
448 disabled_storage_reference_
= old_storage_ref
;
451 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup
* group
) {
452 DCHECK(!group_being_updated_
.get());
453 group_being_updated_
= group
;
454 newest_cache_of_group_being_updated_
= group
->newest_complete_cache();
455 group
->AddUpdateObserver(this);
458 void AppCacheHost::OnUpdateComplete(AppCacheGroup
* group
) {
459 DCHECK_EQ(group
, group_being_updated_
.get());
460 group
->RemoveUpdateObserver(this);
462 // Add a reference to the newest complete cache.
463 SetSwappableCache(group
);
465 group_being_updated_
= NULL
;
466 newest_cache_of_group_being_updated_
= NULL
;
468 if (associated_cache_info_pending_
&& associated_cache_
.get() &&
469 associated_cache_
->is_complete()) {
472 associated_cache_
.get(), preferred_manifest_url_
, GetStatus(), &info
);
473 associated_cache_info_pending_
= false;
474 frontend_
->OnCacheSelected(host_id_
, info
);
478 void AppCacheHost::SetSwappableCache(AppCacheGroup
* group
) {
480 swappable_cache_
= NULL
;
482 AppCache
* new_cache
= group
->newest_complete_cache();
483 if (new_cache
!= associated_cache_
.get())
484 swappable_cache_
= new_cache
;
486 swappable_cache_
= NULL
;
490 void AppCacheHost::LoadMainResourceCache(int64 cache_id
) {
491 DCHECK(cache_id
!= kAppCacheNoCacheId
);
492 if (pending_main_resource_cache_id_
== cache_id
||
493 (main_resource_cache_
.get() &&
494 main_resource_cache_
->cache_id() == cache_id
)) {
497 pending_main_resource_cache_id_
= cache_id
;
498 storage()->LoadCache(cache_id
, this);
501 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
502 const GURL
& namespace_entry_url
) {
503 main_resource_was_namespace_entry_
= true;
504 namespace_entry_url_
= namespace_entry_url
;
507 void AppCacheHost::NotifyMainResourceBlocked(const GURL
& manifest_url
) {
508 main_resource_blocked_
= true;
509 blocked_manifest_url_
= manifest_url
;
512 void AppCacheHost::PrepareForTransfer() {
513 // This can only happen prior to the document having been loaded.
514 DCHECK(!associated_cache());
515 DCHECK(!is_selection_pending());
516 DCHECK(!group_being_updated_
.get());
517 host_id_
= kAppCacheNoHostId
;
521 void AppCacheHost::CompleteTransfer(int host_id
, AppCacheFrontend
* frontend
) {
523 frontend_
= frontend
;
526 void AppCacheHost::AssociateNoCache(const GURL
& manifest_url
) {
527 // manifest url can be empty.
528 AssociateCacheHelper(NULL
, manifest_url
);
531 void AppCacheHost::AssociateIncompleteCache(AppCache
* cache
,
532 const GURL
& manifest_url
) {
533 DCHECK(cache
&& !cache
->is_complete());
534 DCHECK(!manifest_url
.is_empty());
535 AssociateCacheHelper(cache
, manifest_url
);
538 void AppCacheHost::AssociateCompleteCache(AppCache
* cache
) {
539 DCHECK(cache
&& cache
->is_complete());
540 AssociateCacheHelper(cache
, cache
->owning_group()->manifest_url());
543 void AppCacheHost::AssociateCacheHelper(AppCache
* cache
,
544 const GURL
& manifest_url
) {
545 if (associated_cache_
.get()) {
546 associated_cache_
->UnassociateHost(this);
549 associated_cache_
= cache
;
550 SetSwappableCache(cache
? cache
->owning_group() : NULL
);
551 associated_cache_info_pending_
= cache
&& !cache
->is_complete();
554 cache
->AssociateHost(this);
556 FillCacheInfo(cache
, manifest_url
, GetStatus(), &info
);
557 frontend_
->OnCacheSelected(host_id_
, info
);
560 } // namespace content