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/embedded_worker_instance.h"
11 #include "base/bind_helpers.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/threading/non_thread_safe.h"
14 #include "base/trace_event/trace_event.h"
15 #include "content/browser/devtools/service_worker_devtools_manager.h"
16 #include "content/browser/service_worker/embedded_worker_registry.h"
17 #include "content/browser/service_worker/service_worker_context_core.h"
18 #include "content/common/content_switches_internal.h"
19 #include "content/common/mojo/service_registry_impl.h"
20 #include "content/common/service_worker/embedded_worker_messages.h"
21 #include "content/common/service_worker/embedded_worker_setup.mojom.h"
22 #include "content/common/service_worker/service_worker_types.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "ipc/ipc_message.h"
32 // Functor to sort by the .second element of a struct.
33 struct SecondGreater
{
34 template <typename Value
>
35 bool operator()(const Value
& lhs
, const Value
& rhs
) {
36 return lhs
.second
> rhs
.second
;
40 void NotifyWorkerReadyForInspectionOnUI(int worker_process_id
,
41 int worker_route_id
) {
42 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
43 ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
44 worker_process_id
, worker_route_id
);
47 void NotifyWorkerDestroyedOnUI(int worker_process_id
, int worker_route_id
) {
48 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
49 ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
50 worker_process_id
, worker_route_id
);
53 void NotifyWorkerStopIgnoredOnUI(int worker_process_id
, int worker_route_id
) {
54 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
55 ServiceWorkerDevToolsManager::GetInstance()->WorkerStopIgnored(
56 worker_process_id
, worker_route_id
);
59 void RegisterToWorkerDevToolsManagerOnUI(
61 const ServiceWorkerContextCore
* service_worker_context
,
62 const base::WeakPtr
<ServiceWorkerContextCore
>& service_worker_context_weak
,
63 int64 service_worker_version_id
,
65 const base::Callback
<void(int worker_devtools_agent_route_id
,
66 bool wait_for_debugger
)>& callback
) {
67 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
68 int worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
69 bool wait_for_debugger
= false;
70 if (RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
)) {
71 // |rph| may be NULL in unit tests.
72 worker_devtools_agent_route_id
= rph
->GetNextRoutingID();
74 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
76 worker_devtools_agent_route_id
,
77 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
78 service_worker_context
,
79 service_worker_context_weak
,
80 service_worker_version_id
,
83 BrowserThread::PostTask(
86 base::Bind(callback
, worker_devtools_agent_route_id
, wait_for_debugger
));
89 void SetupMojoOnUIThread(int process_id
,
91 mojo::InterfaceRequest
<mojo::ServiceProvider
> services
,
92 mojo::ServiceProviderPtr exposed_services
) {
93 RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
);
94 // |rph| may be NULL in unit tests.
97 EmbeddedWorkerSetupPtr setup
;
98 rph
->GetServiceRegistry()->ConnectToRemoteService(mojo::GetProxy(&setup
));
99 setup
->ExchangeServiceProviders(thread_id
, services
.Pass(),
100 exposed_services
.Pass());
105 // Lives on IO thread, proxies notifications to DevToolsManager that lives on
106 // UI thread. Owned by EmbeddedWorkerInstance.
107 class EmbeddedWorkerInstance::DevToolsProxy
: public base::NonThreadSafe
{
109 DevToolsProxy(int process_id
, int agent_route_id
)
110 : process_id_(process_id
),
111 agent_route_id_(agent_route_id
) {}
114 BrowserThread::PostTask(
117 base::Bind(NotifyWorkerDestroyedOnUI
,
118 process_id_
, agent_route_id_
));
121 void NotifyWorkerReadyForInspection() {
122 DCHECK(CalledOnValidThread());
123 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
124 base::Bind(NotifyWorkerReadyForInspectionOnUI
,
125 process_id_
, agent_route_id_
));
128 void NotifyWorkerStopIgnored() {
129 DCHECK(CalledOnValidThread());
130 BrowserThread::PostTask(BrowserThread::UI
,
132 base::Bind(NotifyWorkerStopIgnoredOnUI
,
133 process_id_
, agent_route_id_
));
136 int agent_route_id() const { return agent_route_id_
; }
139 const int process_id_
;
140 const int agent_route_id_
;
141 DISALLOW_COPY_AND_ASSIGN(DevToolsProxy
);
144 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
145 DCHECK(status_
== STOPPING
|| status_
== STOPPED
);
146 devtools_proxy_
.reset();
147 if (context_
&& process_id_
!= -1)
148 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
149 registry_
->RemoveWorker(process_id_
, embedded_worker_id_
);
152 void EmbeddedWorkerInstance::Start(int64 service_worker_version_id
,
154 const GURL
& script_url
,
155 const StatusCallback
& callback
) {
157 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
160 DCHECK(status_
== STOPPED
);
161 start_timing_
= base::TimeTicks::Now();
163 starting_phase_
= ALLOCATING_PROCESS
;
164 network_accessed_for_script_
= false;
165 service_registry_
.reset(new ServiceRegistryImpl());
166 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarting());
167 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params(
168 new EmbeddedWorkerMsg_StartWorker_Params());
169 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
170 "EmbeddedWorkerInstance::ProcessAllocate",
172 "Scope", scope
.spec(),
173 "Script URL", script_url
.spec());
174 params
->embedded_worker_id
= embedded_worker_id_
;
175 params
->service_worker_version_id
= service_worker_version_id
;
176 params
->scope
= scope
;
177 params
->script_url
= script_url
;
178 params
->worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
179 params
->wait_for_debugger
= false;
180 params
->v8_cache_options
= GetV8CacheOptions();
181 context_
->process_manager()->AllocateWorkerProcess(
185 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated
,
186 weak_factory_
.GetWeakPtr(),
188 base::Passed(¶ms
),
192 ServiceWorkerStatusCode
EmbeddedWorkerInstance::Stop() {
193 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
194 ServiceWorkerStatusCode status
=
195 registry_
->StopWorker(process_id_
, embedded_worker_id_
);
196 UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status
,
197 SERVICE_WORKER_ERROR_MAX_VALUE
);
198 // StopWorker could fail if we were starting up and don't have a process yet,
199 // or we can no longer communicate with the process. So just detach.
200 if (status
!= SERVICE_WORKER_OK
) {
206 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopping());
210 void EmbeddedWorkerInstance::StopIfIdle() {
211 if (devtools_attached_
) {
213 devtools_proxy_
->NotifyWorkerStopIgnored();
219 ServiceWorkerStatusCode
EmbeddedWorkerInstance::SendMessage(
220 const IPC::Message
& message
) {
221 DCHECK_NE(kInvalidEmbeddedWorkerThreadId
, thread_id_
);
222 if (status_
!= RUNNING
&& status_
!= STARTING
)
223 return SERVICE_WORKER_ERROR_IPC_FAILED
;
224 return registry_
->Send(process_id_
,
225 new EmbeddedWorkerContextMsg_MessageToWorker(
226 thread_id_
, embedded_worker_id_
, message
));
229 ServiceRegistry
* EmbeddedWorkerInstance::GetServiceRegistry() {
230 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
231 return service_registry_
.get();
234 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
235 base::WeakPtr
<ServiceWorkerContextCore
> context
,
236 int embedded_worker_id
)
238 registry_(context
->embedded_worker_registry()),
239 embedded_worker_id_(embedded_worker_id
),
241 starting_phase_(NOT_STARTING
),
243 thread_id_(kInvalidEmbeddedWorkerThreadId
),
244 devtools_attached_(false),
245 network_accessed_for_script_(false),
246 weak_factory_(this) {
250 void EmbeddedWorkerInstance::RunProcessAllocated(
251 base::WeakPtr
<EmbeddedWorkerInstance
> instance
,
252 base::WeakPtr
<ServiceWorkerContextCore
> context
,
253 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
254 const EmbeddedWorkerInstance::StatusCallback
& callback
,
255 ServiceWorkerStatusCode status
,
257 bool is_new_process
) {
259 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
263 if (status
== SERVICE_WORKER_OK
) {
264 // We only have a process allocated if the status is OK.
265 context
->process_manager()->ReleaseWorkerProcess(
266 params
->embedded_worker_id
);
268 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
271 instance
->ProcessAllocated(params
.Pass(), callback
, process_id
,
272 is_new_process
, status
);
275 void EmbeddedWorkerInstance::ProcessAllocated(
276 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
277 const StatusCallback
& callback
,
280 ServiceWorkerStatusCode status
) {
281 DCHECK_EQ(process_id_
, -1);
282 TRACE_EVENT_ASYNC_END1("ServiceWorker",
283 "EmbeddedWorkerInstance::ProcessAllocate",
286 if (status
!= SERVICE_WORKER_OK
) {
287 Status old_status
= status_
;
289 service_registry_
.reset();
290 callback
.Run(status
);
291 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
294 const int64 service_worker_version_id
= params
->service_worker_version_id
;
295 process_id_
= process_id
;
296 GURL
script_url(params
->script_url
);
298 // Register this worker to DevToolsManager on UI thread, then continue to
299 // call SendStartWorker on IO thread.
300 starting_phase_
= REGISTERING_TO_DEVTOOLS
;
301 BrowserThread::PostTask(
302 BrowserThread::UI
, FROM_HERE
,
303 base::Bind(RegisterToWorkerDevToolsManagerOnUI
, process_id_
,
304 context_
.get(), context_
, service_worker_version_id
,
306 base::Bind(&EmbeddedWorkerInstance::SendStartWorker
,
307 weak_factory_
.GetWeakPtr(), base::Passed(¶ms
),
308 callback
, is_new_process
)));
311 void EmbeddedWorkerInstance::SendStartWorker(
312 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
313 const StatusCallback
& callback
,
315 int worker_devtools_agent_route_id
,
316 bool wait_for_debugger
) {
317 // We may have been detached or stopped at some point during the start up
318 // process, making process_id_ and other state invalid. If that happened,
319 // abort instead of trying to send the IPC.
320 if (status_
!= STARTING
) {
321 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
326 if (worker_devtools_agent_route_id
!= MSG_ROUTING_NONE
) {
327 DCHECK(!devtools_proxy_
);
328 devtools_proxy_
.reset(new DevToolsProxy(process_id_
,
329 worker_devtools_agent_route_id
));
331 params
->worker_devtools_agent_route_id
= worker_devtools_agent_route_id
;
332 params
->wait_for_debugger
= wait_for_debugger
;
333 if (params
->wait_for_debugger
) {
334 // We don't measure the start time when wait_for_debugger flag is set. So we
335 // set the NULL time here.
336 start_timing_
= base::TimeTicks();
338 DCHECK(!start_timing_
.is_null());
339 if (is_new_process
) {
340 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.NewProcessAllocation",
341 base::TimeTicks::Now() - start_timing_
);
343 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ExistingProcessAllocation",
344 base::TimeTicks::Now() - start_timing_
);
346 UMA_HISTOGRAM_BOOLEAN("EmbeddedWorkerInstance.ProcessCreated",
348 // Reset |start_timing_| to measure the time excluding the process
350 start_timing_
= base::TimeTicks::Now();
353 starting_phase_
= SENT_START_WORKER
;
354 ServiceWorkerStatusCode status
=
355 registry_
->SendStartWorker(params
.Pass(), process_id_
);
356 if (status
!= SERVICE_WORKER_OK
) {
357 callback
.Run(status
);
360 DCHECK(start_callback_
.is_null());
361 start_callback_
= callback
;
364 void EmbeddedWorkerInstance::OnReadyForInspection() {
366 devtools_proxy_
->NotifyWorkerReadyForInspection();
369 void EmbeddedWorkerInstance::OnScriptLoaded() {
370 starting_phase_
= SCRIPT_LOADED
;
373 void EmbeddedWorkerInstance::OnThreadStarted(int thread_id
) {
374 starting_phase_
= THREAD_STARTED
;
375 if (!start_timing_
.is_null()) {
376 if (network_accessed_for_script_
) {
377 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
378 base::TimeTicks::Now() - start_timing_
);
381 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
382 base::TimeTicks::Now() - start_timing_
);
384 // Reset |start_timing_| to measure the time excluding the process
385 // allocation time and the script loading time.
386 start_timing_
= base::TimeTicks::Now();
388 thread_id_
= thread_id
;
389 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnThreadStarted());
391 mojo::ServiceProviderPtr exposed_services
;
392 service_registry_
->Bind(GetProxy(&exposed_services
));
393 mojo::ServiceProviderPtr services
;
394 mojo::InterfaceRequest
<mojo::ServiceProvider
> services_request
=
396 BrowserThread::PostTask(
397 BrowserThread::UI
, FROM_HERE
,
398 base::Bind(SetupMojoOnUIThread
, process_id_
, thread_id_
,
399 base::Passed(&services_request
),
400 base::Passed(&exposed_services
)));
401 service_registry_
->BindRemoteServiceProvider(services
.Pass());
404 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
407 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success
) {
408 starting_phase_
= SCRIPT_EVALUATED
;
409 if (start_callback_
.is_null()) {
410 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
413 if (success
&& !start_timing_
.is_null()) {
414 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
415 base::TimeTicks::Now() - start_timing_
);
417 start_callback_
.Run(success
? SERVICE_WORKER_OK
418 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED
);
419 start_callback_
.Reset();
422 void EmbeddedWorkerInstance::OnStarted() {
423 // Stop is requested before OnStarted is sent back from the worker.
424 if (status_
== STOPPING
)
426 DCHECK(status_
== STARTING
);
428 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarted());
431 void EmbeddedWorkerInstance::OnStopped() {
432 Status old_status
= status_
;
434 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
437 void EmbeddedWorkerInstance::OnDetached() {
438 Status old_status
= status_
;
440 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnDetached(old_status
));
443 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message
& message
) {
444 ListenerList::Iterator
it(&listener_list_
);
445 while (Listener
* listener
= it
.GetNext()) {
446 if (listener
->OnMessageReceived(message
))
452 void EmbeddedWorkerInstance::OnReportException(
453 const base::string16
& error_message
,
456 const GURL
& source_url
) {
460 OnReportException(error_message
, line_number
, column_number
, source_url
));
463 void EmbeddedWorkerInstance::OnReportConsoleMessage(
464 int source_identifier
,
466 const base::string16
& message
,
468 const GURL
& source_url
) {
472 OnReportConsoleMessage(
473 source_identifier
, message_level
, message
, line_number
, source_url
));
476 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
478 return devtools_proxy_
->agent_route_id();
479 return MSG_ROUTING_NONE
;
482 MessagePortMessageFilter
* EmbeddedWorkerInstance::message_port_message_filter()
484 return registry_
->MessagePortMessageFilterForProcess(process_id_
);
487 void EmbeddedWorkerInstance::AddListener(Listener
* listener
) {
488 listener_list_
.AddObserver(listener
);
491 void EmbeddedWorkerInstance::RemoveListener(Listener
* listener
) {
492 listener_list_
.RemoveObserver(listener
);
495 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
496 starting_phase_
= SCRIPT_DOWNLOADING
;
497 network_accessed_for_script_
= true;
500 void EmbeddedWorkerInstance::ReleaseProcess() {
501 devtools_proxy_
.reset();
502 if (context_
&& process_id_
!= -1)
503 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
507 service_registry_
.reset();
508 start_callback_
.Reset();
512 std::string
EmbeddedWorkerInstance::StatusToString(Status status
) {
523 NOTREACHED() << status
;
524 return std::string();
528 std::string
EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase
) {
531 return "Not in STARTING status";
532 case ALLOCATING_PROCESS
:
533 return "Allocating process";
534 case REGISTERING_TO_DEVTOOLS
:
535 return "Registering to DevTools";
536 case SENT_START_WORKER
:
537 return "Sent StartWorker message to renderer";
538 case SCRIPT_DOWNLOADING
:
539 return "Script downloading";
541 return "Script loaded";
542 case SCRIPT_EVALUATED
:
543 return "Script evaluated";
545 return "Thread started";
546 case STARTING_PHASE_MAX_VALUE
:
549 NOTREACHED() << phase
;
550 return std::string();
553 } // namespace content