• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "content/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 "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"
16 
17 namespace content {
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 resource_type)286 AppCacheRequestHandler* AppCacheHost::CreateRequestHandler(
287     net::URLRequest* request,
288     ResourceType 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_.get());
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_.get());
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 content
547