1 // Copyright 2013 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.
5 #include "content/browser/service_worker/service_worker_version.h"
7 #include "base/command_line.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string16.h"
10 #include "content/browser/service_worker/embedded_worker_instance.h"
11 #include "content/browser/service_worker/embedded_worker_registry.h"
12 #include "content/browser/service_worker/service_worker_context_core.h"
13 #include "content/browser/service_worker/service_worker_registration.h"
14 #include "content/browser/service_worker/service_worker_utils.h"
15 #include "content/common/service_worker/service_worker_messages.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/common/content_switches.h"
21 typedef ServiceWorkerVersion::StatusCallback StatusCallback
;
22 typedef ServiceWorkerVersion::MessageCallback MessageCallback
;
26 // Default delay to stop the worker context after all documents that
27 // are associated to the worker are closed.
28 // (Note that if all references to the version is dropped the worker
29 // is also stopped without delay)
30 const int64 kStopWorkerDelay
= 30; // 30 secs.
32 // Default delay for scheduled update.
33 const int kUpdateDelaySeconds
= 10;
35 void RunSoon(const base::Closure
& callback
) {
36 if (!callback
.is_null())
37 base::MessageLoop::current()->PostTask(FROM_HERE
, callback
);
40 template <typename CallbackArray
, typename Arg
>
41 void RunCallbacks(ServiceWorkerVersion
* version
,
42 CallbackArray
* callbacks_ptr
,
44 CallbackArray callbacks
;
45 callbacks
.swap(*callbacks_ptr
);
46 scoped_refptr
<ServiceWorkerVersion
> protect(version
);
47 for (typename
CallbackArray::const_iterator i
= callbacks
.begin();
48 i
!= callbacks
.end(); ++i
)
52 template <typename IDMAP
, typename Method
, typename Params
>
53 void RunIDMapCallbacks(IDMAP
* callbacks
, Method method
, const Params
& params
) {
54 typename
IDMAP::iterator
iter(callbacks
);
55 while (!iter
.IsAtEnd()) {
56 DispatchToMethod(iter
.GetCurrentValue(), method
, params
);
62 // A callback adapter to start a |task| after StartWorker.
63 void RunTaskAfterStartWorker(
64 base::WeakPtr
<ServiceWorkerVersion
> version
,
65 const StatusCallback
& error_callback
,
66 const base::Closure
& task
,
67 ServiceWorkerStatusCode status
) {
68 if (status
!= SERVICE_WORKER_OK
) {
69 if (!error_callback
.is_null())
70 error_callback
.Run(status
);
73 if (version
->running_status() != ServiceWorkerVersion::RUNNING
) {
74 // We've tried to start the worker (and it has succeeded), but
75 // it looks it's not running yet.
76 NOTREACHED() << "The worker's not running after successful StartWorker";
77 if (!error_callback
.is_null())
78 error_callback
.Run(SERVICE_WORKER_ERROR_START_WORKER_FAILED
);
84 void RunErrorFetchCallback(const ServiceWorkerVersion::FetchCallback
& callback
,
85 ServiceWorkerStatusCode status
) {
87 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK
,
88 ServiceWorkerResponse());
93 ServiceWorkerVersion::ServiceWorkerVersion(
94 ServiceWorkerRegistration
* registration
,
96 base::WeakPtr
<ServiceWorkerContextCore
> context
)
97 : version_id_(version_id
),
98 registration_id_(kInvalidServiceWorkerVersionId
),
101 script_cache_map_(this, context
),
103 weak_factory_(this) {
105 DCHECK(registration
);
107 registration_id_
= registration
->id();
108 script_url_
= registration
->script_url();
109 scope_
= registration
->pattern();
111 context_
->AddLiveVersion(this);
112 embedded_worker_
= context_
->embedded_worker_registry()->CreateWorker();
113 embedded_worker_
->AddListener(this);
116 ServiceWorkerVersion::~ServiceWorkerVersion() {
117 embedded_worker_
->RemoveListener(this);
119 context_
->RemoveLiveVersion(version_id_
);
120 // EmbeddedWorker's dtor sends StopWorker if it's still running.
123 void ServiceWorkerVersion::SetStatus(Status status
) {
124 if (status_
== status
)
129 std::vector
<base::Closure
> callbacks
;
130 callbacks
.swap(status_change_callbacks_
);
131 for (std::vector
<base::Closure
>::const_iterator i
= callbacks
.begin();
132 i
!= callbacks
.end(); ++i
) {
136 FOR_EACH_OBSERVER(Listener
, listeners_
, OnVersionStateChanged(this));
139 void ServiceWorkerVersion::RegisterStatusChangeCallback(
140 const base::Closure
& callback
) {
141 status_change_callbacks_
.push_back(callback
);
144 ServiceWorkerVersionInfo
ServiceWorkerVersion::GetInfo() {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
146 return ServiceWorkerVersionInfo(
150 embedded_worker()->process_id(),
151 embedded_worker()->thread_id(),
152 embedded_worker()->worker_devtools_agent_route_id());
155 void ServiceWorkerVersion::StartWorker(const StatusCallback
& callback
) {
156 StartWorkerWithCandidateProcesses(std::vector
<int>(), false, callback
);
159 void ServiceWorkerVersion::StartWorkerWithCandidateProcesses(
160 const std::vector
<int>& possible_process_ids
,
161 bool pause_after_download
,
162 const StatusCallback
& callback
) {
163 switch (running_status()) {
165 RunSoon(base::Bind(callback
, SERVICE_WORKER_OK
));
168 RunSoon(base::Bind(callback
, SERVICE_WORKER_ERROR_START_WORKER_FAILED
));
172 start_callbacks_
.push_back(callback
);
173 if (running_status() == STOPPED
) {
174 embedded_worker_
->Start(
178 pause_after_download
,
179 possible_process_ids
,
180 base::Bind(&ServiceWorkerVersion::RunStartWorkerCallbacksOnError
,
181 weak_factory_
.GetWeakPtr()));
187 void ServiceWorkerVersion::StopWorker(const StatusCallback
& callback
) {
188 if (running_status() == STOPPED
) {
189 RunSoon(base::Bind(callback
, SERVICE_WORKER_OK
));
192 if (stop_callbacks_
.empty()) {
193 ServiceWorkerStatusCode status
= embedded_worker_
->Stop();
194 if (status
!= SERVICE_WORKER_OK
) {
195 RunSoon(base::Bind(callback
, status
));
199 stop_callbacks_
.push_back(callback
);
202 void ServiceWorkerVersion::ScheduleUpdate() {
203 if (update_timer_
.IsRunning()) {
204 update_timer_
.Reset();
208 FROM_HERE
, base::TimeDelta::FromSeconds(kUpdateDelaySeconds
),
209 base::Bind(&ServiceWorkerVersion::StartUpdate
,
210 weak_factory_
.GetWeakPtr()));
213 void ServiceWorkerVersion::DeferScheduledUpdate() {
214 if (update_timer_
.IsRunning())
215 update_timer_
.Reset();
218 void ServiceWorkerVersion::StartUpdate() {
219 update_timer_
.Stop();
222 ServiceWorkerRegistration
* registration
=
223 context_
->GetLiveRegistration(registration_id_
);
226 context_
->UpdateServiceWorker(registration
);
229 void ServiceWorkerVersion::SendMessage(
230 const IPC::Message
& message
, const StatusCallback
& callback
) {
231 if (running_status() != RUNNING
) {
232 // Schedule calling this method after starting the worker.
233 StartWorker(base::Bind(&RunTaskAfterStartWorker
,
234 weak_factory_
.GetWeakPtr(), callback
,
235 base::Bind(&self::SendMessage
,
236 weak_factory_
.GetWeakPtr(),
237 message
, callback
)));
241 ServiceWorkerStatusCode status
= embedded_worker_
->SendMessage(message
);
242 RunSoon(base::Bind(callback
, status
));
245 void ServiceWorkerVersion::DispatchInstallEvent(
246 int active_version_id
,
247 const StatusCallback
& callback
) {
248 DCHECK_EQ(INSTALLING
, status()) << status();
250 if (running_status() != RUNNING
) {
251 // Schedule calling this method after starting the worker.
253 base::Bind(&RunTaskAfterStartWorker
,
254 weak_factory_
.GetWeakPtr(),
256 base::Bind(&self::DispatchInstallEventAfterStartWorker
,
257 weak_factory_
.GetWeakPtr(),
261 DispatchInstallEventAfterStartWorker(active_version_id
, callback
);
265 void ServiceWorkerVersion::DispatchActivateEvent(
266 const StatusCallback
& callback
) {
267 DCHECK_EQ(ACTIVATING
, status()) << status();
269 if (running_status() != RUNNING
) {
270 // Schedule calling this method after starting the worker.
272 base::Bind(&RunTaskAfterStartWorker
,
273 weak_factory_
.GetWeakPtr(),
275 base::Bind(&self::DispatchActivateEventAfterStartWorker
,
276 weak_factory_
.GetWeakPtr(),
279 DispatchActivateEventAfterStartWorker(callback
);
283 void ServiceWorkerVersion::DispatchFetchEvent(
284 const ServiceWorkerFetchRequest
& request
,
285 const FetchCallback
& callback
) {
286 DCHECK_EQ(ACTIVATED
, status()) << status();
288 if (running_status() != RUNNING
) {
289 // Schedule calling this method after starting the worker.
290 StartWorker(base::Bind(&RunTaskAfterStartWorker
,
291 weak_factory_
.GetWeakPtr(),
292 base::Bind(&RunErrorFetchCallback
, callback
),
293 base::Bind(&self::DispatchFetchEvent
,
294 weak_factory_
.GetWeakPtr(),
295 request
, callback
)));
299 int request_id
= fetch_callbacks_
.Add(new FetchCallback(callback
));
300 ServiceWorkerStatusCode status
= embedded_worker_
->SendMessage(
301 ServiceWorkerMsg_FetchEvent(request_id
, request
));
302 if (status
!= SERVICE_WORKER_OK
) {
303 fetch_callbacks_
.Remove(request_id
);
304 RunSoon(base::Bind(&RunErrorFetchCallback
,
306 SERVICE_WORKER_ERROR_FAILED
));
310 void ServiceWorkerVersion::DispatchSyncEvent(const StatusCallback
& callback
) {
311 DCHECK_EQ(ACTIVATED
, status()) << status();
313 if (!CommandLine::ForCurrentProcess()->HasSwitch(
314 switches::kEnableServiceWorkerSync
)) {
315 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
319 if (running_status() != RUNNING
) {
320 // Schedule calling this method after starting the worker.
321 StartWorker(base::Bind(&RunTaskAfterStartWorker
,
322 weak_factory_
.GetWeakPtr(), callback
,
323 base::Bind(&self::DispatchSyncEvent
,
324 weak_factory_
.GetWeakPtr(),
329 int request_id
= sync_callbacks_
.Add(new StatusCallback(callback
));
330 ServiceWorkerStatusCode status
= embedded_worker_
->SendMessage(
331 ServiceWorkerMsg_SyncEvent(request_id
));
332 if (status
!= SERVICE_WORKER_OK
) {
333 sync_callbacks_
.Remove(request_id
);
334 RunSoon(base::Bind(callback
, status
));
338 void ServiceWorkerVersion::DispatchPushEvent(const StatusCallback
& callback
,
339 const std::string
& data
) {
340 DCHECK_EQ(ACTIVATED
, status()) << status();
342 if (!CommandLine::ForCurrentProcess()->HasSwitch(
343 switches::kEnableExperimentalWebPlatformFeatures
)) {
344 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
348 if (running_status() != RUNNING
) {
349 // Schedule calling this method after starting the worker.
350 StartWorker(base::Bind(&RunTaskAfterStartWorker
,
351 weak_factory_
.GetWeakPtr(), callback
,
352 base::Bind(&self::DispatchPushEvent
,
353 weak_factory_
.GetWeakPtr(),
358 int request_id
= push_callbacks_
.Add(new StatusCallback(callback
));
359 ServiceWorkerStatusCode status
= embedded_worker_
->SendMessage(
360 ServiceWorkerMsg_PushEvent(request_id
, data
));
361 if (status
!= SERVICE_WORKER_OK
) {
362 push_callbacks_
.Remove(request_id
);
363 RunSoon(base::Bind(callback
, status
));
367 void ServiceWorkerVersion::AddProcessToWorker(int process_id
) {
368 embedded_worker_
->AddProcessReference(process_id
);
371 void ServiceWorkerVersion::RemoveProcessFromWorker(int process_id
) {
372 embedded_worker_
->ReleaseProcessReference(process_id
);
375 bool ServiceWorkerVersion::HasProcessToRun() const {
376 return embedded_worker_
->HasProcessToRun();
379 void ServiceWorkerVersion::AddControllee(
380 ServiceWorkerProviderHost
* provider_host
) {
381 DCHECK(!ContainsKey(controllee_map_
, provider_host
));
382 int controllee_id
= controllee_by_id_
.Add(provider_host
);
383 controllee_map_
[provider_host
] = controllee_id
;
384 AddProcessToWorker(provider_host
->process_id());
385 if (stop_worker_timer_
.IsRunning())
386 stop_worker_timer_
.Stop();
389 void ServiceWorkerVersion::RemoveControllee(
390 ServiceWorkerProviderHost
* provider_host
) {
391 ControlleeMap::iterator found
= controllee_map_
.find(provider_host
);
392 DCHECK(found
!= controllee_map_
.end());
393 controllee_by_id_
.Remove(found
->second
);
394 controllee_map_
.erase(found
);
395 RemoveProcessFromWorker(provider_host
->process_id());
398 FOR_EACH_OBSERVER(Listener
, listeners_
, OnNoControllees(this));
403 ScheduleStopWorker();
406 void ServiceWorkerVersion::AddPotentialControllee(
407 ServiceWorkerProviderHost
* provider_host
) {
408 AddProcessToWorker(provider_host
->process_id());
411 void ServiceWorkerVersion::RemovePotentialControllee(
412 ServiceWorkerProviderHost
* provider_host
) {
413 RemoveProcessFromWorker(provider_host
->process_id());
416 void ServiceWorkerVersion::AddListener(Listener
* listener
) {
417 listeners_
.AddObserver(listener
);
420 void ServiceWorkerVersion::RemoveListener(Listener
* listener
) {
421 listeners_
.RemoveObserver(listener
);
424 void ServiceWorkerVersion::Doom() {
428 if (!HasControllee())
432 void ServiceWorkerVersion::OnStarted() {
433 DCHECK_EQ(RUNNING
, running_status());
434 // Fire all start callbacks.
435 RunCallbacks(this, &start_callbacks_
, SERVICE_WORKER_OK
);
436 FOR_EACH_OBSERVER(Listener
, listeners_
, OnWorkerStarted(this));
439 void ServiceWorkerVersion::OnStopped() {
440 DCHECK_EQ(STOPPED
, running_status());
441 scoped_refptr
<ServiceWorkerVersion
> protect(this);
443 // Fire all stop callbacks.
444 RunCallbacks(this, &stop_callbacks_
, SERVICE_WORKER_OK
);
446 // Let all start callbacks fail.
448 this, &start_callbacks_
, SERVICE_WORKER_ERROR_START_WORKER_FAILED
);
450 // Let all message callbacks fail (this will also fire and clear all
451 // callbacks for events).
452 // TODO(kinuko): Consider if we want to add queue+resend mechanism here.
453 RunIDMapCallbacks(&activate_callbacks_
,
454 &StatusCallback::Run
,
455 MakeTuple(SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED
));
456 RunIDMapCallbacks(&install_callbacks_
,
457 &StatusCallback::Run
,
458 MakeTuple(SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
));
459 RunIDMapCallbacks(&fetch_callbacks_
,
461 MakeTuple(SERVICE_WORKER_ERROR_FAILED
,
462 SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK
,
463 ServiceWorkerResponse()));
464 RunIDMapCallbacks(&sync_callbacks_
,
465 &StatusCallback::Run
,
466 MakeTuple(SERVICE_WORKER_ERROR_FAILED
));
467 RunIDMapCallbacks(&push_callbacks_
,
468 &StatusCallback::Run
,
469 MakeTuple(SERVICE_WORKER_ERROR_FAILED
));
471 FOR_EACH_OBSERVER(Listener
, listeners_
, OnWorkerStopped(this));
474 void ServiceWorkerVersion::OnReportException(
475 const base::string16
& error_message
,
478 const GURL
& source_url
) {
483 this, error_message
, line_number
, column_number
, source_url
));
486 void ServiceWorkerVersion::OnReportConsoleMessage(int source_identifier
,
488 const base::string16
& message
,
490 const GURL
& source_url
) {
491 FOR_EACH_OBSERVER(Listener
,
493 OnReportConsoleMessage(this,
501 bool ServiceWorkerVersion::OnMessageReceived(const IPC::Message
& message
) {
503 IPC_BEGIN_MESSAGE_MAP(ServiceWorkerVersion
, message
)
504 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_GetClientDocuments
,
505 OnGetClientDocuments
)
506 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_ActivateEventFinished
,
507 OnActivateEventFinished
)
508 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_InstallEventFinished
,
509 OnInstallEventFinished
)
510 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_FetchEventFinished
,
511 OnFetchEventFinished
)
512 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_SyncEventFinished
,
514 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PushEventFinished
,
516 IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToDocument
,
517 OnPostMessageToDocument
)
518 IPC_MESSAGE_UNHANDLED(handled
= false)
519 IPC_END_MESSAGE_MAP()
523 void ServiceWorkerVersion::RunStartWorkerCallbacksOnError(
524 ServiceWorkerStatusCode status
) {
525 if (status
!= SERVICE_WORKER_OK
)
526 RunCallbacks(this, &start_callbacks_
, status
);
529 void ServiceWorkerVersion::DispatchInstallEventAfterStartWorker(
530 int active_version_id
,
531 const StatusCallback
& callback
) {
532 DCHECK_EQ(RUNNING
, running_status())
533 << "Worker stopped too soon after it was started.";
535 int request_id
= install_callbacks_
.Add(new StatusCallback(callback
));
536 ServiceWorkerStatusCode status
= embedded_worker_
->SendMessage(
537 ServiceWorkerMsg_InstallEvent(request_id
, active_version_id
));
538 if (status
!= SERVICE_WORKER_OK
) {
539 install_callbacks_
.Remove(request_id
);
540 RunSoon(base::Bind(callback
, status
));
544 void ServiceWorkerVersion::DispatchActivateEventAfterStartWorker(
545 const StatusCallback
& callback
) {
546 DCHECK_EQ(RUNNING
, running_status())
547 << "Worker stopped too soon after it was started.";
549 int request_id
= activate_callbacks_
.Add(new StatusCallback(callback
));
550 ServiceWorkerStatusCode status
=
551 embedded_worker_
->SendMessage(ServiceWorkerMsg_ActivateEvent(request_id
));
552 if (status
!= SERVICE_WORKER_OK
) {
553 activate_callbacks_
.Remove(request_id
);
554 RunSoon(base::Bind(callback
, status
));
558 void ServiceWorkerVersion::OnGetClientDocuments(int request_id
) {
559 std::vector
<int> client_ids
;
560 ControlleeByIDMap::iterator
it(&controllee_by_id_
);
561 while (!it
.IsAtEnd()) {
562 client_ids
.push_back(it
.GetCurrentKey());
565 // Don't bother if it's no longer running.
566 if (running_status() == RUNNING
) {
567 embedded_worker_
->SendMessage(
568 ServiceWorkerMsg_DidGetClientDocuments(request_id
, client_ids
));
572 void ServiceWorkerVersion::OnActivateEventFinished(
574 blink::WebServiceWorkerEventResult result
) {
575 DCHECK(ACTIVATING
== status() ||
576 REDUNDANT
== status()) << status();
578 StatusCallback
* callback
= activate_callbacks_
.Lookup(request_id
);
580 NOTREACHED() << "Got unexpected message: " << request_id
;
583 ServiceWorkerStatusCode rv
= SERVICE_WORKER_OK
;
584 if (result
== blink::WebServiceWorkerEventResultRejected
||
585 status() != ACTIVATING
) {
586 rv
= SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED
;
589 scoped_refptr
<ServiceWorkerVersion
> protect(this);
591 activate_callbacks_
.Remove(request_id
);
594 void ServiceWorkerVersion::OnInstallEventFinished(
596 blink::WebServiceWorkerEventResult result
) {
597 DCHECK_EQ(INSTALLING
, status()) << status();
599 StatusCallback
* callback
= install_callbacks_
.Lookup(request_id
);
601 NOTREACHED() << "Got unexpected message: " << request_id
;
604 ServiceWorkerStatusCode status
= SERVICE_WORKER_OK
;
605 if (result
== blink::WebServiceWorkerEventResultRejected
)
606 status
= SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED
;
608 scoped_refptr
<ServiceWorkerVersion
> protect(this);
609 callback
->Run(status
);
610 install_callbacks_
.Remove(request_id
);
613 void ServiceWorkerVersion::OnFetchEventFinished(
615 ServiceWorkerFetchEventResult result
,
616 const ServiceWorkerResponse
& response
) {
617 FetchCallback
* callback
= fetch_callbacks_
.Lookup(request_id
);
619 NOTREACHED() << "Got unexpected message: " << request_id
;
623 scoped_refptr
<ServiceWorkerVersion
> protect(this);
624 callback
->Run(SERVICE_WORKER_OK
, result
, response
);
625 fetch_callbacks_
.Remove(request_id
);
628 void ServiceWorkerVersion::OnSyncEventFinished(
630 StatusCallback
* callback
= sync_callbacks_
.Lookup(request_id
);
632 NOTREACHED() << "Got unexpected message: " << request_id
;
636 scoped_refptr
<ServiceWorkerVersion
> protect(this);
637 callback
->Run(SERVICE_WORKER_OK
);
638 sync_callbacks_
.Remove(request_id
);
641 void ServiceWorkerVersion::OnPushEventFinished(
643 StatusCallback
* callback
= push_callbacks_
.Lookup(request_id
);
645 NOTREACHED() << "Got unexpected message: " << request_id
;
649 scoped_refptr
<ServiceWorkerVersion
> protect(this);
650 callback
->Run(SERVICE_WORKER_OK
);
651 push_callbacks_
.Remove(request_id
);
654 void ServiceWorkerVersion::OnPostMessageToDocument(
656 const base::string16
& message
,
657 const std::vector
<int>& sent_message_port_ids
) {
658 ServiceWorkerProviderHost
* provider_host
=
659 controllee_by_id_
.Lookup(client_id
);
660 if (!provider_host
) {
661 // The client may already have been closed, just ignore.
664 provider_host
->PostMessage(message
, sent_message_port_ids
);
667 void ServiceWorkerVersion::ScheduleStopWorker() {
668 if (running_status() != RUNNING
)
670 if (stop_worker_timer_
.IsRunning()) {
671 stop_worker_timer_
.Reset();
674 stop_worker_timer_
.Start(
675 FROM_HERE
, base::TimeDelta::FromSeconds(kStopWorkerDelay
),
676 base::Bind(&ServiceWorkerVersion::StopWorker
,
677 weak_factory_
.GetWeakPtr(),
678 base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
)));
681 void ServiceWorkerVersion::DoomInternal() {
682 DCHECK(!HasControllee());
683 SetStatus(REDUNDANT
);
684 StopWorker(base::Bind(&ServiceWorkerUtils::NoOpStatusCallback
));
687 std::vector
<ServiceWorkerDatabase::ResourceRecord
> resources
;
688 script_cache_map_
.GetResources(&resources
);
689 context_
->storage()->PurgeResources(resources
);
692 } // namespace content