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