Remove the 'gyp_config' concept from MB.
[chromium-blink-merge.git] / content / browser / service_worker / embedded_worker_instance.cc
blobef03475f8df22ff8bfdf296c1375449e8ec7a0c3
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 <algorithm>
8 #include <string>
9 #include <utility>
11 #include "base/bind_helpers.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/threading/non_thread_safe.h"
14 #include "base/trace_event/trace_event.h"
15 #include "content/browser/devtools/service_worker_devtools_manager.h"
16 #include "content/browser/service_worker/embedded_worker_registry.h"
17 #include "content/browser/service_worker/service_worker_context_core.h"
18 #include "content/common/content_switches_internal.h"
19 #include "content/common/mojo/service_registry_impl.h"
20 #include "content/common/service_worker/embedded_worker_messages.h"
21 #include "content/common/service_worker/embedded_worker_setup.mojom.h"
22 #include "content/common/service_worker/service_worker_types.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "ipc/ipc_message.h"
26 #include "url/gurl.h"
28 namespace content {
30 namespace {
32 // Functor to sort by the .second element of a struct.
33 struct SecondGreater {
34 template <typename Value>
35 bool operator()(const Value& lhs, const Value& rhs) {
36 return lhs.second > rhs.second;
40 void NotifyWorkerReadyForInspectionOnUI(int worker_process_id,
41 int worker_route_id) {
42 DCHECK_CURRENTLY_ON(BrowserThread::UI);
43 ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
44 worker_process_id, worker_route_id);
47 void NotifyWorkerDestroyedOnUI(int worker_process_id, int worker_route_id) {
48 DCHECK_CURRENTLY_ON(BrowserThread::UI);
49 ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
50 worker_process_id, worker_route_id);
53 void NotifyWorkerStopIgnoredOnUI(int worker_process_id, int worker_route_id) {
54 DCHECK_CURRENTLY_ON(BrowserThread::UI);
55 ServiceWorkerDevToolsManager::GetInstance()->WorkerStopIgnored(
56 worker_process_id, worker_route_id);
59 void RegisterToWorkerDevToolsManagerOnUI(
60 int process_id,
61 const ServiceWorkerContextCore* service_worker_context,
62 const base::WeakPtr<ServiceWorkerContextCore>& service_worker_context_weak,
63 int64 service_worker_version_id,
64 const GURL& url,
65 const base::Callback<void(int worker_devtools_agent_route_id,
66 bool wait_for_debugger)>& callback) {
67 DCHECK_CURRENTLY_ON(BrowserThread::UI);
68 int worker_devtools_agent_route_id = MSG_ROUTING_NONE;
69 bool wait_for_debugger = false;
70 if (RenderProcessHost* rph = RenderProcessHost::FromID(process_id)) {
71 // |rph| may be NULL in unit tests.
72 worker_devtools_agent_route_id = rph->GetNextRoutingID();
73 wait_for_debugger =
74 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
75 process_id,
76 worker_devtools_agent_route_id,
77 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
78 service_worker_context,
79 service_worker_context_weak,
80 service_worker_version_id,
81 url));
83 BrowserThread::PostTask(
84 BrowserThread::IO,
85 FROM_HERE,
86 base::Bind(callback, worker_devtools_agent_route_id, wait_for_debugger));
89 void SetupMojoOnUIThread(int process_id,
90 int thread_id,
91 mojo::InterfaceRequest<mojo::ServiceProvider> services,
92 mojo::ServiceProviderPtr exposed_services) {
93 RenderProcessHost* rph = RenderProcessHost::FromID(process_id);
94 // |rph| may be NULL in unit tests.
95 if (!rph)
96 return;
97 EmbeddedWorkerSetupPtr setup;
98 rph->GetServiceRegistry()->ConnectToRemoteService(mojo::GetProxy(&setup));
99 setup->ExchangeServiceProviders(thread_id, services.Pass(),
100 exposed_services.Pass());
103 } // namespace
105 // Lives on IO thread, proxies notifications to DevToolsManager that lives on
106 // UI thread. Owned by EmbeddedWorkerInstance.
107 class EmbeddedWorkerInstance::DevToolsProxy : public base::NonThreadSafe {
108 public:
109 DevToolsProxy(int process_id, int agent_route_id)
110 : process_id_(process_id),
111 agent_route_id_(agent_route_id) {}
113 ~DevToolsProxy() {
114 BrowserThread::PostTask(
115 BrowserThread::UI,
116 FROM_HERE,
117 base::Bind(NotifyWorkerDestroyedOnUI,
118 process_id_, agent_route_id_));
121 void NotifyWorkerReadyForInspection() {
122 DCHECK(CalledOnValidThread());
123 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
124 base::Bind(NotifyWorkerReadyForInspectionOnUI,
125 process_id_, agent_route_id_));
128 void NotifyWorkerStopIgnored() {
129 DCHECK(CalledOnValidThread());
130 BrowserThread::PostTask(BrowserThread::UI,
131 FROM_HERE,
132 base::Bind(NotifyWorkerStopIgnoredOnUI,
133 process_id_, agent_route_id_));
136 int agent_route_id() const { return agent_route_id_; }
138 private:
139 const int process_id_;
140 const int agent_route_id_;
141 DISALLOW_COPY_AND_ASSIGN(DevToolsProxy);
144 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
145 DCHECK(status_ == STOPPING || status_ == STOPPED);
146 devtools_proxy_.reset();
147 if (context_ && process_id_ != -1)
148 context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
149 registry_->RemoveWorker(process_id_, embedded_worker_id_);
152 void EmbeddedWorkerInstance::Start(int64 service_worker_version_id,
153 const GURL& scope,
154 const GURL& script_url,
155 const StatusCallback& callback) {
156 if (!context_) {
157 callback.Run(SERVICE_WORKER_ERROR_ABORT);
158 return;
160 DCHECK(status_ == STOPPED);
161 start_timing_ = base::TimeTicks::Now();
162 status_ = STARTING;
163 starting_phase_ = ALLOCATING_PROCESS;
164 network_accessed_for_script_ = false;
165 service_registry_.reset(new ServiceRegistryImpl());
166 FOR_EACH_OBSERVER(Listener, listener_list_, OnStarting());
167 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params(
168 new EmbeddedWorkerMsg_StartWorker_Params());
169 TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
170 "EmbeddedWorkerInstance::ProcessAllocate",
171 params.get(),
172 "Scope", scope.spec(),
173 "Script URL", script_url.spec());
174 params->embedded_worker_id = embedded_worker_id_;
175 params->service_worker_version_id = service_worker_version_id;
176 params->scope = scope;
177 params->script_url = script_url;
178 params->worker_devtools_agent_route_id = MSG_ROUTING_NONE;
179 params->wait_for_debugger = false;
180 params->v8_cache_options = GetV8CacheOptions();
181 context_->process_manager()->AllocateWorkerProcess(
182 embedded_worker_id_,
183 scope,
184 script_url,
185 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated,
186 weak_factory_.GetWeakPtr(),
187 context_,
188 base::Passed(&params),
189 callback));
192 ServiceWorkerStatusCode EmbeddedWorkerInstance::Stop() {
193 DCHECK(status_ == STARTING || status_ == RUNNING) << status_;
194 ServiceWorkerStatusCode status =
195 registry_->StopWorker(process_id_, embedded_worker_id_);
196 UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status,
197 SERVICE_WORKER_ERROR_MAX_VALUE);
198 // StopWorker could fail if we were starting up and don't have a process yet,
199 // or we can no longer communicate with the process. So just detach.
200 if (status != SERVICE_WORKER_OK) {
201 OnDetached();
202 return status;
205 status_ = STOPPING;
206 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopping());
207 return status;
210 void EmbeddedWorkerInstance::StopIfIdle() {
211 if (devtools_attached_) {
212 if (devtools_proxy_)
213 devtools_proxy_->NotifyWorkerStopIgnored();
214 return;
216 Stop();
219 ServiceWorkerStatusCode EmbeddedWorkerInstance::SendMessage(
220 const IPC::Message& message) {
221 DCHECK_NE(kInvalidEmbeddedWorkerThreadId, thread_id_);
222 if (status_ != RUNNING && status_ != STARTING)
223 return SERVICE_WORKER_ERROR_IPC_FAILED;
224 return registry_->Send(process_id_,
225 new EmbeddedWorkerContextMsg_MessageToWorker(
226 thread_id_, embedded_worker_id_, message));
229 ServiceRegistry* EmbeddedWorkerInstance::GetServiceRegistry() {
230 DCHECK(status_ == STARTING || status_ == RUNNING) << status_;
231 return service_registry_.get();
234 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
235 base::WeakPtr<ServiceWorkerContextCore> context,
236 int embedded_worker_id)
237 : context_(context),
238 registry_(context->embedded_worker_registry()),
239 embedded_worker_id_(embedded_worker_id),
240 status_(STOPPED),
241 starting_phase_(NOT_STARTING),
242 process_id_(-1),
243 thread_id_(kInvalidEmbeddedWorkerThreadId),
244 devtools_attached_(false),
245 network_accessed_for_script_(false),
246 weak_factory_(this) {
249 // static
250 void EmbeddedWorkerInstance::RunProcessAllocated(
251 base::WeakPtr<EmbeddedWorkerInstance> instance,
252 base::WeakPtr<ServiceWorkerContextCore> context,
253 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
254 const EmbeddedWorkerInstance::StatusCallback& callback,
255 ServiceWorkerStatusCode status,
256 int process_id,
257 bool is_new_process) {
258 if (!context) {
259 callback.Run(SERVICE_WORKER_ERROR_ABORT);
260 return;
262 if (!instance) {
263 if (status == SERVICE_WORKER_OK) {
264 // We only have a process allocated if the status is OK.
265 context->process_manager()->ReleaseWorkerProcess(
266 params->embedded_worker_id);
268 callback.Run(SERVICE_WORKER_ERROR_ABORT);
269 return;
271 instance->ProcessAllocated(params.Pass(), callback, process_id,
272 is_new_process, status);
275 void EmbeddedWorkerInstance::ProcessAllocated(
276 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
277 const StatusCallback& callback,
278 int process_id,
279 bool is_new_process,
280 ServiceWorkerStatusCode status) {
281 DCHECK_EQ(process_id_, -1);
282 TRACE_EVENT_ASYNC_END1("ServiceWorker",
283 "EmbeddedWorkerInstance::ProcessAllocate",
284 params.get(),
285 "Status", status);
286 if (status != SERVICE_WORKER_OK) {
287 Status old_status = status_;
288 status_ = STOPPED;
289 service_registry_.reset();
290 callback.Run(status);
291 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
292 return;
294 const int64 service_worker_version_id = params->service_worker_version_id;
295 process_id_ = process_id;
296 GURL script_url(params->script_url);
298 // Register this worker to DevToolsManager on UI thread, then continue to
299 // call SendStartWorker on IO thread.
300 starting_phase_ = REGISTERING_TO_DEVTOOLS;
301 BrowserThread::PostTask(
302 BrowserThread::UI, FROM_HERE,
303 base::Bind(RegisterToWorkerDevToolsManagerOnUI, process_id_,
304 context_.get(), context_, service_worker_version_id,
305 script_url,
306 base::Bind(&EmbeddedWorkerInstance::SendStartWorker,
307 weak_factory_.GetWeakPtr(), base::Passed(&params),
308 callback, is_new_process)));
311 void EmbeddedWorkerInstance::SendStartWorker(
312 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
313 const StatusCallback& callback,
314 bool is_new_process,
315 int worker_devtools_agent_route_id,
316 bool wait_for_debugger) {
317 // We may have been detached or stopped at some point during the start up
318 // process, making process_id_ and other state invalid. If that happened,
319 // abort instead of trying to send the IPC.
320 if (status_ != STARTING) {
321 callback.Run(SERVICE_WORKER_ERROR_ABORT);
322 ReleaseProcess();
323 return;
326 if (worker_devtools_agent_route_id != MSG_ROUTING_NONE) {
327 DCHECK(!devtools_proxy_);
328 devtools_proxy_.reset(new DevToolsProxy(process_id_,
329 worker_devtools_agent_route_id));
331 params->worker_devtools_agent_route_id = worker_devtools_agent_route_id;
332 params->wait_for_debugger = wait_for_debugger;
333 if (params->wait_for_debugger) {
334 // We don't measure the start time when wait_for_debugger flag is set. So we
335 // set the NULL time here.
336 start_timing_ = base::TimeTicks();
337 } else {
338 DCHECK(!start_timing_.is_null());
339 if (is_new_process) {
340 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.NewProcessAllocation",
341 base::TimeTicks::Now() - start_timing_);
342 } else {
343 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ExistingProcessAllocation",
344 base::TimeTicks::Now() - start_timing_);
346 UMA_HISTOGRAM_BOOLEAN("EmbeddedWorkerInstance.ProcessCreated",
347 is_new_process);
348 // Reset |start_timing_| to measure the time excluding the process
349 // allocation time.
350 start_timing_ = base::TimeTicks::Now();
353 starting_phase_ = SENT_START_WORKER;
354 ServiceWorkerStatusCode status =
355 registry_->SendStartWorker(params.Pass(), process_id_);
356 if (status != SERVICE_WORKER_OK) {
357 callback.Run(status);
358 return;
360 DCHECK(start_callback_.is_null());
361 start_callback_ = callback;
364 void EmbeddedWorkerInstance::OnReadyForInspection() {
365 if (devtools_proxy_)
366 devtools_proxy_->NotifyWorkerReadyForInspection();
369 void EmbeddedWorkerInstance::OnScriptLoaded() {
370 starting_phase_ = SCRIPT_LOADED;
373 void EmbeddedWorkerInstance::OnThreadStarted(int thread_id) {
374 starting_phase_ = THREAD_STARTED;
375 if (!start_timing_.is_null()) {
376 if (network_accessed_for_script_) {
377 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
378 base::TimeTicks::Now() - start_timing_);
379 } else {
380 UMA_HISTOGRAM_TIMES(
381 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
382 base::TimeTicks::Now() - start_timing_);
384 // Reset |start_timing_| to measure the time excluding the process
385 // allocation time and the script loading time.
386 start_timing_ = base::TimeTicks::Now();
388 thread_id_ = thread_id;
389 FOR_EACH_OBSERVER(Listener, listener_list_, OnThreadStarted());
391 mojo::ServiceProviderPtr exposed_services;
392 service_registry_->Bind(GetProxy(&exposed_services));
393 mojo::ServiceProviderPtr services;
394 mojo::InterfaceRequest<mojo::ServiceProvider> services_request =
395 GetProxy(&services);
396 BrowserThread::PostTask(
397 BrowserThread::UI, FROM_HERE,
398 base::Bind(SetupMojoOnUIThread, process_id_, thread_id_,
399 base::Passed(&services_request),
400 base::Passed(&exposed_services)));
401 service_registry_->BindRemoteServiceProvider(services.Pass());
404 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
407 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success) {
408 starting_phase_ = SCRIPT_EVALUATED;
409 if (start_callback_.is_null()) {
410 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
411 return;
413 if (success && !start_timing_.is_null()) {
414 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
415 base::TimeTicks::Now() - start_timing_);
417 start_callback_.Run(success ? SERVICE_WORKER_OK
418 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED);
419 start_callback_.Reset();
422 void EmbeddedWorkerInstance::OnStarted() {
423 // Stop is requested before OnStarted is sent back from the worker.
424 if (status_ == STOPPING)
425 return;
426 DCHECK(status_ == STARTING);
427 status_ = RUNNING;
428 FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted());
431 void EmbeddedWorkerInstance::OnStopped() {
432 Status old_status = status_;
433 ReleaseProcess();
434 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
437 void EmbeddedWorkerInstance::OnDetached() {
438 Status old_status = status_;
439 ReleaseProcess();
440 FOR_EACH_OBSERVER(Listener, listener_list_, OnDetached(old_status));
443 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message) {
444 ListenerList::Iterator it(&listener_list_);
445 while (Listener* listener = it.GetNext()) {
446 if (listener->OnMessageReceived(message))
447 return true;
449 return false;
452 void EmbeddedWorkerInstance::OnReportException(
453 const base::string16& error_message,
454 int line_number,
455 int column_number,
456 const GURL& source_url) {
457 FOR_EACH_OBSERVER(
458 Listener,
459 listener_list_,
460 OnReportException(error_message, line_number, column_number, source_url));
463 void EmbeddedWorkerInstance::OnReportConsoleMessage(
464 int source_identifier,
465 int message_level,
466 const base::string16& message,
467 int line_number,
468 const GURL& source_url) {
469 FOR_EACH_OBSERVER(
470 Listener,
471 listener_list_,
472 OnReportConsoleMessage(
473 source_identifier, message_level, message, line_number, source_url));
476 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
477 if (devtools_proxy_)
478 return devtools_proxy_->agent_route_id();
479 return MSG_ROUTING_NONE;
482 MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter()
483 const {
484 return registry_->MessagePortMessageFilterForProcess(process_id_);
487 void EmbeddedWorkerInstance::AddListener(Listener* listener) {
488 listener_list_.AddObserver(listener);
491 void EmbeddedWorkerInstance::RemoveListener(Listener* listener) {
492 listener_list_.RemoveObserver(listener);
495 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
496 starting_phase_ = SCRIPT_DOWNLOADING;
497 network_accessed_for_script_ = true;
500 void EmbeddedWorkerInstance::ReleaseProcess() {
501 devtools_proxy_.reset();
502 if (context_ && process_id_ != -1)
503 context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
504 status_ = STOPPED;
505 process_id_ = -1;
506 thread_id_ = -1;
507 service_registry_.reset();
508 start_callback_.Reset();
511 // static
512 std::string EmbeddedWorkerInstance::StatusToString(Status status) {
513 switch (status) {
514 case STOPPED:
515 return "STOPPED";
516 case STARTING:
517 return "STARTING";
518 case RUNNING:
519 return "RUNNING";
520 case STOPPING:
521 return "STOPPING";
523 NOTREACHED() << status;
524 return std::string();
527 // static
528 std::string EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase) {
529 switch (phase) {
530 case NOT_STARTING:
531 return "Not in STARTING status";
532 case ALLOCATING_PROCESS:
533 return "Allocating process";
534 case REGISTERING_TO_DEVTOOLS:
535 return "Registering to DevTools";
536 case SENT_START_WORKER:
537 return "Sent StartWorker message to renderer";
538 case SCRIPT_DOWNLOADING:
539 return "Script downloading";
540 case SCRIPT_LOADED:
541 return "Script loaded";
542 case SCRIPT_EVALUATED:
543 return "Script evaluated";
544 case THREAD_STARTED:
545 return "Thread started";
546 case STARTING_PHASE_MAX_VALUE:
547 NOTREACHED();
549 NOTREACHED() << phase;
550 return std::string();
553 } // namespace content