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