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"
10 #include "base/bind_helpers.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/threading/non_thread_safe.h"
13 #include "base/trace_event/trace_event.h"
14 #include "content/browser/devtools/service_worker_devtools_manager.h"
15 #include "content/browser/service_worker/embedded_worker_registry.h"
16 #include "content/browser/service_worker/service_worker_context_core.h"
17 #include "content/common/content_switches_internal.h"
18 #include "content/common/mojo/service_registry_impl.h"
19 #include "content/common/service_worker/embedded_worker_messages.h"
20 #include "content/common/service_worker/embedded_worker_setup.mojom.h"
21 #include "content/common/service_worker/service_worker_types.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "ipc/ipc_message.h"
31 // Functor to sort by the .second element of a struct.
32 struct SecondGreater
{
33 template <typename Value
>
34 bool operator()(const Value
& lhs
, const Value
& rhs
) {
35 return lhs
.second
> rhs
.second
;
39 void NotifyWorkerReadyForInspectionOnUI(int worker_process_id
,
40 int worker_route_id
) {
41 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
42 ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
43 worker_process_id
, worker_route_id
);
46 void NotifyWorkerDestroyedOnUI(int worker_process_id
, int worker_route_id
) {
47 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
48 ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
49 worker_process_id
, worker_route_id
);
52 void NotifyWorkerStopIgnoredOnUI(int worker_process_id
, int worker_route_id
) {
53 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
54 ServiceWorkerDevToolsManager::GetInstance()->WorkerStopIgnored(
55 worker_process_id
, worker_route_id
);
58 void RegisterToWorkerDevToolsManagerOnUI(
60 const ServiceWorkerContextCore
* service_worker_context
,
61 const base::WeakPtr
<ServiceWorkerContextCore
>& service_worker_context_weak
,
62 int64 service_worker_version_id
,
64 const base::Callback
<void(int worker_devtools_agent_route_id
,
65 bool wait_for_debugger
)>& callback
) {
66 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
67 int worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
68 bool wait_for_debugger
= false;
69 if (RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
)) {
70 // |rph| may be NULL in unit tests.
71 worker_devtools_agent_route_id
= rph
->GetNextRoutingID();
73 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
75 worker_devtools_agent_route_id
,
76 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
77 service_worker_context
,
78 service_worker_context_weak
,
79 service_worker_version_id
,
82 BrowserThread::PostTask(
85 base::Bind(callback
, worker_devtools_agent_route_id
, wait_for_debugger
));
88 void SetupMojoOnUIThread(int process_id
,
90 mojo::InterfaceRequest
<mojo::ServiceProvider
> services
,
91 mojo::ServiceProviderPtr exposed_services
) {
92 RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
);
93 // |rph| may be NULL in unit tests.
96 EmbeddedWorkerSetupPtr setup
;
97 rph
->GetServiceRegistry()->ConnectToRemoteService(mojo::GetProxy(&setup
));
98 setup
->ExchangeServiceProviders(thread_id
, services
.Pass(),
99 exposed_services
.Pass());
104 // Lives on IO thread, proxies notifications to DevToolsManager that lives on
105 // UI thread. Owned by EmbeddedWorkerInstance.
106 class EmbeddedWorkerInstance::DevToolsProxy
: public base::NonThreadSafe
{
108 DevToolsProxy(int process_id
, int agent_route_id
)
109 : process_id_(process_id
),
110 agent_route_id_(agent_route_id
) {}
113 BrowserThread::PostTask(
116 base::Bind(NotifyWorkerDestroyedOnUI
,
117 process_id_
, agent_route_id_
));
120 void NotifyWorkerReadyForInspection() {
121 DCHECK(CalledOnValidThread());
122 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
123 base::Bind(NotifyWorkerReadyForInspectionOnUI
,
124 process_id_
, agent_route_id_
));
127 void NotifyWorkerStopIgnored() {
128 DCHECK(CalledOnValidThread());
129 BrowserThread::PostTask(BrowserThread::UI
,
131 base::Bind(NotifyWorkerStopIgnoredOnUI
,
132 process_id_
, agent_route_id_
));
135 int agent_route_id() const { return agent_route_id_
; }
138 const int process_id_
;
139 const int agent_route_id_
;
140 DISALLOW_COPY_AND_ASSIGN(DevToolsProxy
);
143 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
144 DCHECK(status_
== STOPPING
|| status_
== STOPPED
);
145 devtools_proxy_
.reset();
146 if (context_
&& process_id_
!= -1)
147 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
148 registry_
->RemoveWorker(process_id_
, embedded_worker_id_
);
151 void EmbeddedWorkerInstance::Start(int64 service_worker_version_id
,
153 const GURL
& script_url
,
154 const StatusCallback
& callback
) {
156 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
159 DCHECK(status_
== STOPPED
);
160 start_timing_
= base::TimeTicks::Now();
162 starting_phase_
= ALLOCATING_PROCESS
;
163 network_accessed_for_script_
= false;
164 service_registry_
.reset(new ServiceRegistryImpl());
165 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarting());
166 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params(
167 new EmbeddedWorkerMsg_StartWorker_Params());
168 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
169 "EmbeddedWorkerInstance::ProcessAllocate",
171 "Scope", scope
.spec(),
172 "Script URL", script_url
.spec());
173 params
->embedded_worker_id
= embedded_worker_id_
;
174 params
->service_worker_version_id
= service_worker_version_id
;
175 params
->scope
= scope
;
176 params
->script_url
= script_url
;
177 params
->worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
178 params
->wait_for_debugger
= false;
179 params
->v8_cache_options
= GetV8CacheOptions();
180 context_
->process_manager()->AllocateWorkerProcess(
184 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated
,
185 weak_factory_
.GetWeakPtr(),
187 base::Passed(¶ms
),
191 ServiceWorkerStatusCode
EmbeddedWorkerInstance::Stop() {
192 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
193 ServiceWorkerStatusCode status
=
194 registry_
->StopWorker(process_id_
, embedded_worker_id_
);
195 // StopWorker could fail if we can't talk to the worker, which should
196 // basically means it's being terminated, so unconditionally change
197 // the status to STOPPING.
199 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopping());
203 void EmbeddedWorkerInstance::StopIfIdle() {
204 if (devtools_attached_
) {
206 devtools_proxy_
->NotifyWorkerStopIgnored();
212 ServiceWorkerStatusCode
EmbeddedWorkerInstance::SendMessage(
213 const IPC::Message
& message
) {
214 DCHECK_NE(kInvalidEmbeddedWorkerThreadId
, thread_id_
);
215 if (status_
!= RUNNING
&& status_
!= STARTING
)
216 return SERVICE_WORKER_ERROR_IPC_FAILED
;
217 return registry_
->Send(process_id_
,
218 new EmbeddedWorkerContextMsg_MessageToWorker(
219 thread_id_
, embedded_worker_id_
, message
));
222 ServiceRegistry
* EmbeddedWorkerInstance::GetServiceRegistry() {
223 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
224 return service_registry_
.get();
227 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
228 base::WeakPtr
<ServiceWorkerContextCore
> context
,
229 int embedded_worker_id
)
231 registry_(context
->embedded_worker_registry()),
232 embedded_worker_id_(embedded_worker_id
),
234 starting_phase_(NOT_STARTING
),
236 thread_id_(kInvalidEmbeddedWorkerThreadId
),
237 devtools_attached_(false),
238 network_accessed_for_script_(false),
239 weak_factory_(this) {
243 void EmbeddedWorkerInstance::RunProcessAllocated(
244 base::WeakPtr
<EmbeddedWorkerInstance
> instance
,
245 base::WeakPtr
<ServiceWorkerContextCore
> context
,
246 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
247 const EmbeddedWorkerInstance::StatusCallback
& callback
,
248 ServiceWorkerStatusCode status
,
250 bool is_new_process
) {
252 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
256 if (status
== SERVICE_WORKER_OK
) {
257 // We only have a process allocated if the status is OK.
258 context
->process_manager()->ReleaseWorkerProcess(
259 params
->embedded_worker_id
);
261 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
264 instance
->ProcessAllocated(params
.Pass(), callback
, process_id
,
265 is_new_process
, status
);
268 void EmbeddedWorkerInstance::ProcessAllocated(
269 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
270 const StatusCallback
& callback
,
273 ServiceWorkerStatusCode status
) {
274 DCHECK_EQ(process_id_
, -1);
275 TRACE_EVENT_ASYNC_END1("ServiceWorker",
276 "EmbeddedWorkerInstance::ProcessAllocate",
279 if (status
!= SERVICE_WORKER_OK
) {
280 Status old_status
= status_
;
282 service_registry_
.reset();
283 callback
.Run(status
);
284 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
287 const int64 service_worker_version_id
= params
->service_worker_version_id
;
288 process_id_
= process_id
;
289 GURL
script_url(params
->script_url
);
291 // Register this worker to DevToolsManager on UI thread, then continue to
292 // call SendStartWorker on IO thread.
293 starting_phase_
= REGISTERING_TO_DEVTOOLS
;
294 BrowserThread::PostTask(
295 BrowserThread::UI
, FROM_HERE
,
296 base::Bind(RegisterToWorkerDevToolsManagerOnUI
, process_id_
,
297 context_
.get(), context_
, service_worker_version_id
,
299 base::Bind(&EmbeddedWorkerInstance::SendStartWorker
,
300 weak_factory_
.GetWeakPtr(), base::Passed(¶ms
),
301 callback
, is_new_process
)));
304 void EmbeddedWorkerInstance::SendStartWorker(
305 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
306 const StatusCallback
& callback
,
308 int worker_devtools_agent_route_id
,
309 bool wait_for_debugger
) {
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 callback
.Run(status
);
344 DCHECK(start_callback_
.is_null());
345 start_callback_
= callback
;
348 void EmbeddedWorkerInstance::OnReadyForInspection() {
350 devtools_proxy_
->NotifyWorkerReadyForInspection();
353 void EmbeddedWorkerInstance::OnScriptLoaded(int thread_id
) {
354 starting_phase_
= SCRIPT_LOADED
;
355 if (!start_timing_
.is_null()) {
356 if (network_accessed_for_script_
) {
357 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
358 base::TimeTicks::Now() - start_timing_
);
361 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
362 base::TimeTicks::Now() - start_timing_
);
364 // Reset |start_timing_| to measure the time excluding the process
365 // allocation time and the script loading time.
366 start_timing_
= base::TimeTicks::Now();
368 thread_id_
= thread_id
;
369 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnScriptLoaded());
371 mojo::ServiceProviderPtr exposed_services
;
372 service_registry_
->Bind(GetProxy(&exposed_services
));
373 mojo::ServiceProviderPtr services
;
374 mojo::InterfaceRequest
<mojo::ServiceProvider
> services_request
=
376 BrowserThread::PostTask(
377 BrowserThread::UI
, FROM_HERE
,
378 base::Bind(SetupMojoOnUIThread
, process_id_
, thread_id_
,
379 base::Passed(&services_request
),
380 base::Passed(&exposed_services
)));
381 service_registry_
->BindRemoteServiceProvider(services
.Pass());
384 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
387 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success
) {
388 starting_phase_
= SCRIPT_EVALUATED
;
389 if (start_callback_
.is_null()) {
390 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
393 if (success
&& !start_timing_
.is_null()) {
394 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
395 base::TimeTicks::Now() - start_timing_
);
397 start_callback_
.Run(success
? SERVICE_WORKER_OK
398 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED
);
399 start_callback_
.Reset();
402 void EmbeddedWorkerInstance::OnStarted() {
403 // Stop is requested before OnStarted is sent back from the worker.
404 if (status_
== STOPPING
)
406 DCHECK(status_
== STARTING
);
408 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarted());
411 void EmbeddedWorkerInstance::OnStopped() {
412 Status old_status
= status_
;
414 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
417 void EmbeddedWorkerInstance::OnDetached() {
418 Status old_status
= status_
;
420 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnDetached(old_status
));
423 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message
& message
) {
424 ListenerList::Iterator
it(&listener_list_
);
425 while (Listener
* listener
= it
.GetNext()) {
426 if (listener
->OnMessageReceived(message
))
432 void EmbeddedWorkerInstance::OnReportException(
433 const base::string16
& error_message
,
436 const GURL
& source_url
) {
440 OnReportException(error_message
, line_number
, column_number
, source_url
));
443 void EmbeddedWorkerInstance::OnReportConsoleMessage(
444 int source_identifier
,
446 const base::string16
& message
,
448 const GURL
& source_url
) {
452 OnReportConsoleMessage(
453 source_identifier
, message_level
, message
, line_number
, source_url
));
456 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
458 return devtools_proxy_
->agent_route_id();
459 return MSG_ROUTING_NONE
;
462 MessagePortMessageFilter
* EmbeddedWorkerInstance::message_port_message_filter()
464 return registry_
->MessagePortMessageFilterForProcess(process_id_
);
467 void EmbeddedWorkerInstance::AddListener(Listener
* listener
) {
468 listener_list_
.AddObserver(listener
);
471 void EmbeddedWorkerInstance::RemoveListener(Listener
* listener
) {
472 listener_list_
.RemoveObserver(listener
);
475 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
476 starting_phase_
= SCRIPT_DOWNLOADING
;
477 network_accessed_for_script_
= true;
480 void EmbeddedWorkerInstance::ReleaseProcess() {
481 devtools_proxy_
.reset();
483 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
487 service_registry_
.reset();
488 start_callback_
.Reset();
492 std::string
EmbeddedWorkerInstance::StatusToString(Status status
) {
503 NOTREACHED() << status
;
504 return std::string();
508 std::string
EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase
) {
511 return "Not in STARTING status";
512 case ALLOCATING_PROCESS
:
513 return "Allocating process";
514 case REGISTERING_TO_DEVTOOLS
:
515 return "Registering to DevTools";
516 case SENT_START_WORKER
:
517 return "Sent StartWorker message to renderer";
518 case SCRIPT_DOWNLOADING
:
519 return "Script downloading";
521 return "Script loaded";
522 case SCRIPT_EVALUATED
:
523 return "Script evaluated";
524 case STARTING_PHASE_MAX_VALUE
:
527 NOTREACHED() << phase
;
528 return std::string();
531 } // namespace content