Permission messages: Add a bunch of missing combinations/suppressions.
[chromium-blink-merge.git] / content / browser / service_worker / embedded_worker_instance.cc
blob68e473dcebc15d57c5ee825054bcb4a78ba2ea23
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(int thread_id) {
370 starting_phase_ = SCRIPT_LOADED;
371 if (!start_timing_.is_null()) {
372 if (network_accessed_for_script_) {
373 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptLoadWithNetworkAccess",
374 base::TimeTicks::Now() - start_timing_);
375 } else {
376 UMA_HISTOGRAM_TIMES(
377 "EmbeddedWorkerInstance.ScriptLoadWithoutNetworkAccess",
378 base::TimeTicks::Now() - start_timing_);
380 // Reset |start_timing_| to measure the time excluding the process
381 // allocation time and the script loading time.
382 start_timing_ = base::TimeTicks::Now();
384 thread_id_ = thread_id;
385 FOR_EACH_OBSERVER(Listener, listener_list_, OnScriptLoaded());
387 mojo::ServiceProviderPtr exposed_services;
388 service_registry_->Bind(GetProxy(&exposed_services));
389 mojo::ServiceProviderPtr services;
390 mojo::InterfaceRequest<mojo::ServiceProvider> services_request =
391 GetProxy(&services);
392 BrowserThread::PostTask(
393 BrowserThread::UI, FROM_HERE,
394 base::Bind(SetupMojoOnUIThread, process_id_, thread_id_,
395 base::Passed(&services_request),
396 base::Passed(&exposed_services)));
397 service_registry_->BindRemoteServiceProvider(services.Pass());
400 void EmbeddedWorkerInstance::OnScriptLoadFailed() {
403 void EmbeddedWorkerInstance::OnScriptEvaluated(bool success) {
404 starting_phase_ = SCRIPT_EVALUATED;
405 if (start_callback_.is_null()) {
406 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
407 return;
409 if (success && !start_timing_.is_null()) {
410 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
411 base::TimeTicks::Now() - start_timing_);
413 start_callback_.Run(success ? SERVICE_WORKER_OK
414 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED);
415 start_callback_.Reset();
418 void EmbeddedWorkerInstance::OnStarted() {
419 // Stop is requested before OnStarted is sent back from the worker.
420 if (status_ == STOPPING)
421 return;
422 DCHECK(status_ == STARTING);
423 status_ = RUNNING;
424 FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted());
427 void EmbeddedWorkerInstance::OnStopped() {
428 Status old_status = status_;
429 ReleaseProcess();
430 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
433 void EmbeddedWorkerInstance::OnDetached() {
434 Status old_status = status_;
435 ReleaseProcess();
436 FOR_EACH_OBSERVER(Listener, listener_list_, OnDetached(old_status));
439 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message) {
440 ListenerList::Iterator it(&listener_list_);
441 while (Listener* listener = it.GetNext()) {
442 if (listener->OnMessageReceived(message))
443 return true;
445 return false;
448 void EmbeddedWorkerInstance::OnReportException(
449 const base::string16& error_message,
450 int line_number,
451 int column_number,
452 const GURL& source_url) {
453 FOR_EACH_OBSERVER(
454 Listener,
455 listener_list_,
456 OnReportException(error_message, line_number, column_number, source_url));
459 void EmbeddedWorkerInstance::OnReportConsoleMessage(
460 int source_identifier,
461 int message_level,
462 const base::string16& message,
463 int line_number,
464 const GURL& source_url) {
465 FOR_EACH_OBSERVER(
466 Listener,
467 listener_list_,
468 OnReportConsoleMessage(
469 source_identifier, message_level, message, line_number, source_url));
472 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
473 if (devtools_proxy_)
474 return devtools_proxy_->agent_route_id();
475 return MSG_ROUTING_NONE;
478 MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter()
479 const {
480 return registry_->MessagePortMessageFilterForProcess(process_id_);
483 void EmbeddedWorkerInstance::AddListener(Listener* listener) {
484 listener_list_.AddObserver(listener);
487 void EmbeddedWorkerInstance::RemoveListener(Listener* listener) {
488 listener_list_.RemoveObserver(listener);
491 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
492 starting_phase_ = SCRIPT_DOWNLOADING;
493 network_accessed_for_script_ = true;
496 void EmbeddedWorkerInstance::ReleaseProcess() {
497 devtools_proxy_.reset();
498 if (context_ && process_id_ != -1)
499 context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
500 status_ = STOPPED;
501 process_id_ = -1;
502 thread_id_ = -1;
503 service_registry_.reset();
504 start_callback_.Reset();
507 // static
508 std::string EmbeddedWorkerInstance::StatusToString(Status status) {
509 switch (status) {
510 case STOPPED:
511 return "STOPPED";
512 case STARTING:
513 return "STARTING";
514 case RUNNING:
515 return "RUNNING";
516 case STOPPING:
517 return "STOPPING";
519 NOTREACHED() << status;
520 return std::string();
523 // static
524 std::string EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase) {
525 switch (phase) {
526 case NOT_STARTING:
527 return "Not in STARTING status";
528 case ALLOCATING_PROCESS:
529 return "Allocating process";
530 case REGISTERING_TO_DEVTOOLS:
531 return "Registering to DevTools";
532 case SENT_START_WORKER:
533 return "Sent StartWorker message to renderer";
534 case SCRIPT_DOWNLOADING:
535 return "Script downloading";
536 case SCRIPT_LOADED:
537 return "Script loaded";
538 case SCRIPT_EVALUATED:
539 return "Script evaluated";
540 case STARTING_PHASE_MAX_VALUE:
541 NOTREACHED();
543 NOTREACHED() << phase;
544 return std::string();
547 } // namespace content