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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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 network_accessed_for_script_
= false;
149 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params(
150 new EmbeddedWorkerMsg_StartWorker_Params());
151 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
152 "EmbeddedWorkerInstance::ProcessAllocate",
154 "Scope", scope
.spec(),
155 "Script URL", script_url
.spec());
156 params
->embedded_worker_id
= embedded_worker_id_
;
157 params
->service_worker_version_id
= service_worker_version_id
;
158 params
->scope
= scope
;
159 params
->script_url
= script_url
;
160 params
->worker_devtools_agent_route_id
= MSG_ROUTING_NONE
;
161 params
->pause_after_download
= pause_after_download
;
162 params
->wait_for_debugger
= false;
163 params
->v8_cache_options
= GetV8CacheOptions();
164 context_
->process_manager()->AllocateWorkerProcess(
168 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated
,
169 weak_factory_
.GetWeakPtr(),
171 base::Passed(¶ms
),
175 ServiceWorkerStatusCode
EmbeddedWorkerInstance::Stop() {
176 DCHECK(status_
== STARTING
|| status_
== RUNNING
) << status_
;
177 ServiceWorkerStatusCode status
=
178 registry_
->StopWorker(process_id_
, embedded_worker_id_
);
179 if (status
== SERVICE_WORKER_OK
)
184 void EmbeddedWorkerInstance::StopIfIdle() {
185 if (devtools_attached_
) {
187 devtools_proxy_
->NotifyWorkerStopIgnored();
193 void EmbeddedWorkerInstance::ResumeAfterDownload() {
194 DCHECK_EQ(STARTING
, status_
);
197 new EmbeddedWorkerMsg_ResumeAfterDownload(embedded_worker_id_
));
200 ServiceWorkerStatusCode
EmbeddedWorkerInstance::SendMessage(
201 const IPC::Message
& message
) {
202 DCHECK_NE(kInvalidEmbeddedWorkerThreadId
, thread_id_
);
203 if (status_
!= RUNNING
&& status_
!= STARTING
)
204 return SERVICE_WORKER_ERROR_IPC_FAILED
;
205 return registry_
->Send(process_id_
,
206 new EmbeddedWorkerContextMsg_MessageToWorker(
207 thread_id_
, embedded_worker_id_
, message
));
210 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
211 base::WeakPtr
<ServiceWorkerContextCore
> context
,
212 int embedded_worker_id
)
214 registry_(context
->embedded_worker_registry()),
215 embedded_worker_id_(embedded_worker_id
),
218 thread_id_(kInvalidEmbeddedWorkerThreadId
),
219 devtools_attached_(false),
220 network_accessed_for_script_(false),
221 weak_factory_(this) {
225 void EmbeddedWorkerInstance::RunProcessAllocated(
226 base::WeakPtr
<EmbeddedWorkerInstance
> instance
,
227 base::WeakPtr
<ServiceWorkerContextCore
> context
,
228 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
229 const EmbeddedWorkerInstance::StatusCallback
& callback
,
230 ServiceWorkerStatusCode status
,
233 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
237 if (status
== SERVICE_WORKER_OK
) {
238 // We only have a process allocated if the status is OK.
239 context
->process_manager()->ReleaseWorkerProcess(
240 params
->embedded_worker_id
);
242 callback
.Run(SERVICE_WORKER_ERROR_ABORT
);
245 instance
->ProcessAllocated(params
.Pass(), callback
, process_id
, status
);
248 void EmbeddedWorkerInstance::ProcessAllocated(
249 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
250 const StatusCallback
& callback
,
252 ServiceWorkerStatusCode status
) {
253 DCHECK_EQ(process_id_
, -1);
254 TRACE_EVENT_ASYNC_END1("ServiceWorker",
255 "EmbeddedWorkerInstance::ProcessAllocate",
258 if (status
!= SERVICE_WORKER_OK
) {
260 callback
.Run(status
);
263 const int64 service_worker_version_id
= params
->service_worker_version_id
;
264 process_id_
= process_id
;
265 GURL
script_url(params
->script_url
);
267 // Register this worker to DevToolsManager on UI thread, then continue to
268 // call SendStartWorker on IO thread.
269 BrowserThread::PostTask(
272 base::Bind(RegisterToWorkerDevToolsManagerOnUI
,
276 service_worker_version_id
,
278 base::Bind(&EmbeddedWorkerInstance::SendStartWorker
,
279 weak_factory_
.GetWeakPtr(),
280 base::Passed(¶ms
),
284 void EmbeddedWorkerInstance::SendStartWorker(
285 scoped_ptr
<EmbeddedWorkerMsg_StartWorker_Params
> params
,
286 const StatusCallback
& callback
,
287 int worker_devtools_agent_route_id
,
288 bool wait_for_debugger
) {
289 if (worker_devtools_agent_route_id
!= MSG_ROUTING_NONE
) {
290 DCHECK(!devtools_proxy_
);
291 devtools_proxy_
.reset(new DevToolsProxy(process_id_
,
292 worker_devtools_agent_route_id
));
294 params
->worker_devtools_agent_route_id
= worker_devtools_agent_route_id
;
295 params
->wait_for_debugger
= wait_for_debugger
;
296 if (params
->pause_after_download
|| params
->wait_for_debugger
) {
297 // We don't measure the start time when pause_after_download or
298 // wait_for_debugger flag is set. So we set the NULL time here.
299 start_timing_
= base::TimeTicks();
301 DCHECK(!start_timing_
.is_null());
302 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ProcessAllocation",
303 base::TimeTicks::Now() - start_timing_
);
304 // Reset |start_timing_| to measure the time excluding the process
306 start_timing_
= base::TimeTicks::Now();
308 ServiceWorkerStatusCode status
=
309 registry_
->SendStartWorker(params
.Pass(), process_id_
);
310 if (status
!= SERVICE_WORKER_OK
) {
311 callback
.Run(status
);
314 DCHECK(start_callback_
.is_null());
315 start_callback_
= callback
;
318 void EmbeddedWorkerInstance::OnReadyForInspection() {
320 devtools_proxy_
->NotifyWorkerReadyForInspection();
323 void EmbeddedWorkerInstance::OnScriptLoaded(int thread_id
) {
324 if (!start_timing_
.is_null()) {
325 if (network_accessed_for_script_
) {
326 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
327 base::TimeTicks::Now() - start_timing_
);
330 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
331 base::TimeTicks::Now() - start_timing_
);
333 // Reset |start_timing_| to measure the time excluding the process
334 // allocation time and the script loading time.
335 start_timing_
= base::TimeTicks::Now();
337 thread_id_
= thread_id
;
338 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnScriptLoaded());
341 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
344 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success
) {
345 if (success
&& !start_timing_
.is_null()) {
346 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
347 base::TimeTicks::Now() - start_timing_
);
349 DCHECK(!start_callback_
.is_null());
350 start_callback_
.Run(success
? SERVICE_WORKER_OK
351 : SERVICE_WORKER_ERROR_START_WORKER_FAILED
);
352 start_callback_
.Reset();
355 void EmbeddedWorkerInstance::OnStarted() {
356 // Stop is requested before OnStarted is sent back from the worker.
357 if (status_
== STOPPING
)
359 DCHECK(status_
== STARTING
);
361 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStarted());
364 void EmbeddedWorkerInstance::OnStopped() {
365 devtools_proxy_
.reset();
367 context_
->process_manager()->ReleaseWorkerProcess(embedded_worker_id_
);
368 Status old_status
= status_
;
372 start_callback_
.Reset();
373 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnStopped(old_status
));
376 void EmbeddedWorkerInstance::OnPausedAfterDownload() {
377 // Stop can be requested before getting this far.
378 if (status_
== STOPPING
)
380 DCHECK(status_
== STARTING
);
381 FOR_EACH_OBSERVER(Listener
, listener_list_
, OnPausedAfterDownload());
384 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message
& message
) {
385 ListenerList::Iterator
it(listener_list_
);
386 while (Listener
* listener
= it
.GetNext()) {
387 if (listener
->OnMessageReceived(message
))
393 void EmbeddedWorkerInstance::OnReportException(
394 const base::string16
& error_message
,
397 const GURL
& source_url
) {
401 OnReportException(error_message
, line_number
, column_number
, source_url
));
404 void EmbeddedWorkerInstance::OnReportConsoleMessage(
405 int source_identifier
,
407 const base::string16
& message
,
409 const GURL
& source_url
) {
413 OnReportConsoleMessage(
414 source_identifier
, message_level
, message
, line_number
, source_url
));
417 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
419 return devtools_proxy_
->agent_route_id();
420 return MSG_ROUTING_NONE
;
423 MessagePortMessageFilter
* EmbeddedWorkerInstance::message_port_message_filter()
425 return registry_
->MessagePortMessageFilterForProcess(process_id_
);
428 void EmbeddedWorkerInstance::AddListener(Listener
* listener
) {
429 listener_list_
.AddObserver(listener
);
432 void EmbeddedWorkerInstance::RemoveListener(Listener
* listener
) {
433 listener_list_
.RemoveObserver(listener
);
436 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
437 network_accessed_for_script_
= true;
440 } // namespace content