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 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
);
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
);
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();
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(
126 AppCacheErrorDetails(
127 "Cache creation was blocked by the content policy",
128 APPCACHE_POLICY_ERROR
,
131 false /*is_cross_origin*/));
132 frontend_
->OnContentBlocked(host_id_
, manifest_url
);
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
);
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
);
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())
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())
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()) {
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())
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()) {
261 AssociateNoCache(GURL());
262 } else if (swappable_cache_
.get()) {
263 DCHECK(swappable_cache_
.get() ==
264 swappable_cache_
->owning_group()->newest_complete_cache());
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 if (is_for_dedicated_worker()) {
296 AppCacheHost
* parent_host
= GetParentAppCacheHost();
298 return parent_host
->CreateRequestHandler(request
, resource_type
);
302 if (AppCacheRequestHandler::IsMainResourceType(resource_type
)) {
303 // Store the first party origin so that it can be used later in SelectCache
304 // for checking whether the creation of the appcache is allowed.
305 first_party_url_
= request
->first_party_for_cookies();
306 return new AppCacheRequestHandler(this, resource_type
);
309 if ((associated_cache() && associated_cache()->is_complete()) ||
310 is_selection_pending()) {
311 return new AppCacheRequestHandler(this, resource_type
);
316 void AppCacheHost::GetResourceList(
317 AppCacheResourceInfoVector
* resource_infos
) {
318 if (associated_cache_
.get() && associated_cache_
->is_complete())
319 associated_cache_
->ToResourceInfoVector(resource_infos
);
322 AppCacheStatus
AppCacheHost::GetStatus() {
323 // 6.9.8 Application cache API
324 AppCache
* cache
= associated_cache();
326 return APPCACHE_STATUS_UNCACHED
;
328 // A cache without an owning group represents the cache being constructed
329 // during the application cache update process.
330 if (!cache
->owning_group())
331 return APPCACHE_STATUS_DOWNLOADING
;
333 if (cache
->owning_group()->is_obsolete())
334 return APPCACHE_STATUS_OBSOLETE
;
335 if (cache
->owning_group()->update_status() == AppCacheGroup::CHECKING
)
336 return APPCACHE_STATUS_CHECKING
;
337 if (cache
->owning_group()->update_status() == AppCacheGroup::DOWNLOADING
)
338 return APPCACHE_STATUS_DOWNLOADING
;
339 if (swappable_cache_
.get())
340 return APPCACHE_STATUS_UPDATE_READY
;
341 return APPCACHE_STATUS_IDLE
;
344 void AppCacheHost::LoadOrCreateGroup(const GURL
& manifest_url
) {
345 DCHECK(manifest_url
.is_valid());
346 pending_selected_manifest_url_
= manifest_url
;
347 storage()->LoadOrCreateGroup(manifest_url
, this);
350 void AppCacheHost::OnGroupLoaded(AppCacheGroup
* group
,
351 const GURL
& manifest_url
) {
352 DCHECK(manifest_url
== pending_selected_manifest_url_
);
353 pending_selected_manifest_url_
= GURL();
354 FinishCacheSelection(NULL
, group
);
357 void AppCacheHost::LoadSelectedCache(int64 cache_id
) {
358 DCHECK(cache_id
!= kAppCacheNoCacheId
);
359 pending_selected_cache_id_
= cache_id
;
360 storage()->LoadCache(cache_id
, this);
363 void AppCacheHost::OnCacheLoaded(AppCache
* cache
, int64 cache_id
) {
364 if (cache_id
== pending_main_resource_cache_id_
) {
365 pending_main_resource_cache_id_
= kAppCacheNoCacheId
;
366 main_resource_cache_
= cache
;
367 } else if (cache_id
== pending_selected_cache_id_
) {
368 pending_selected_cache_id_
= kAppCacheNoCacheId
;
369 FinishCacheSelection(cache
, NULL
);
373 void AppCacheHost::FinishCacheSelection(
374 AppCache
*cache
, AppCacheGroup
* group
) {
375 DCHECK(!associated_cache());
377 // 6.9.6 The application cache selection algorithm
379 // If document was loaded from an application cache, Associate document
380 // with the application cache from which it was loaded. Invoke the
381 // application cache update process for that cache and with the browsing
382 // context being navigated.
383 DCHECK(cache
->owning_group());
384 DCHECK(new_master_entry_url_
.is_empty());
385 DCHECK_EQ(cache
->owning_group()->manifest_url(), preferred_manifest_url_
);
386 AppCacheGroup
* owing_group
= cache
->owning_group();
387 const char* kFormatString
=
388 "Document was loaded from Application Cache with manifest %s";
389 frontend_
->OnLogMessage(
390 host_id_
, APPCACHE_LOG_INFO
,
392 kFormatString
, owing_group
->manifest_url().spec().c_str()));
393 AssociateCompleteCache(cache
);
394 if (!owing_group
->is_obsolete() && !owing_group
->is_being_deleted()) {
395 owing_group
->StartUpdateWithHost(this);
396 ObserveGroupBeingUpdated(owing_group
);
398 } else if (group
&& !group
->is_being_deleted()) {
399 // If document was loaded using HTTP GET or equivalent, and, there is a
400 // manifest URL, and manifest URL has the same origin as document.
401 // Invoke the application cache update process for manifest URL, with
402 // the browsing context being navigated, and with document and the
403 // resource from which document was loaded as the new master resourse.
404 DCHECK(!group
->is_obsolete());
405 DCHECK(new_master_entry_url_
.is_valid());
406 DCHECK_EQ(group
->manifest_url(), preferred_manifest_url_
);
407 const char* kFormatString
= group
->HasCache() ?
408 "Adding master entry to Application Cache with manifest %s" :
409 "Creating Application Cache with manifest %s";
410 frontend_
->OnLogMessage(
411 host_id_
, APPCACHE_LOG_INFO
,
412 base::StringPrintf(kFormatString
,
413 group
->manifest_url().spec().c_str()));
414 // The UpdateJob may produce one for us later.
415 AssociateNoCache(preferred_manifest_url_
);
416 group
->StartUpdateWithNewMasterEntry(this, new_master_entry_url_
);
417 ObserveGroupBeingUpdated(group
);
419 // Otherwise, the Document is not associated with any application cache.
420 new_master_entry_url_
= GURL();
421 AssociateNoCache(GURL());
424 // Respond to pending callbacks now that we have a selection.
425 if (!pending_get_status_callback_
.is_null())
426 DoPendingGetStatus();
427 else if (!pending_start_update_callback_
.is_null())
428 DoPendingStartUpdate();
429 else if (!pending_swap_cache_callback_
.is_null())
430 DoPendingSwapCache();
432 FOR_EACH_OBSERVER(Observer
, observers_
, OnCacheSelectionComplete(this));
435 void AppCacheHost::OnServiceReinitialized(
436 AppCacheStorageReference
* old_storage_ref
) {
437 // We continue to use the disabled instance, but arrange for its
438 // deletion when its no longer needed.
439 if (old_storage_ref
->storage() == storage())
440 disabled_storage_reference_
= old_storage_ref
;
443 void AppCacheHost::ObserveGroupBeingUpdated(AppCacheGroup
* group
) {
444 DCHECK(!group_being_updated_
.get());
445 group_being_updated_
= group
;
446 newest_cache_of_group_being_updated_
= group
->newest_complete_cache();
447 group
->AddUpdateObserver(this);
450 void AppCacheHost::OnUpdateComplete(AppCacheGroup
* group
) {
451 DCHECK_EQ(group
, group_being_updated_
.get());
452 group
->RemoveUpdateObserver(this);
454 // Add a reference to the newest complete cache.
455 SetSwappableCache(group
);
457 group_being_updated_
= NULL
;
458 newest_cache_of_group_being_updated_
= NULL
;
460 if (associated_cache_info_pending_
&& associated_cache_
.get() &&
461 associated_cache_
->is_complete()) {
464 associated_cache_
.get(), preferred_manifest_url_
, GetStatus(), &info
);
465 associated_cache_info_pending_
= false;
466 frontend_
->OnCacheSelected(host_id_
, info
);
470 void AppCacheHost::SetSwappableCache(AppCacheGroup
* group
) {
472 swappable_cache_
= NULL
;
474 AppCache
* new_cache
= group
->newest_complete_cache();
475 if (new_cache
!= associated_cache_
.get())
476 swappable_cache_
= new_cache
;
478 swappable_cache_
= NULL
;
482 void AppCacheHost::LoadMainResourceCache(int64 cache_id
) {
483 DCHECK(cache_id
!= kAppCacheNoCacheId
);
484 if (pending_main_resource_cache_id_
== cache_id
||
485 (main_resource_cache_
.get() &&
486 main_resource_cache_
->cache_id() == cache_id
)) {
489 pending_main_resource_cache_id_
= cache_id
;
490 storage()->LoadCache(cache_id
, this);
493 void AppCacheHost::NotifyMainResourceIsNamespaceEntry(
494 const GURL
& namespace_entry_url
) {
495 main_resource_was_namespace_entry_
= true;
496 namespace_entry_url_
= namespace_entry_url
;
499 void AppCacheHost::NotifyMainResourceBlocked(const GURL
& manifest_url
) {
500 main_resource_blocked_
= true;
501 blocked_manifest_url_
= manifest_url
;
504 void AppCacheHost::PrepareForTransfer() {
505 // This can only happen prior to the document having been loaded.
506 DCHECK(!associated_cache());
507 DCHECK(!is_selection_pending());
508 DCHECK(!group_being_updated_
.get());
509 host_id_
= kAppCacheNoHostId
;
513 void AppCacheHost::CompleteTransfer(int host_id
, AppCacheFrontend
* frontend
) {
515 frontend_
= frontend
;
518 void AppCacheHost::AssociateNoCache(const GURL
& manifest_url
) {
519 // manifest url can be empty.
520 AssociateCacheHelper(NULL
, manifest_url
);
523 void AppCacheHost::AssociateIncompleteCache(AppCache
* cache
,
524 const GURL
& manifest_url
) {
525 DCHECK(cache
&& !cache
->is_complete());
526 DCHECK(!manifest_url
.is_empty());
527 AssociateCacheHelper(cache
, manifest_url
);
530 void AppCacheHost::AssociateCompleteCache(AppCache
* cache
) {
531 DCHECK(cache
&& cache
->is_complete());
532 AssociateCacheHelper(cache
, cache
->owning_group()->manifest_url());
535 void AppCacheHost::AssociateCacheHelper(AppCache
* cache
,
536 const GURL
& manifest_url
) {
537 if (associated_cache_
.get()) {
538 associated_cache_
->UnassociateHost(this);
541 associated_cache_
= cache
;
542 SetSwappableCache(cache
? cache
->owning_group() : NULL
);
543 associated_cache_info_pending_
= cache
&& !cache
->is_complete();
546 cache
->AssociateHost(this);
548 FillCacheInfo(cache
, manifest_url
, GetStatus(), &info
);
549 frontend_
->OnCacheSelected(host_id_
, info
);
552 } // namespace content