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