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/service_worker/embedded_worker_messages.h"
19 #include "content/common/service_worker/service_worker_types.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "ipc/ipc_message.h"
29 // Functor to sort by the .second element of a struct.
30 struct SecondGreater
{
31 template <typename Value
>
32 bool operator()(const Value
& lhs
, const Value
& rhs
) {
33 return lhs
.second
> rhs
.second
;
37 void NotifyWorkerReadyForInspectionOnUI(int worker_process_id
,
38 int worker_route_id
) {
39 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
40 ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
41 worker_process_id
, worker_route_id
);
44 void NotifyWorkerDestroyedOnUI(int worker_process_id
, int worker_route_id
) {
45 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
46 ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
47 worker_process_id
, worker_route_id
);
50 void NotifyWorkerStopIgnoredOnUI(int worker_process_id
, int worker_route_id
) {
51 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
52 ServiceWorkerDevToolsManager::GetInstance()->WorkerStopIgnored(
53 worker_process_id
, worker_route_id
);
56 void RegisterToWorkerDevToolsManagerOnUI(
58 const ServiceWorkerContextCore
* service_worker_context
,
59 const base::WeakPtr
<ServiceWorkerContextCore
>& service_worker_context_weak
,
60 int64 service_worker_version_id
,
62 const base::Callback
<void(int worker_devtools_agent_route_id
,
63 bool wait_for_debugger
)>& callback
) {
64 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
65 int worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
66 bool wait_for_debugger
= false;
67 if (RenderProcessHost
* rph
= RenderProcessHost::FromID(process_id
)) {
68 // |rph| may be NULL in unit tests.
69 worker_devtools_agent_route_id
= rph
->GetNextRoutingID();
71 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
73 worker_devtools_agent_route_id
,
74 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
75 service_worker_context
,
76 service_worker_context_weak
,
77 service_worker_version_id
,
80 BrowserThread::PostTask(
83 base::Bind(callback
, worker_devtools_agent_route_id
, wait_for_debugger
));
88 // Lives on IO thread, proxies notifications to DevToolsManager that lives on
89 // UI thread. Owned by EmbeddedWorkerInstance.
90 class EmbeddedWorkerInstance::DevToolsProxy
: public base::NonThreadSafe
{
92 DevToolsProxy(int process_id
, int agent_route_id
)
93 : process_id_(process_id
),
94 agent_route_id_(agent_route_id
) {}
97 BrowserThread::PostTask(
100 base::Bind(NotifyWorkerDestroyedOnUI
,
101 process_id_
, agent_route_id_
));
104 void NotifyWorkerReadyForInspection() {
105 DCHECK(CalledOnValidThread());
106 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
107 base::Bind(NotifyWorkerReadyForInspectionOnUI
,
108 process_id_
, agent_route_id_
));
111 void NotifyWorkerStopIgnored() {
112 DCHECK(CalledOnValidThread());
113 BrowserThread::PostTask(BrowserThread::UI
,
115 base::Bind(NotifyWorkerStopIgnoredOnUI
,
116 process_id_
, agent_route_id_
));
119 int agent_route_id() const { return agent_route_id_
; }
122 const int process_id_
;
123 const int agent_route_id_
;
124 DISALLOW_COPY_AND_ASSIGN(DevToolsProxy
);
127 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
128 if (status_
== STARTING
|| status_
== RUNNING
)
130 devtools_proxy_
.reset();
131 if (context_
&& process_id_
!= -1)
132 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
133 registry_
->RemoveWorker(process_id_
, embedded_worker_id_
);
136 void EmbeddedWorkerInstance::Start(int64 service_worker_version_id
,
138 const GURL
& script_url
,
139 bool pause_after_download
,
140 const StatusCallback
& callback
) {
142 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
145 DCHECK(status_
== STOPPED
);
146 start_timing_
= base::TimeTicks::Now();
148 starting_phase_
= ALLOCATING_PROCESS
;
149 network_accessed_for_script_
= false;
150 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarting());
151 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params(
152 new EmbeddedWorkerMsg_StartWorker_Params());
153 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
154 "EmbeddedWorkerInstance::ProcessAllocate",
156 "Scope", scope
.spec(),
157 "Script URL", script_url
.spec());
158 params
->embedded_worker_id
= embedded_worker_id_
;
159 params
->service_worker_version_id
= service_worker_version_id
;
160 params
->scope
= scope
;
161 params
->script_url
= script_url
;
162 params
->worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
163 params
->pause_after_download
= pause_after_download
;
164 params
->wait_for_debugger
= false;
165 params
->v8_cache_options
= GetV8CacheOptions();
166 context_
->process_manager()->AllocateWorkerProcess(
170 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated
,
171 weak_factory_
.GetWeakPtr(),
173 base::Passed(¶ms
),
177 ServiceWorkerStatusCode
EmbeddedWorkerInstance::Stop() {
178 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
179 ServiceWorkerStatusCode status
=
180 registry_
->StopWorker(process_id_
, embedded_worker_id_
);
181 if (status
== SERVICE_WORKER_OK
) {
183 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopping());
188 void EmbeddedWorkerInstance::StopIfIdle() {
189 if (devtools_attached_
) {
191 devtools_proxy_
->NotifyWorkerStopIgnored();
197 void EmbeddedWorkerInstance::ResumeAfterDownload() {
198 DCHECK_EQ(STARTING
, status_
);
201 new EmbeddedWorkerMsg_ResumeAfterDownload(embedded_worker_id_
));
204 ServiceWorkerStatusCode
EmbeddedWorkerInstance::SendMessage(
205 const IPC::Message
& message
) {
206 DCHECK_NE(kInvalidEmbeddedWorkerThreadId
, thread_id_
);
207 if (status_
!= RUNNING
&& status_
!= STARTING
)
208 return SERVICE_WORKER_ERROR_IPC_FAILED
;
209 return registry_
->Send(process_id_
,
210 new EmbeddedWorkerContextMsg_MessageToWorker(
211 thread_id_
, embedded_worker_id_
, message
));
214 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
215 base::WeakPtr
<ServiceWorkerContextCore
> context
,
216 int embedded_worker_id
)
218 registry_(context
->embedded_worker_registry()),
219 embedded_worker_id_(embedded_worker_id
),
221 starting_phase_(NOT_STARTING
),
223 thread_id_(kInvalidEmbeddedWorkerThreadId
),
224 devtools_attached_(false),
225 network_accessed_for_script_(false),
226 weak_factory_(this) {
230 void EmbeddedWorkerInstance::RunProcessAllocated(
231 base::WeakPtr
<EmbeddedWorkerInstance
> instance
,
232 base::WeakPtr
<ServiceWorkerContextCore
> context
,
233 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
234 const EmbeddedWorkerInstance::StatusCallback
& callback
,
235 ServiceWorkerStatusCode status
,
238 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
242 if (status
== SERVICE_WORKER_OK
) {
243 // We only have a process allocated if the status is OK.
244 context
->process_manager()->ReleaseWorkerProcess(
245 params
->embedded_worker_id
);
247 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
250 instance
->ProcessAllocated(params
.Pass(), callback
, process_id
, status
);
253 void EmbeddedWorkerInstance::ProcessAllocated(
254 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
255 const StatusCallback
& callback
,
257 ServiceWorkerStatusCode status
) {
258 DCHECK_EQ(process_id_
, -1);
259 TRACE_EVENT_ASYNC_END1("ServiceWorker",
260 "EmbeddedWorkerInstance::ProcessAllocate",
263 if (status
!= SERVICE_WORKER_OK
) {
264 Status old_status
= status_
;
266 callback
.Run(status
);
267 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
270 const int64 service_worker_version_id
= params
->service_worker_version_id
;
271 process_id_
= process_id
;
272 GURL
script_url(params
->script_url
);
274 // Register this worker to DevToolsManager on UI thread, then continue to
275 // call SendStartWorker on IO thread.
276 starting_phase_
= REGISTERING_TO_DEVTOOLS
;
277 BrowserThread::PostTask(
280 base::Bind(RegisterToWorkerDevToolsManagerOnUI
,
284 service_worker_version_id
,
286 base::Bind(&EmbeddedWorkerInstance::SendStartWorker
,
287 weak_factory_
.GetWeakPtr(),
288 base::Passed(¶ms
),
292 void EmbeddedWorkerInstance::SendStartWorker(
293 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
294 const StatusCallback
& callback
,
295 int worker_devtools_agent_route_id
,
296 bool wait_for_debugger
) {
297 if (worker_devtools_agent_route_id
!= MSG_ROUTING_NONE
) {
298 DCHECK(!devtools_proxy_
);
299 devtools_proxy_
.reset(new DevToolsProxy(process_id_
,
300 worker_devtools_agent_route_id
));
302 params
->worker_devtools_agent_route_id
= worker_devtools_agent_route_id
;
303 params
->wait_for_debugger
= wait_for_debugger
;
304 if (params
->pause_after_download
|| params
->wait_for_debugger
) {
305 // We don't measure the start time when pause_after_download or
306 // wait_for_debugger flag is set. So we set the NULL time here.
307 start_timing_
= base::TimeTicks();
309 DCHECK(!start_timing_
.is_null());
310 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ProcessAllocation",
311 base::TimeTicks::Now() - start_timing_
);
312 // Reset |start_timing_| to measure the time excluding the process
314 start_timing_
= base::TimeTicks::Now();
317 starting_phase_
= SENT_START_WORKER
;
318 ServiceWorkerStatusCode status
=
319 registry_
->SendStartWorker(params
.Pass(), process_id_
);
320 if (status
!= SERVICE_WORKER_OK
) {
321 callback
.Run(status
);
324 DCHECK(start_callback_
.is_null());
325 start_callback_
= callback
;
328 void EmbeddedWorkerInstance::OnReadyForInspection() {
330 devtools_proxy_
->NotifyWorkerReadyForInspection();
333 void EmbeddedWorkerInstance::OnScriptLoaded(int thread_id
) {
334 starting_phase_
= SCRIPT_LOADED
;
335 if (!start_timing_
.is_null()) {
336 if (network_accessed_for_script_
) {
337 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
338 base::TimeTicks::Now() - start_timing_
);
341 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
342 base::TimeTicks::Now() - start_timing_
);
344 // Reset |start_timing_| to measure the time excluding the process
345 // allocation time and the script loading time.
346 start_timing_
= base::TimeTicks::Now();
348 thread_id_
= thread_id
;
349 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnScriptLoaded());
352 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
355 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success
) {
356 starting_phase_
= SCRIPT_EVALUATED
;
357 if (success
&& !start_timing_
.is_null()) {
358 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
359 base::TimeTicks::Now() - start_timing_
);
361 DCHECK(!start_callback_
.is_null());
362 start_callback_
.Run(success
? SERVICE_WORKER_OK
363 : SERVICE_WORKER_ERROR_START_WORKER_FAILED
);
364 start_callback_
.Reset();
367 void EmbeddedWorkerInstance::OnStarted() {
368 // Stop is requested before OnStarted is sent back from the worker.
369 if (status_
== STOPPING
)
371 DCHECK(status_
== STARTING
);
373 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarted());
376 void EmbeddedWorkerInstance::OnStopped() {
377 devtools_proxy_
.reset();
379 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
380 Status old_status
= status_
;
384 start_callback_
.Reset();
385 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
388 void EmbeddedWorkerInstance::OnPausedAfterDownload() {
389 // Stop can be requested before getting this far.
390 if (status_
== STOPPING
)
392 DCHECK(status_
== STARTING
);
393 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnPausedAfterDownload());
396 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message
& message
) {
397 ListenerList::Iterator
it(&listener_list_
);
398 while (Listener
* listener
= it
.GetNext()) {
399 if (listener
->OnMessageReceived(message
))
405 void EmbeddedWorkerInstance::OnReportException(
406 const base::string16
& error_message
,
409 const GURL
& source_url
) {
413 OnReportException(error_message
, line_number
, column_number
, source_url
));
416 void EmbeddedWorkerInstance::OnReportConsoleMessage(
417 int source_identifier
,
419 const base::string16
& message
,
421 const GURL
& source_url
) {
425 OnReportConsoleMessage(
426 source_identifier
, message_level
, message
, line_number
, source_url
));
429 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
431 return devtools_proxy_
->agent_route_id();
432 return MSG_ROUTING_NONE
;
435 MessagePortMessageFilter
* EmbeddedWorkerInstance::message_port_message_filter()
437 return registry_
->MessagePortMessageFilterForProcess(process_id_
);
440 void EmbeddedWorkerInstance::AddListener(Listener
* listener
) {
441 listener_list_
.AddObserver(listener
);
444 void EmbeddedWorkerInstance::RemoveListener(Listener
* listener
) {
445 listener_list_
.RemoveObserver(listener
);
448 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
449 starting_phase_
= SCRIPT_DOWNLOADING
;
450 network_accessed_for_script_
= true;
454 std::string
EmbeddedWorkerInstance::StatusToString(Status status
) {
465 NOTREACHED() << status
;
466 return std::string();
470 std::string
EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase
) {
473 return "Not in STARTING status";
474 case ALLOCATING_PROCESS
:
475 return "Allocating process";
476 case REGISTERING_TO_DEVTOOLS
:
477 return "Registering to DevTools";
478 case SENT_START_WORKER
:
479 return "Sent StartWorker message to renderer";
480 case SCRIPT_DOWNLOADING
:
481 return "Script downloading";
483 return "Script loaded";
484 case SCRIPT_EVALUATED
:
485 return "Script evaluated";
486 case STARTING_PHASE_MAX_VALUE
:
489 NOTREACHED() << phase
;
490 return std::string();
493 } // namespace content