ServiceWorker: Stop exposing ServiceWorkerContextCore
[chromium-blink-merge.git] / content / browser / service_worker / embedded_worker_instance.cc
blobfd09125c13f31610a1fd44a8f092df7430d7e12e
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 <utility>
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"
23 #include "url/gurl.h"
25 namespace content {
27 namespace {
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(
57 int process_id,
58 const ServiceWorkerContextCore* service_worker_context,
59 const base::WeakPtr<ServiceWorkerContextCore>& service_worker_context_weak,
60 int64 service_worker_version_id,
61 const GURL& url,
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();
70 wait_for_debugger =
71 ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
72 process_id,
73 worker_devtools_agent_route_id,
74 ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
75 service_worker_context,
76 service_worker_context_weak,
77 service_worker_version_id,
78 url));
80 BrowserThread::PostTask(
81 BrowserThread::IO,
82 FROM_HERE,
83 base::Bind(callback, worker_devtools_agent_route_id, wait_for_debugger));
86 } // namespace
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 {
91 public:
92 DevToolsProxy(int process_id, int agent_route_id)
93 : process_id_(process_id),
94 agent_route_id_(agent_route_id) {}
96 ~DevToolsProxy() {
97 BrowserThread::PostTask(
98 BrowserThread::UI,
99 FROM_HERE,
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,
114 FROM_HERE,
115 base::Bind(NotifyWorkerStopIgnoredOnUI,
116 process_id_, agent_route_id_));
119 int agent_route_id() const { return agent_route_id_; }
121 private:
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)
129 Stop();
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,
137 const GURL& scope,
138 const GURL& script_url,
139 bool pause_after_download,
140 const StatusCallback& callback) {
141 if (!context_) {
142 callback.Run(SERVICE_WORKER_ERROR_ABORT);
143 return;
145 DCHECK(status_ == STOPPED);
146 start_timing_ = base::TimeTicks::Now();
147 status_ = STARTING;
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",
155 params.get(),
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(
167 embedded_worker_id_,
168 scope,
169 script_url,
170 base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated,
171 weak_factory_.GetWeakPtr(),
172 context_,
173 base::Passed(&params),
174 callback));
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) {
182 status_ = STOPPING;
183 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopping());
185 return status;
188 void EmbeddedWorkerInstance::StopIfIdle() {
189 if (devtools_attached_) {
190 if (devtools_proxy_)
191 devtools_proxy_->NotifyWorkerStopIgnored();
192 return;
194 Stop();
197 void EmbeddedWorkerInstance::ResumeAfterDownload() {
198 DCHECK_EQ(STARTING, status_);
199 registry_->Send(
200 process_id_,
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)
217 : context_(context),
218 registry_(context->embedded_worker_registry()),
219 embedded_worker_id_(embedded_worker_id),
220 status_(STOPPED),
221 starting_phase_(NOT_STARTING),
222 process_id_(-1),
223 thread_id_(kInvalidEmbeddedWorkerThreadId),
224 devtools_attached_(false),
225 network_accessed_for_script_(false),
226 weak_factory_(this) {
229 // static
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,
236 int process_id) {
237 if (!context) {
238 callback.Run(SERVICE_WORKER_ERROR_ABORT);
239 return;
241 if (!instance) {
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);
248 return;
250 instance->ProcessAllocated(params.Pass(), callback, process_id, status);
253 void EmbeddedWorkerInstance::ProcessAllocated(
254 scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
255 const StatusCallback& callback,
256 int process_id,
257 ServiceWorkerStatusCode status) {
258 DCHECK_EQ(process_id_, -1);
259 TRACE_EVENT_ASYNC_END1("ServiceWorker",
260 "EmbeddedWorkerInstance::ProcessAllocate",
261 params.get(),
262 "Status", status);
263 if (status != SERVICE_WORKER_OK) {
264 Status old_status = status_;
265 status_ = STOPPED;
266 callback.Run(status);
267 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
268 return;
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(
278 BrowserThread::UI,
279 FROM_HERE,
280 base::Bind(RegisterToWorkerDevToolsManagerOnUI,
281 process_id_,
282 context_.get(),
283 context_,
284 service_worker_version_id,
285 script_url,
286 base::Bind(&EmbeddedWorkerInstance::SendStartWorker,
287 weak_factory_.GetWeakPtr(),
288 base::Passed(&params),
289 callback)));
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();
308 } else {
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
313 // allocation time.
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);
322 return;
324 DCHECK(start_callback_.is_null());
325 start_callback_ = callback;
328 void EmbeddedWorkerInstance::OnReadyForInspection() {
329 if (devtools_proxy_)
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_);
339 } else {
340 UMA_HISTOGRAM_TIMES(
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 (start_callback_.is_null()) {
358 DVLOG(1) << "Received unexpected OnScriptEvaluated message.";
359 return;
361 if (success && !start_timing_.is_null()) {
362 UMA_HISTOGRAM_TIMES("EmbeddedWorkerInstance.ScriptEvaluate",
363 base::TimeTicks::Now() - start_timing_);
365 start_callback_.Run(success ? SERVICE_WORKER_OK
366 : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED);
367 start_callback_.Reset();
370 void EmbeddedWorkerInstance::OnStarted() {
371 // Stop is requested before OnStarted is sent back from the worker.
372 if (status_ == STOPPING)
373 return;
374 DCHECK(status_ == STARTING);
375 status_ = RUNNING;
376 FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted());
379 void EmbeddedWorkerInstance::OnStopped() {
380 devtools_proxy_.reset();
381 if (context_)
382 context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
383 Status old_status = status_;
384 status_ = STOPPED;
385 process_id_ = -1;
386 thread_id_ = -1;
387 start_callback_.Reset();
388 FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped(old_status));
391 void EmbeddedWorkerInstance::OnPausedAfterDownload() {
392 // Stop can be requested before getting this far.
393 if (status_ == STOPPING)
394 return;
395 DCHECK(status_ == STARTING);
396 FOR_EACH_OBSERVER(Listener, listener_list_, OnPausedAfterDownload());
399 bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message) {
400 ListenerList::Iterator it(&listener_list_);
401 while (Listener* listener = it.GetNext()) {
402 if (listener->OnMessageReceived(message))
403 return true;
405 return false;
408 void EmbeddedWorkerInstance::OnReportException(
409 const base::string16& error_message,
410 int line_number,
411 int column_number,
412 const GURL& source_url) {
413 FOR_EACH_OBSERVER(
414 Listener,
415 listener_list_,
416 OnReportException(error_message, line_number, column_number, source_url));
419 void EmbeddedWorkerInstance::OnReportConsoleMessage(
420 int source_identifier,
421 int message_level,
422 const base::string16& message,
423 int line_number,
424 const GURL& source_url) {
425 FOR_EACH_OBSERVER(
426 Listener,
427 listener_list_,
428 OnReportConsoleMessage(
429 source_identifier, message_level, message, line_number, source_url));
432 int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const {
433 if (devtools_proxy_)
434 return devtools_proxy_->agent_route_id();
435 return MSG_ROUTING_NONE;
438 MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter()
439 const {
440 return registry_->MessagePortMessageFilterForProcess(process_id_);
443 void EmbeddedWorkerInstance::AddListener(Listener* listener) {
444 listener_list_.AddObserver(listener);
447 void EmbeddedWorkerInstance::RemoveListener(Listener* listener) {
448 listener_list_.RemoveObserver(listener);
451 void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad() {
452 starting_phase_ = SCRIPT_DOWNLOADING;
453 network_accessed_for_script_ = true;
456 // static
457 std::string EmbeddedWorkerInstance::StatusToString(Status status) {
458 switch (status) {
459 case STOPPED:
460 return "STOPPED";
461 case STARTING:
462 return "STARTING";
463 case RUNNING:
464 return "RUNNING";
465 case STOPPING:
466 return "STOPPING";
468 NOTREACHED() << status;
469 return std::string();
472 // static
473 std::string EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase) {
474 switch (phase) {
475 case NOT_STARTING:
476 return "Not in STARTING status";
477 case ALLOCATING_PROCESS:
478 return "Allocating process";
479 case REGISTERING_TO_DEVTOOLS:
480 return "Registering to DevTools";
481 case SENT_START_WORKER:
482 return "Sent StartWorker message to renderer";
483 case SCRIPT_DOWNLOADING:
484 return "Script downloading";
485 case SCRIPT_LOADED:
486 return "Script loaded";
487 case SCRIPT_EVALUATED:
488 return "Script evaluated";
489 case STARTING_PHASE_MAX_VALUE:
490 NOTREACHED();
492 NOTREACHED() << phase;
493 return std::string();
496 } // namespace content