Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / service_worker / embedded_worker_instance.cc
blob9e7b36af6334f4b4ee2515ec9f15987b0f07079e
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"
22 #include "url/gurl.h"
24 namespace content {
26 namespace {
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(
48 int process_id,
49 const ServiceWorkerContextCore* service_worker_context,
50 const base::WeakPtr<ServiceWorkerContextCore>& service_worker_context_weak,
51 int64 service_worker_version_id,
52 const GURL& url,
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();
61 wait_for_debugger =
62 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
63 process_id,
64 worker_devtools_agent_route_id,
65 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
66 service_worker_context,
67 service_worker_context_weak,
68 service_worker_version_id,
69 url));
71 BrowserThread::PostTask(
72 BrowserThread::IO,
73 FROM_HERE,
74 base::Bind(callback, worker_devtools_agent_route_id, wait_for_debugger));
77 void SetupMojoOnUIThread(int process_id,
78 int thread_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.
83 if (!rph)
84 return;
85 EmbeddedWorkerSetupPtr setup;
86 rph->GetServiceRegistry()->ConnectToRemoteService(mojo::GetProxy(&setup));
87 setup->ExchangeServiceProviders(thread_id, services.Pass(),
88 exposed_services.Pass());
91 } // namespace
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 {
96 public:
97 DevToolsProxy(int process_id, int agent_route_id)
98 : process_id_(process_id),
99 agent_route_id_(agent_route_id) {}
101 ~DevToolsProxy() {
102 BrowserThread::PostTask(
103 BrowserThread::UI,
104 FROM_HERE,
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,
119 FROM_HERE,
120 base::Bind(NotifyWorkerStopIgnoredOnUI,
121 process_id_, agent_route_id_));
124 int agent_route_id() const { return agent_route_id_; }
126 private:
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,
141 const GURL& scope,
142 const GURL& script_url,
143 const StatusCallback& callback) {
144 if (!context_) {
145 callback.Run(SERVICE_WORKER_ERROR_ABORT);
146 // |this| may be destroyed by the callback.
147 return;
149 DCHECK(status_ == STOPPED);
150 start_timing_ = base::TimeTicks::Now();
151 status_ = STARTING;
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",
160 params.get(),
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(
171 embedded_worker_id_,
172 scope,
173 script_url,
174 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated,
175 weak_factory_.GetWeakPtr(),
176 context_,
177 base::Passed(&params),
178 callback));
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) {
190 OnDetached();
191 return status;
194 status_ = STOPPING;
195 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopping());
196 return status;
199 void EmbeddedWorkerInstance::StopIfIdle() {
200 if (devtools_attached_) {
201 if (devtools_proxy_)
202 devtools_proxy_->NotifyWorkerStopIgnored();
203 return;
205 Stop();
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)
226 : context_(context),
227 registry_(context->embedded_worker_registry()),
228 embedded_worker_id_(embedded_worker_id),
229 status_(STOPPED),
230 starting_phase_(NOT_STARTING),
231 process_id_(-1),
232 thread_id_(kInvalidEmbeddedWorkerThreadId),
233 devtools_attached_(false),
234 network_accessed_for_script_(false),
235 weak_factory_(this) {
238 // static
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,
245 int process_id,
246 bool is_new_process) {
247 if (!context) {
248 callback.Run(SERVICE_WORKER_ERROR_ABORT);
249 return;
251 if (!instance) {
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);
258 return;
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,
267 int process_id,
268 bool is_new_process,
269 ServiceWorkerStatusCode status) {
270 DCHECK_EQ(process_id_, -1);
271 TRACE_EVENT_ASYNC_END1("ServiceWorker",
272 "EmbeddedWorkerInstance::ProcessAllocate",
273 params.get(),
274 "Status", status);
275 if (status != SERVICE_WORKER_OK) {
276 OnStartFailed(callback, status);
277 return;
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,
290 script_url,
291 base::Bind(&EmbeddedWorkerInstance::SendStartWorker,
292 weak_factory_.GetWeakPtr(), base::Passed(&params),
293 callback, is_new_process)));
296 void EmbeddedWorkerInstance::SendStartWorker(
297 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
298 const StatusCallback& callback,
299 bool is_new_process,
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);
307 return;
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();
321 } else {
322 DCHECK(!start_timing_.is_null());
323 if (is_new_process) {
324 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.NewProcessAllocation",
325 base::TimeTicks::Now() - start_timing_);
326 } else {
327 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ExistingProcessAllocation",
328 base::TimeTicks::Now() - start_timing_);
330 UMA_HISTOGRAM_BOOLEAN("EmbeddedWorkerInstance.ProcessCreated",
331 is_new_process);
332 // Reset |start_timing_| to measure the time excluding the process
333 // allocation time.
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);
342 return;
344 DCHECK(start_callback_.is_null());
345 start_callback_ = callback;
348 void EmbeddedWorkerInstance::OnReadyForInspection() {
349 if (devtools_proxy_)
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_);
363 } else {
364 UMA_HISTOGRAM_TIMES(
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 =
379 GetProxy(&services);
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.";
395 return;
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)
411 return;
412 DCHECK(status_ == STARTING);
413 status_ = RUNNING;
414 FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted());
417 void EmbeddedWorkerInstance::OnStopped() {
418 Status old_status = status_;
419 ReleaseProcess();
420 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
423 void EmbeddedWorkerInstance::OnDetached() {
424 Status old_status = status_;
425 ReleaseProcess();
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))
433 return true;
435 return false;
438 void EmbeddedWorkerInstance::OnReportException(
439 const base::string16& error_message,
440 int line_number,
441 int column_number,
442 const GURL& source_url) {
443 FOR_EACH_OBSERVER(
444 Listener,
445 listener_list_,
446 OnReportException(error_message, line_number, column_number, source_url));
449 void EmbeddedWorkerInstance::OnReportConsoleMessage(
450 int source_identifier,
451 int message_level,
452 const base::string16& message,
453 int line_number,
454 const GURL& source_url) {
455 FOR_EACH_OBSERVER(
456 Listener,
457 listener_list_,
458 OnReportConsoleMessage(
459 source_identifier, message_level, message, line_number, source_url));
462 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
463 if (devtools_proxy_)
464 return devtools_proxy_->agent_route_id();
465 return MSG_ROUTING_NONE;
468 MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter()
469 const {
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_);
490 status_ = STOPPED;
491 process_id_ = -1;
492 thread_id_ = -1;
493 service_registry_.reset();
494 start_callback_.Reset();
497 void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback& callback,
498 ServiceWorkerStatusCode status) {
499 Status old_status = status_;
500 ReleaseProcess();
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));
508 // static
509 std::string EmbeddedWorkerInstance::StatusToString(Status status) {
510 switch (status) {
511 case STOPPED:
512 return "STOPPED";
513 case STARTING:
514 return "STARTING";
515 case RUNNING:
516 return "RUNNING";
517 case STOPPING:
518 return "STOPPING";
520 NOTREACHED() << status;
521 return std::string();
524 // static
525 std::string EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase) {
526 switch (phase) {
527 case NOT_STARTING:
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";
537 case SCRIPT_LOADED:
538 return "Script loaded";
539 case SCRIPT_EVALUATED:
540 return "Script evaluated";
541 case THREAD_STARTED:
542 return "Thread started";
543 case STARTING_PHASE_MAX_VALUE:
544 NOTREACHED();
546 NOTREACHED() << phase;
547 return std::string();
550 } // namespace content