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"
7 #include "base/bind_helpers.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/threading/non_thread_safe.h"
10 #include "base/trace_event/trace_event.h"
11 #include "content/browser/devtools/service_worker_devtools_manager.h"
12 #include "content/browser/service_worker/embedded_worker_registry.h"
13 #include "content/browser/service_worker/service_worker_context_core.h"
14 #include "content/common/content_switches_internal.h"
15 #include "content/common/mojo/service_registry_impl.h"
16 #include "content/common/service_worker/embedded_worker_messages.h"
17 #include "content/common/service_worker/embedded_worker_setup.mojom.h"
18 #include "content/common/service_worker/service_worker_types.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/render_process_host.h"
21 #include "ipc/ipc_message.h"
28 void NotifyWorkerReadyForInspectionOnUI(int worker_process_id
,
29 int worker_route_id
) {
30 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
31 ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
32 worker_process_id
, worker_route_id
);
35 void NotifyWorkerDestroyedOnUI(int worker_process_id
, int worker_route_id
) {
36 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
37 ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
38 worker_process_id
, worker_route_id
);
41 void NotifyWorkerStopIgnoredOnUI(int worker_process_id
, int worker_route_id
) {
42 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
43 ServiceWorkerDevToolsManager::GetInstance()->WorkerStopIgnored(
44 worker_process_id
, worker_route_id
);
47 void RegisterToWorkerDevToolsManagerOnUI(
49 const ServiceWorkerContextCore
* service_worker_context
,
50 const base::WeakPtr
<ServiceWorkerContextCore
>& service_worker_context_weak
,
51 int64 service_worker_version_id
,
53 const base::Callback
<void(int worker_devtools_agent_route_id
,
54 bool wait_for_debugger
)>& callback
) {
55 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
56 int worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
57 bool wait_for_debugger
= false;
58 if (RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
)) {
59 // |rph| may be NULL in unit tests.
60 worker_devtools_agent_route_id
= rph
->GetNextRoutingID();
62 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
64 worker_devtools_agent_route_id
,
65 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
66 service_worker_context
,
67 service_worker_context_weak
,
68 service_worker_version_id
,
71 BrowserThread::PostTask(
74 base::Bind(callback
, worker_devtools_agent_route_id
, wait_for_debugger
));
77 void SetupMojoOnUIThread(int process_id
,
79 mojo::InterfaceRequest
<mojo::ServiceProvider
> services
,
80 mojo::ServiceProviderPtr exposed_services
) {
81 RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
);
82 // |rph| may be NULL in unit tests.
85 EmbeddedWorkerSetupPtr setup
;
86 rph
->GetServiceRegistry()->ConnectToRemoteService(mojo::GetProxy(&setup
));
87 setup
->ExchangeServiceProviders(thread_id
, services
.Pass(),
88 exposed_services
.Pass());
93 // Lives on IO thread, proxies notifications to DevToolsManager that lives on
94 // UI thread. Owned by EmbeddedWorkerInstance.
95 class EmbeddedWorkerInstance::DevToolsProxy
: public base::NonThreadSafe
{
97 DevToolsProxy(int process_id
, int agent_route_id
)
98 : process_id_(process_id
),
99 agent_route_id_(agent_route_id
) {}
102 BrowserThread::PostTask(
105 base::Bind(NotifyWorkerDestroyedOnUI
,
106 process_id_
, agent_route_id_
));
109 void NotifyWorkerReadyForInspection() {
110 DCHECK(CalledOnValidThread());
111 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
112 base::Bind(NotifyWorkerReadyForInspectionOnUI
,
113 process_id_
, agent_route_id_
));
116 void NotifyWorkerStopIgnored() {
117 DCHECK(CalledOnValidThread());
118 BrowserThread::PostTask(BrowserThread::UI
,
120 base::Bind(NotifyWorkerStopIgnoredOnUI
,
121 process_id_
, agent_route_id_
));
124 int agent_route_id() const { return agent_route_id_
; }
127 const int process_id_
;
128 const int agent_route_id_
;
129 DISALLOW_COPY_AND_ASSIGN(DevToolsProxy
);
132 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
133 DCHECK(status_
== STOPPING
|| status_
== STOPPED
);
134 devtools_proxy_
.reset();
135 if (context_
&& process_id_
!= -1)
136 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
137 registry_
->RemoveWorker(process_id_
, embedded_worker_id_
);
140 void EmbeddedWorkerInstance::Start(int64 service_worker_version_id
,
142 const GURL
& script_url
,
143 const StatusCallback
& callback
) {
145 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
146 // |this| may be destroyed by the callback.
149 DCHECK(status_
== STOPPED
);
150 start_timing_
= base::TimeTicks::Now();
152 starting_phase_
= ALLOCATING_PROCESS
;
153 network_accessed_for_script_
= false;
154 service_registry_
.reset(new ServiceRegistryImpl());
155 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarting());
156 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params(
157 new EmbeddedWorkerMsg_StartWorker_Params());
158 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
159 "EmbeddedWorkerInstance::ProcessAllocate",
161 "Scope", scope
.spec(),
162 "Script URL", script_url
.spec());
163 params
->embedded_worker_id
= embedded_worker_id_
;
164 params
->service_worker_version_id
= service_worker_version_id
;
165 params
->scope
= scope
;
166 params
->script_url
= script_url
;
167 params
->worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
168 params
->wait_for_debugger
= false;
169 params
->v8_cache_options
= GetV8CacheOptions();
170 context_
->process_manager()->AllocateWorkerProcess(
174 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated
,
175 weak_factory_
.GetWeakPtr(),
177 base::Passed(¶ms
),
181 ServiceWorkerStatusCode
EmbeddedWorkerInstance::Stop() {
182 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
183 ServiceWorkerStatusCode status
=
184 registry_
->StopWorker(process_id_
, embedded_worker_id_
);
185 UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status
,
186 SERVICE_WORKER_ERROR_MAX_VALUE
);
187 // StopWorker could fail if we were starting up and don't have a process yet,
188 // or we can no longer communicate with the process. So just detach.
189 if (status
!= SERVICE_WORKER_OK
) {
195 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopping());
199 void EmbeddedWorkerInstance::StopIfIdle() {
200 if (devtools_attached_
) {
202 devtools_proxy_
->NotifyWorkerStopIgnored();
208 ServiceWorkerStatusCode
EmbeddedWorkerInstance::SendMessage(
209 const IPC::Message
& message
) {
210 DCHECK_NE(kInvalidEmbeddedWorkerThreadId
, thread_id_
);
211 if (status_
!= RUNNING
&& status_
!= STARTING
)
212 return SERVICE_WORKER_ERROR_IPC_FAILED
;
213 return registry_
->Send(process_id_
,
214 new EmbeddedWorkerContextMsg_MessageToWorker(
215 thread_id_
, embedded_worker_id_
, message
));
218 ServiceRegistry
* EmbeddedWorkerInstance::GetServiceRegistry() {
219 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
220 return service_registry_
.get();
223 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
224 base::WeakPtr
<ServiceWorkerContextCore
> context
,
225 int embedded_worker_id
)
227 registry_(context
->embedded_worker_registry()),
228 embedded_worker_id_(embedded_worker_id
),
230 starting_phase_(NOT_STARTING
),
232 thread_id_(kInvalidEmbeddedWorkerThreadId
),
233 devtools_attached_(false),
234 network_accessed_for_script_(false),
235 weak_factory_(this) {
239 void EmbeddedWorkerInstance::RunProcessAllocated(
240 base::WeakPtr
<EmbeddedWorkerInstance
> instance
,
241 base::WeakPtr
<ServiceWorkerContextCore
> context
,
242 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
243 const EmbeddedWorkerInstance::StatusCallback
& callback
,
244 ServiceWorkerStatusCode status
,
246 bool is_new_process
) {
248 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
252 if (status
== SERVICE_WORKER_OK
) {
253 // We only have a process allocated if the status is OK.
254 context
->process_manager()->ReleaseWorkerProcess(
255 params
->embedded_worker_id
);
257 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
260 instance
->ProcessAllocated(params
.Pass(), callback
, process_id
,
261 is_new_process
, status
);
264 void EmbeddedWorkerInstance::ProcessAllocated(
265 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
266 const StatusCallback
& callback
,
269 ServiceWorkerStatusCode status
) {
270 DCHECK_EQ(process_id_
, -1);
271 TRACE_EVENT_ASYNC_END1("ServiceWorker",
272 "EmbeddedWorkerInstance::ProcessAllocate",
275 if (status
!= SERVICE_WORKER_OK
) {
276 OnStartFailed(callback
, status
);
279 const int64 service_worker_version_id
= params
->service_worker_version_id
;
280 process_id_
= process_id
;
281 GURL
script_url(params
->script_url
);
283 // Register this worker to DevToolsManager on UI thread, then continue to
284 // call SendStartWorker on IO thread.
285 starting_phase_
= REGISTERING_TO_DEVTOOLS
;
286 BrowserThread::PostTask(
287 BrowserThread::UI
, FROM_HERE
,
288 base::Bind(RegisterToWorkerDevToolsManagerOnUI
, process_id_
,
289 context_
.get(), context_
, service_worker_version_id
,
291 base::Bind(&EmbeddedWorkerInstance::SendStartWorker
,
292 weak_factory_
.GetWeakPtr(), base::Passed(¶ms
),
293 callback
, is_new_process
)));
296 void EmbeddedWorkerInstance::SendStartWorker(
297 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
298 const StatusCallback
& callback
,
300 int worker_devtools_agent_route_id
,
301 bool wait_for_debugger
) {
302 // We may have been detached or stopped at some point during the start up
303 // process, making process_id_ and other state invalid. If that happened,
304 // abort instead of trying to send the IPC.
305 if (status_
!= STARTING
) {
306 OnStartFailed(callback
, SERVICE_WORKER_ERROR_ABORT
);
310 if (worker_devtools_agent_route_id
!= MSG_ROUTING_NONE
) {
311 DCHECK(!devtools_proxy_
);
312 devtools_proxy_
.reset(new DevToolsProxy(process_id_
,
313 worker_devtools_agent_route_id
));
315 params
->worker_devtools_agent_route_id
= worker_devtools_agent_route_id
;
316 params
->wait_for_debugger
= wait_for_debugger
;
317 if (params
->wait_for_debugger
) {
318 // We don't measure the start time when wait_for_debugger flag is set. So we
319 // set the NULL time here.
320 start_timing_
= base::TimeTicks();
322 DCHECK(!start_timing_
.is_null());
323 if (is_new_process
) {
324 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.NewProcessAllocation",
325 base::TimeTicks::Now() - start_timing_
);
327 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ExistingProcessAllocation",
328 base::TimeTicks::Now() - start_timing_
);
330 UMA_HISTOGRAM_BOOLEAN("EmbeddedWorkerInstance.ProcessCreated",
332 // Reset |start_timing_| to measure the time excluding the process
334 start_timing_
= base::TimeTicks::Now();
337 starting_phase_
= SENT_START_WORKER
;
338 ServiceWorkerStatusCode status
=
339 registry_
->SendStartWorker(params
.Pass(), process_id_
);
340 if (status
!= SERVICE_WORKER_OK
) {
341 OnStartFailed(callback
, status
);
344 DCHECK(start_callback_
.is_null());
345 start_callback_
= callback
;
348 void EmbeddedWorkerInstance::OnReadyForInspection() {
350 devtools_proxy_
->NotifyWorkerReadyForInspection();
353 void EmbeddedWorkerInstance::OnScriptLoaded() {
354 starting_phase_
= SCRIPT_LOADED
;
357 void EmbeddedWorkerInstance::OnThreadStarted(int thread_id
) {
358 starting_phase_
= THREAD_STARTED
;
359 if (!start_timing_
.is_null()) {
360 if (network_accessed_for_script_
) {
361 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
362 base::TimeTicks::Now() - start_timing_
);
365 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
366 base::TimeTicks::Now() - start_timing_
);
368 // Reset |start_timing_| to measure the time excluding the process
369 // allocation time and the script loading time.
370 start_timing_
= base::TimeTicks::Now();
372 thread_id_
= thread_id
;
373 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnThreadStarted());
375 mojo::ServiceProviderPtr exposed_services
;
376 service_registry_
->Bind(GetProxy(&exposed_services
));
377 mojo::ServiceProviderPtr services
;
378 mojo::InterfaceRequest
<mojo::ServiceProvider
> services_request
=
380 BrowserThread::PostTask(
381 BrowserThread::UI
, FROM_HERE
,
382 base::Bind(SetupMojoOnUIThread
, process_id_
, thread_id_
,
383 base::Passed(&services_request
),
384 base::Passed(&exposed_services
)));
385 service_registry_
->BindRemoteServiceProvider(services
.Pass());
388 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
391 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success
) {
392 starting_phase_
= SCRIPT_EVALUATED
;
393 if (start_callback_
.is_null()) {
394 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
397 if (success
&& !start_timing_
.is_null()) {
398 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
399 base::TimeTicks::Now() - start_timing_
);
401 StatusCallback callback
= start_callback_
;
402 start_callback_
.Reset();
403 callback
.Run(success
? SERVICE_WORKER_OK
404 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED
);
405 // |this| may be destroyed by the callback.
408 void EmbeddedWorkerInstance::OnStarted() {
409 // Stop is requested before OnStarted is sent back from the worker.
410 if (status_
== STOPPING
)
412 DCHECK(status_
== STARTING
);
414 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarted());
417 void EmbeddedWorkerInstance::OnStopped() {
418 Status old_status
= status_
;
420 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
423 void EmbeddedWorkerInstance::OnDetached() {
424 Status old_status
= status_
;
426 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnDetached(old_status
));
429 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message
& message
) {
430 ListenerList::Iterator
it(&listener_list_
);
431 while (Listener
* listener
= it
.GetNext()) {
432 if (listener
->OnMessageReceived(message
))
438 void EmbeddedWorkerInstance::OnReportException(
439 const base::string16
& error_message
,
442 const GURL
& source_url
) {
446 OnReportException(error_message
, line_number
, column_number
, source_url
));
449 void EmbeddedWorkerInstance::OnReportConsoleMessage(
450 int source_identifier
,
452 const base::string16
& message
,
454 const GURL
& source_url
) {
458 OnReportConsoleMessage(
459 source_identifier
, message_level
, message
, line_number
, source_url
));
462 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
464 return devtools_proxy_
->agent_route_id();
465 return MSG_ROUTING_NONE
;
468 MessagePortMessageFilter
* EmbeddedWorkerInstance::message_port_message_filter()
470 return registry_
->MessagePortMessageFilterForProcess(process_id_
);
473 void EmbeddedWorkerInstance::AddListener(Listener
* listener
) {
474 listener_list_
.AddObserver(listener
);
477 void EmbeddedWorkerInstance::RemoveListener(Listener
* listener
) {
478 listener_list_
.RemoveObserver(listener
);
481 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
482 starting_phase_
= SCRIPT_DOWNLOADING
;
483 network_accessed_for_script_
= true;
486 void EmbeddedWorkerInstance::ReleaseProcess() {
487 devtools_proxy_
.reset();
488 if (context_
&& process_id_
!= -1)
489 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
493 service_registry_
.reset();
494 start_callback_
.Reset();
497 void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback
& callback
,
498 ServiceWorkerStatusCode status
) {
499 Status old_status
= status_
;
501 base::WeakPtr
<EmbeddedWorkerInstance
> weak_this
= weak_factory_
.GetWeakPtr();
502 callback
.Run(status
);
503 if (weak_this
&& old_status
!= STOPPED
)
504 FOR_EACH_OBSERVER(Listener
, weak_this
->listener_list_
,
505 OnStopped(old_status
));
509 std::string
EmbeddedWorkerInstance::StatusToString(Status status
) {
520 NOTREACHED() << status
;
521 return std::string();
525 std::string
EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase
) {
528 return "Not in STARTING status";
529 case ALLOCATING_PROCESS
:
530 return "Allocating process";
531 case REGISTERING_TO_DEVTOOLS
:
532 return "Registering to DevTools";
533 case SENT_START_WORKER
:
534 return "Sent StartWorker message to renderer";
535 case SCRIPT_DOWNLOADING
:
536 return "Script downloading";
538 return "Script loaded";
539 case SCRIPT_EVALUATED
:
540 return "Script evaluated";
542 return "Thread started";
543 case STARTING_PHASE_MAX_VALUE
:
546 NOTREACHED() << phase
;
547 return std::string();
550 } // namespace content