OLD | NEW |
| (Empty) |
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 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 | |
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 | |
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 != 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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
275 const AppCacheHost* AppCacheHost::GetSpawningHost() const { | |
276 AppCacheBackendImpl* backend = service_->GetBackend(spawning_process_id_); | |
277 return backend ? backend->GetHost(spawning_host_id_) : NULL; | |
278 } | |
279 | |
280 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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
493 void AppCacheHost::NotifyMainResourceBlocked(const GURL& manifest_url) { | |
494 main_resource_blocked_ = true; | |
495 blocked_manifest_url_ = manifest_url; | |
496 } | |
497 | |
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 | |
507 void AppCacheHost::CompleteTransfer(int host_id, AppCacheFrontend* frontend) { | |
508 host_id_ = host_id; | |
509 frontend_ = frontend; | |
510 } | |
511 | |
512 void AppCacheHost::AssociateNoCache(const GURL& manifest_url) { | |
513 // manifest url can be empty. | |
514 AssociateCacheHelper(NULL, manifest_url); | |
515 } | |
516 | |
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 | |
524 void AppCacheHost::AssociateCompleteCache(AppCache* cache) { | |
525 DCHECK(cache && cache->is_complete()); | |
526 AssociateCacheHelper(cache, cache->owning_group()->manifest_url()); | |
527 } | |
528 | |
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 | |
OLD | NEW |