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"
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 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
);
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();
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
);
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
);
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
);
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())
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())
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()) {
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())
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()) {
246 AssociateNoCache(GURL());
247 } else if (swappable_cache_
) {
248 DCHECK(swappable_cache_
.get() ==
249 swappable_cache_
->owning_group()->newest_complete_cache());
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();
283 return parent_host
->CreateRequestHandler(request
, resource_type
);
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
);
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();
313 // A cache without an owning group represents the cache being constructed
314 // during the application cache update process.
315 if (!cache
->owning_group())
318 if (cache
->owning_group()->is_obsolete())
320 if (cache
->owning_group()->update_status() == AppCacheGroup::CHECKING
)
322 if (cache
->owning_group()->update_status() == AppCacheGroup::DOWNLOADING
)
324 if (swappable_cache_
)
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
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(
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(
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
);
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()) {
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
) {
449 swappable_cache_
= NULL
;
451 AppCache
* new_cache
= group
->newest_complete_cache();
452 if (new_cache
!= associated_cache_
)
453 swappable_cache_
= new_cache
;
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
)) {
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();
508 cache
->AssociateHost(this);
510 FillCacheInfo(cache
, manifest_url
, GetStatus(), &info
);
511 frontend_
->OnCacheSelected(host_id_
, info
);
514 } // namespace appcache