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