OLD | NEW |
| (Empty) |
1 // Copyright 2014 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_service_impl.h" | |
6 | |
7 #include <functional> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/bind_helpers.h" | |
11 #include "base/logging.h" | |
12 #include "base/message_loop/message_loop.h" | |
13 #include "base/stl_util.h" | |
14 #include "net/base/completion_callback.h" | |
15 #include "net/base/io_buffer.h" | |
16 #include "webkit/browser/appcache/appcache.h" | |
17 #include "webkit/browser/appcache/appcache_backend_impl.h" | |
18 #include "webkit/browser/appcache/appcache_entry.h" | |
19 #include "webkit/browser/appcache/appcache_executable_handler.h" | |
20 #include "webkit/browser/appcache/appcache_histograms.h" | |
21 #include "webkit/browser/appcache/appcache_policy.h" | |
22 #include "webkit/browser/appcache/appcache_quota_client.h" | |
23 #include "webkit/browser/appcache/appcache_response.h" | |
24 #include "webkit/browser/appcache/appcache_service_impl.h" | |
25 #include "webkit/browser/appcache/appcache_storage_impl.h" | |
26 #include "webkit/browser/quota/special_storage_policy.h" | |
27 | |
28 namespace appcache { | |
29 | |
30 namespace { | |
31 | |
32 void DeferredCallback(const net::CompletionCallback& callback, int rv) { | |
33 callback.Run(rv); | |
34 } | |
35 | |
36 } // namespace | |
37 | |
38 AppCacheInfoCollection::AppCacheInfoCollection() {} | |
39 | |
40 AppCacheInfoCollection::~AppCacheInfoCollection() {} | |
41 | |
42 // AsyncHelper ------- | |
43 | |
44 class AppCacheServiceImpl::AsyncHelper | |
45 : public AppCacheStorage::Delegate { | |
46 public: | |
47 AsyncHelper(AppCacheServiceImpl* service, | |
48 const net::CompletionCallback& callback) | |
49 : service_(service), callback_(callback) { | |
50 service_->pending_helpers_.insert(this); | |
51 } | |
52 | |
53 virtual ~AsyncHelper() { | |
54 if (service_) | |
55 service_->pending_helpers_.erase(this); | |
56 } | |
57 | |
58 virtual void Start() = 0; | |
59 virtual void Cancel(); | |
60 | |
61 protected: | |
62 void CallCallback(int rv) { | |
63 if (!callback_.is_null()) { | |
64 // Defer to guarantee async completion. | |
65 base::MessageLoop::current()->PostTask( | |
66 FROM_HERE, base::Bind(&DeferredCallback, callback_, rv)); | |
67 } | |
68 callback_.Reset(); | |
69 } | |
70 | |
71 AppCacheServiceImpl* service_; | |
72 net::CompletionCallback callback_; | |
73 }; | |
74 | |
75 void AppCacheServiceImpl::AsyncHelper::Cancel() { | |
76 if (!callback_.is_null()) { | |
77 callback_.Run(net::ERR_ABORTED); | |
78 callback_.Reset(); | |
79 } | |
80 service_->storage()->CancelDelegateCallbacks(this); | |
81 service_ = NULL; | |
82 } | |
83 | |
84 // CanHandleOfflineHelper ------- | |
85 | |
86 class AppCacheServiceImpl::CanHandleOfflineHelper : AsyncHelper { | |
87 public: | |
88 CanHandleOfflineHelper( | |
89 AppCacheServiceImpl* service, const GURL& url, | |
90 const GURL& first_party, const net::CompletionCallback& callback) | |
91 : AsyncHelper(service, callback), | |
92 url_(url), | |
93 first_party_(first_party) { | |
94 } | |
95 | |
96 virtual void Start() OVERRIDE { | |
97 AppCachePolicy* policy = service_->appcache_policy(); | |
98 if (policy && !policy->CanLoadAppCache(url_, first_party_)) { | |
99 CallCallback(net::ERR_FAILED); | |
100 delete this; | |
101 return; | |
102 } | |
103 | |
104 service_->storage()->FindResponseForMainRequest(url_, GURL(), this); | |
105 } | |
106 | |
107 private: | |
108 // AppCacheStorage::Delegate implementation. | |
109 virtual void OnMainResponseFound( | |
110 const GURL& url, const AppCacheEntry& entry, | |
111 const GURL& fallback_url, const AppCacheEntry& fallback_entry, | |
112 int64 cache_id, int64 group_id, const GURL& mainfest_url) OVERRIDE; | |
113 | |
114 GURL url_; | |
115 GURL first_party_; | |
116 | |
117 DISALLOW_COPY_AND_ASSIGN(CanHandleOfflineHelper); | |
118 }; | |
119 | |
120 void AppCacheServiceImpl::CanHandleOfflineHelper::OnMainResponseFound( | |
121 const GURL& url, const AppCacheEntry& entry, | |
122 const GURL& fallback_url, const AppCacheEntry& fallback_entry, | |
123 int64 cache_id, int64 group_id, const GURL& manifest_url) { | |
124 bool can = (entry.has_response_id() || fallback_entry.has_response_id()); | |
125 CallCallback(can ? net::OK : net::ERR_FAILED); | |
126 delete this; | |
127 } | |
128 | |
129 // DeleteHelper ------- | |
130 | |
131 class AppCacheServiceImpl::DeleteHelper : public AsyncHelper { | |
132 public: | |
133 DeleteHelper( | |
134 AppCacheServiceImpl* service, const GURL& manifest_url, | |
135 const net::CompletionCallback& callback) | |
136 : AsyncHelper(service, callback), manifest_url_(manifest_url) { | |
137 } | |
138 | |
139 virtual void Start() OVERRIDE { | |
140 service_->storage()->LoadOrCreateGroup(manifest_url_, this); | |
141 } | |
142 | |
143 private: | |
144 // AppCacheStorage::Delegate implementation. | |
145 virtual void OnGroupLoaded( | |
146 appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; | |
147 virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group, | |
148 bool success, | |
149 int response_code) OVERRIDE; | |
150 | |
151 GURL manifest_url_; | |
152 DISALLOW_COPY_AND_ASSIGN(DeleteHelper); | |
153 }; | |
154 | |
155 void AppCacheServiceImpl::DeleteHelper::OnGroupLoaded( | |
156 appcache::AppCacheGroup* group, const GURL& manifest_url) { | |
157 if (group) { | |
158 group->set_being_deleted(true); | |
159 group->CancelUpdate(); | |
160 service_->storage()->MakeGroupObsolete(group, this, 0); | |
161 } else { | |
162 CallCallback(net::ERR_FAILED); | |
163 delete this; | |
164 } | |
165 } | |
166 | |
167 void AppCacheServiceImpl::DeleteHelper::OnGroupMadeObsolete( | |
168 appcache::AppCacheGroup* group, | |
169 bool success, | |
170 int response_code) { | |
171 CallCallback(success ? net::OK : net::ERR_FAILED); | |
172 delete this; | |
173 } | |
174 | |
175 // DeleteOriginHelper ------- | |
176 | |
177 class AppCacheServiceImpl::DeleteOriginHelper : public AsyncHelper { | |
178 public: | |
179 DeleteOriginHelper( | |
180 AppCacheServiceImpl* service, const GURL& origin, | |
181 const net::CompletionCallback& callback) | |
182 : AsyncHelper(service, callback), origin_(origin), | |
183 num_caches_to_delete_(0), successes_(0), failures_(0) { | |
184 } | |
185 | |
186 virtual void Start() OVERRIDE { | |
187 // We start by listing all caches, continues in OnAllInfo(). | |
188 service_->storage()->GetAllInfo(this); | |
189 } | |
190 | |
191 private: | |
192 // AppCacheStorage::Delegate implementation. | |
193 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; | |
194 virtual void OnGroupLoaded( | |
195 appcache::AppCacheGroup* group, const GURL& manifest_url) OVERRIDE; | |
196 virtual void OnGroupMadeObsolete(appcache::AppCacheGroup* group, | |
197 bool success, | |
198 int response_code) OVERRIDE; | |
199 | |
200 void CacheCompleted(bool success); | |
201 | |
202 GURL origin_; | |
203 int num_caches_to_delete_; | |
204 int successes_; | |
205 int failures_; | |
206 | |
207 DISALLOW_COPY_AND_ASSIGN(DeleteOriginHelper); | |
208 }; | |
209 | |
210 void AppCacheServiceImpl::DeleteOriginHelper::OnAllInfo( | |
211 AppCacheInfoCollection* collection) { | |
212 if (!collection) { | |
213 // Failed to get a listing. | |
214 CallCallback(net::ERR_FAILED); | |
215 delete this; | |
216 return; | |
217 } | |
218 | |
219 std::map<GURL, AppCacheInfoVector>::iterator found = | |
220 collection->infos_by_origin.find(origin_); | |
221 if (found == collection->infos_by_origin.end() || found->second.empty()) { | |
222 // No caches for this origin. | |
223 CallCallback(net::OK); | |
224 delete this; | |
225 return; | |
226 } | |
227 | |
228 // We have some caches to delete. | |
229 const AppCacheInfoVector& caches_to_delete = found->second; | |
230 successes_ = 0; | |
231 failures_ = 0; | |
232 num_caches_to_delete_ = static_cast<int>(caches_to_delete.size()); | |
233 for (AppCacheInfoVector::const_iterator iter = caches_to_delete.begin(); | |
234 iter != caches_to_delete.end(); ++iter) { | |
235 service_->storage()->LoadOrCreateGroup(iter->manifest_url, this); | |
236 } | |
237 } | |
238 | |
239 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupLoaded( | |
240 appcache::AppCacheGroup* group, const GURL& manifest_url) { | |
241 if (group) { | |
242 group->set_being_deleted(true); | |
243 group->CancelUpdate(); | |
244 service_->storage()->MakeGroupObsolete(group, this, 0); | |
245 } else { | |
246 CacheCompleted(false); | |
247 } | |
248 } | |
249 | |
250 void AppCacheServiceImpl::DeleteOriginHelper::OnGroupMadeObsolete( | |
251 appcache::AppCacheGroup* group, | |
252 bool success, | |
253 int response_code) { | |
254 CacheCompleted(success); | |
255 } | |
256 | |
257 void AppCacheServiceImpl::DeleteOriginHelper::CacheCompleted(bool success) { | |
258 if (success) | |
259 ++successes_; | |
260 else | |
261 ++failures_; | |
262 if ((successes_ + failures_) < num_caches_to_delete_) | |
263 return; | |
264 | |
265 CallCallback(!failures_ ? net::OK : net::ERR_FAILED); | |
266 delete this; | |
267 } | |
268 | |
269 | |
270 // GetInfoHelper ------- | |
271 | |
272 class AppCacheServiceImpl::GetInfoHelper : AsyncHelper { | |
273 public: | |
274 GetInfoHelper( | |
275 AppCacheServiceImpl* service, AppCacheInfoCollection* collection, | |
276 const net::CompletionCallback& callback) | |
277 : AsyncHelper(service, callback), collection_(collection) { | |
278 } | |
279 | |
280 virtual void Start() OVERRIDE { | |
281 service_->storage()->GetAllInfo(this); | |
282 } | |
283 | |
284 private: | |
285 // AppCacheStorage::Delegate implementation. | |
286 virtual void OnAllInfo(AppCacheInfoCollection* collection) OVERRIDE; | |
287 | |
288 scoped_refptr<AppCacheInfoCollection> collection_; | |
289 | |
290 DISALLOW_COPY_AND_ASSIGN(GetInfoHelper); | |
291 }; | |
292 | |
293 void AppCacheServiceImpl::GetInfoHelper::OnAllInfo( | |
294 AppCacheInfoCollection* collection) { | |
295 if (collection) | |
296 collection->infos_by_origin.swap(collection_->infos_by_origin); | |
297 CallCallback(collection ? net::OK : net::ERR_FAILED); | |
298 delete this; | |
299 } | |
300 | |
301 // CheckResponseHelper ------- | |
302 | |
303 class AppCacheServiceImpl::CheckResponseHelper : AsyncHelper { | |
304 public: | |
305 CheckResponseHelper( | |
306 AppCacheServiceImpl* service, const GURL& manifest_url, int64 cache_id, | |
307 int64 response_id) | |
308 : AsyncHelper(service, net::CompletionCallback()), | |
309 manifest_url_(manifest_url), | |
310 cache_id_(cache_id), | |
311 response_id_(response_id), | |
312 kIOBufferSize(32 * 1024), | |
313 expected_total_size_(0), | |
314 amount_headers_read_(0), | |
315 amount_data_read_(0) { | |
316 } | |
317 | |
318 virtual void Start() OVERRIDE { | |
319 service_->storage()->LoadOrCreateGroup(manifest_url_, this); | |
320 } | |
321 | |
322 virtual void Cancel() OVERRIDE { | |
323 AppCacheHistograms::CountCheckResponseResult( | |
324 AppCacheHistograms::CHECK_CANCELED); | |
325 response_reader_.reset(); | |
326 AsyncHelper::Cancel(); | |
327 } | |
328 | |
329 private: | |
330 virtual void OnGroupLoaded(AppCacheGroup* group, | |
331 const GURL& manifest_url) OVERRIDE; | |
332 void OnReadInfoComplete(int result); | |
333 void OnReadDataComplete(int result); | |
334 | |
335 // Inputs describing what to check. | |
336 GURL manifest_url_; | |
337 int64 cache_id_; | |
338 int64 response_id_; | |
339 | |
340 // Internals used to perform the checks. | |
341 const int kIOBufferSize; | |
342 scoped_refptr<AppCache> cache_; | |
343 scoped_ptr<AppCacheResponseReader> response_reader_; | |
344 scoped_refptr<HttpResponseInfoIOBuffer> info_buffer_; | |
345 scoped_refptr<net::IOBuffer> data_buffer_; | |
346 int64 expected_total_size_; | |
347 int amount_headers_read_; | |
348 int amount_data_read_; | |
349 DISALLOW_COPY_AND_ASSIGN(CheckResponseHelper); | |
350 }; | |
351 | |
352 void AppCacheServiceImpl::CheckResponseHelper::OnGroupLoaded( | |
353 AppCacheGroup* group, const GURL& manifest_url) { | |
354 DCHECK_EQ(manifest_url_, manifest_url); | |
355 if (!group || !group->newest_complete_cache() || group->is_being_deleted() || | |
356 group->is_obsolete()) { | |
357 AppCacheHistograms::CountCheckResponseResult( | |
358 AppCacheHistograms::MANIFEST_OUT_OF_DATE); | |
359 delete this; | |
360 return; | |
361 } | |
362 | |
363 cache_ = group->newest_complete_cache(); | |
364 const AppCacheEntry* entry = cache_->GetEntryWithResponseId(response_id_); | |
365 if (!entry) { | |
366 if (cache_->cache_id() == cache_id_) { | |
367 AppCacheHistograms::CountCheckResponseResult( | |
368 AppCacheHistograms::ENTRY_NOT_FOUND); | |
369 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); | |
370 } else { | |
371 AppCacheHistograms::CountCheckResponseResult( | |
372 AppCacheHistograms::RESPONSE_OUT_OF_DATE); | |
373 } | |
374 delete this; | |
375 return; | |
376 } | |
377 | |
378 // Verify that we can read the response info and data. | |
379 expected_total_size_ = entry->response_size(); | |
380 response_reader_.reset(service_->storage()->CreateResponseReader( | |
381 manifest_url_, group->group_id(), response_id_)); | |
382 info_buffer_ = new HttpResponseInfoIOBuffer(); | |
383 response_reader_->ReadInfo( | |
384 info_buffer_.get(), | |
385 base::Bind(&CheckResponseHelper::OnReadInfoComplete, | |
386 base::Unretained(this))); | |
387 } | |
388 | |
389 void AppCacheServiceImpl::CheckResponseHelper::OnReadInfoComplete(int result) { | |
390 if (result < 0) { | |
391 AppCacheHistograms::CountCheckResponseResult( | |
392 AppCacheHistograms::READ_HEADERS_ERROR); | |
393 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); | |
394 delete this; | |
395 return; | |
396 } | |
397 amount_headers_read_ = result; | |
398 | |
399 // Start reading the data. | |
400 data_buffer_ = new net::IOBuffer(kIOBufferSize); | |
401 response_reader_->ReadData( | |
402 data_buffer_.get(), | |
403 kIOBufferSize, | |
404 base::Bind(&CheckResponseHelper::OnReadDataComplete, | |
405 base::Unretained(this))); | |
406 } | |
407 | |
408 void AppCacheServiceImpl::CheckResponseHelper::OnReadDataComplete(int result) { | |
409 if (result > 0) { | |
410 // Keep reading until we've read thru everything or failed to read. | |
411 amount_data_read_ += result; | |
412 response_reader_->ReadData( | |
413 data_buffer_.get(), | |
414 kIOBufferSize, | |
415 base::Bind(&CheckResponseHelper::OnReadDataComplete, | |
416 base::Unretained(this))); | |
417 return; | |
418 } | |
419 | |
420 AppCacheHistograms::CheckResponseResultType check_result; | |
421 if (result < 0) | |
422 check_result = AppCacheHistograms::READ_DATA_ERROR; | |
423 else if (info_buffer_->response_data_size != amount_data_read_ || | |
424 expected_total_size_ != amount_data_read_ + amount_headers_read_) | |
425 check_result = AppCacheHistograms::UNEXPECTED_DATA_SIZE; | |
426 else | |
427 check_result = AppCacheHistograms::RESPONSE_OK; | |
428 AppCacheHistograms::CountCheckResponseResult(check_result); | |
429 | |
430 if (check_result != AppCacheHistograms::RESPONSE_OK) | |
431 service_->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback()); | |
432 delete this; | |
433 } | |
434 | |
435 // AppCacheStorageReference ------ | |
436 | |
437 AppCacheStorageReference::AppCacheStorageReference( | |
438 scoped_ptr<AppCacheStorage> storage) | |
439 : storage_(storage.Pass()) {} | |
440 AppCacheStorageReference::~AppCacheStorageReference() {} | |
441 | |
442 // AppCacheServiceImpl ------- | |
443 | |
444 AppCacheServiceImpl::AppCacheServiceImpl(quota::QuotaManagerProxy* | |
445 quota_manager_proxy) | |
446 : appcache_policy_(NULL), quota_client_(NULL), handler_factory_(NULL), | |
447 quota_manager_proxy_(quota_manager_proxy), | |
448 request_context_(NULL), | |
449 force_keep_session_state_(false) { | |
450 if (quota_manager_proxy_.get()) { | |
451 quota_client_ = new AppCacheQuotaClient(this); | |
452 quota_manager_proxy_->RegisterClient(quota_client_); | |
453 } | |
454 } | |
455 | |
456 AppCacheServiceImpl::~AppCacheServiceImpl() { | |
457 DCHECK(backends_.empty()); | |
458 std::for_each(pending_helpers_.begin(), | |
459 pending_helpers_.end(), | |
460 std::mem_fun(&AsyncHelper::Cancel)); | |
461 STLDeleteElements(&pending_helpers_); | |
462 if (quota_client_) | |
463 quota_client_->NotifyAppCacheDestroyed(); | |
464 | |
465 // Destroy storage_ first; ~AppCacheStorageImpl accesses other data members | |
466 // (special_storage_policy_). | |
467 storage_.reset(); | |
468 } | |
469 | |
470 void AppCacheServiceImpl::Initialize(const base::FilePath& cache_directory, | |
471 base::MessageLoopProxy* db_thread, | |
472 base::MessageLoopProxy* cache_thread) { | |
473 DCHECK(!storage_.get()); | |
474 cache_directory_ = cache_directory; | |
475 db_thread_ = db_thread; | |
476 cache_thread_ = cache_thread; | |
477 AppCacheStorageImpl* storage = new AppCacheStorageImpl(this); | |
478 storage->Initialize(cache_directory, db_thread, cache_thread); | |
479 storage_.reset(storage); | |
480 } | |
481 | |
482 void AppCacheServiceImpl::ScheduleReinitialize() { | |
483 if (reinit_timer_.IsRunning()) | |
484 return; | |
485 | |
486 // Reinitialization only happens when corruption has been noticed. | |
487 // We don't want to thrash the disk but we also don't want to | |
488 // leave the appcache disabled for an indefinite period of time. Some | |
489 // users never shutdown the browser. | |
490 | |
491 const base::TimeDelta kZeroDelta; | |
492 const base::TimeDelta kOneHour(base::TimeDelta::FromHours(1)); | |
493 const base::TimeDelta k30Seconds(base::TimeDelta::FromSeconds(30)); | |
494 | |
495 // If the system managed to stay up for long enough, reset the | |
496 // delay so a new failure won't incur a long wait to get going again. | |
497 base::TimeDelta up_time = base::Time::Now() - last_reinit_time_; | |
498 if (next_reinit_delay_ != kZeroDelta && up_time > kOneHour) | |
499 next_reinit_delay_ = kZeroDelta; | |
500 | |
501 reinit_timer_.Start(FROM_HERE, next_reinit_delay_, | |
502 this, &AppCacheServiceImpl::Reinitialize); | |
503 | |
504 // Adjust the delay for next time. | |
505 base::TimeDelta increment = std::max(k30Seconds, next_reinit_delay_); | |
506 next_reinit_delay_ = std::min(next_reinit_delay_ + increment, kOneHour); | |
507 } | |
508 | |
509 void AppCacheServiceImpl::Reinitialize() { | |
510 AppCacheHistograms::CountReinitAttempt(!last_reinit_time_.is_null()); | |
511 last_reinit_time_ = base::Time::Now(); | |
512 | |
513 // Inform observers of about this and give them a chance to | |
514 // defer deletion of the old storage object. | |
515 scoped_refptr<AppCacheStorageReference> | |
516 old_storage_ref(new AppCacheStorageReference(storage_.Pass())); | |
517 FOR_EACH_OBSERVER(Observer, observers_, | |
518 OnServiceReinitialized(old_storage_ref.get())); | |
519 | |
520 Initialize(cache_directory_, db_thread_, cache_thread_); | |
521 } | |
522 | |
523 void AppCacheServiceImpl::CanHandleMainResourceOffline( | |
524 const GURL& url, | |
525 const GURL& first_party, | |
526 const net::CompletionCallback& callback) { | |
527 CanHandleOfflineHelper* helper = | |
528 new CanHandleOfflineHelper(this, url, first_party, callback); | |
529 helper->Start(); | |
530 } | |
531 | |
532 void AppCacheServiceImpl::GetAllAppCacheInfo( | |
533 AppCacheInfoCollection* collection, | |
534 const net::CompletionCallback& callback) { | |
535 DCHECK(collection); | |
536 GetInfoHelper* helper = new GetInfoHelper(this, collection, callback); | |
537 helper->Start(); | |
538 } | |
539 | |
540 void AppCacheServiceImpl::DeleteAppCacheGroup( | |
541 const GURL& manifest_url, | |
542 const net::CompletionCallback& callback) { | |
543 DeleteHelper* helper = new DeleteHelper(this, manifest_url, callback); | |
544 helper->Start(); | |
545 } | |
546 | |
547 void AppCacheServiceImpl::DeleteAppCachesForOrigin( | |
548 const GURL& origin, const net::CompletionCallback& callback) { | |
549 DeleteOriginHelper* helper = new DeleteOriginHelper(this, origin, callback); | |
550 helper->Start(); | |
551 } | |
552 | |
553 void AppCacheServiceImpl::CheckAppCacheResponse(const GURL& manifest_url, | |
554 int64 cache_id, | |
555 int64 response_id) { | |
556 CheckResponseHelper* helper = new CheckResponseHelper( | |
557 this, manifest_url, cache_id, response_id); | |
558 helper->Start(); | |
559 } | |
560 | |
561 void AppCacheServiceImpl::set_special_storage_policy( | |
562 quota::SpecialStoragePolicy* policy) { | |
563 special_storage_policy_ = policy; | |
564 } | |
565 | |
566 void AppCacheServiceImpl::RegisterBackend( | |
567 AppCacheBackendImpl* backend_impl) { | |
568 DCHECK(backends_.find(backend_impl->process_id()) == backends_.end()); | |
569 backends_.insert( | |
570 BackendMap::value_type(backend_impl->process_id(), backend_impl)); | |
571 } | |
572 | |
573 void AppCacheServiceImpl::UnregisterBackend( | |
574 AppCacheBackendImpl* backend_impl) { | |
575 backends_.erase(backend_impl->process_id()); | |
576 } | |
577 | |
578 } // namespace appcache | |
OLD | NEW |