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.
4
5#include "content/browser/service_worker/embedded_worker_instance.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "base/bind_helpers.h"
11#include "base/debug/trace_event.h"
12#include "content/browser/devtools/embedded_worker_devtools_manager.h"
13#include "content/browser/service_worker/embedded_worker_registry.h"
14#include "content/browser/service_worker/service_worker_context_core.h"
15#include "content/common/service_worker/embedded_worker_messages.h"
16#include "content/common/service_worker/service_worker_types.h"
17#include "content/public/browser/browser_thread.h"
18#include "content/public/browser/render_process_host.h"
19#include "ipc/ipc_message.h"
20#include "url/gurl.h"
21
22namespace content {
23
24namespace {
25
26// Functor to sort by the .second element of a struct.
27struct SecondGreater {
28  template <typename Value>
29  bool operator()(const Value& lhs, const Value& rhs) {
30    return lhs.second > rhs.second;
31  }
32};
33
34void NotifyWorkerReadyForInspection(int worker_process_id,
35                                    int worker_route_id) {
36  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
37    BrowserThread::PostTask(BrowserThread::UI,
38                            FROM_HERE,
39                            base::Bind(NotifyWorkerReadyForInspection,
40                                       worker_process_id,
41                                       worker_route_id));
42    return;
43  }
44  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
45      worker_process_id, worker_route_id);
46}
47
48void NotifyWorkerContextStarted(int worker_process_id, int worker_route_id) {
49  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
50    BrowserThread::PostTask(
51        BrowserThread::UI,
52        FROM_HERE,
53        base::Bind(
54            NotifyWorkerContextStarted, worker_process_id, worker_route_id));
55    return;
56  }
57  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerContextStarted(
58      worker_process_id, worker_route_id);
59}
60
61void NotifyWorkerDestroyed(int worker_process_id, int worker_route_id) {
62  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
63    BrowserThread::PostTask(
64        BrowserThread::UI,
65        FROM_HERE,
66        base::Bind(NotifyWorkerDestroyed, worker_process_id, worker_route_id));
67    return;
68  }
69  EmbeddedWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
70      worker_process_id, worker_route_id);
71}
72
73void RegisterToWorkerDevToolsManager(
74    int process_id,
75    const ServiceWorkerContextCore* service_worker_context,
76    base::WeakPtr<ServiceWorkerContextCore> service_worker_context_weak,
77    int64 service_worker_version_id,
78    const GURL& url,
79    const base::Callback<void(int worker_devtools_agent_route_id,
80                              bool wait_for_debugger)>& callback) {
81  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
82    BrowserThread::PostTask(BrowserThread::UI,
83                            FROM_HERE,
84                            base::Bind(RegisterToWorkerDevToolsManager,
85                                       process_id,
86                                       service_worker_context,
87                                       service_worker_context_weak,
88                                       service_worker_version_id,
89                                       url,
90                                       callback));
91    return;
92  }
93  int worker_devtools_agent_route_id = MSG_ROUTING_NONE;
94  bool wait_for_debugger = false;
95  if (RenderProcessHost* rph = RenderProcessHost::FromID(process_id)) {
96    // |rph| may be NULL in unit tests.
97    worker_devtools_agent_route_id = rph->GetNextRoutingID();
98    wait_for_debugger =
99        EmbeddedWorkerDevToolsManager::GetInstance()->ServiceWorkerCreated(
100            process_id,
101            worker_devtools_agent_route_id,
102            EmbeddedWorkerDevToolsManager::ServiceWorkerIdentifier(
103                service_worker_context,
104                service_worker_context_weak,
105                service_worker_version_id,
106                url));
107  }
108  BrowserThread::PostTask(
109      BrowserThread::IO,
110      FROM_HERE,
111      base::Bind(callback, worker_devtools_agent_route_id, wait_for_debugger));
112}
113
114}  // namespace
115
116EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
117  if (status_ == STARTING || status_ == RUNNING)
118    Stop();
119  if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE)
120    NotifyWorkerDestroyed(process_id_, worker_devtools_agent_route_id_);
121  if (context_ && process_id_ != -1)
122    context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
123  registry_->RemoveWorker(process_id_, embedded_worker_id_);
124}
125
126void EmbeddedWorkerInstance::Start(int64 service_worker_version_id,
127                                   const GURL& scope,
128                                   const GURL& script_url,
129                                   bool pause_after_download,
130                                   const StatusCallback& callback) {
131  if (!context_) {
132    callback.Run(SERVICE_WORKER_ERROR_ABORT);
133    return;
134  }
135  DCHECK(status_ == STOPPED);
136  status_ = STARTING;
137  scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params(
138      new EmbeddedWorkerMsg_StartWorker_Params());
139  TRACE_EVENT_ASYNC_BEGIN2("ServiceWorker",
140                           "EmbeddedWorkerInstance::ProcessAllocate",
141                           params.get(),
142                           "Scope", scope.spec(),
143                           "Script URL", script_url.spec());
144  params->embedded_worker_id = embedded_worker_id_;
145  params->service_worker_version_id = service_worker_version_id;
146  params->scope = scope;
147  params->script_url = script_url;
148  params->worker_devtools_agent_route_id = MSG_ROUTING_NONE;
149  params->pause_after_download = pause_after_download;
150  params->wait_for_debugger = false;
151  context_->process_manager()->AllocateWorkerProcess(
152      embedded_worker_id_,
153      scope,
154      script_url,
155      base::Bind(&EmbeddedWorkerInstance::RunProcessAllocated,
156                 weak_factory_.GetWeakPtr(),
157                 context_,
158                 base::Passed(&params),
159                 callback));
160}
161
162ServiceWorkerStatusCode EmbeddedWorkerInstance::Stop() {
163  DCHECK(status_ == STARTING || status_ == RUNNING);
164  ServiceWorkerStatusCode status =
165      registry_->StopWorker(process_id_, embedded_worker_id_);
166  if (status == SERVICE_WORKER_OK)
167    status_ = STOPPING;
168  return status;
169}
170
171void EmbeddedWorkerInstance::ResumeAfterDownload() {
172  DCHECK_EQ(STARTING, status_);
173  registry_->Send(
174      process_id_,
175      new EmbeddedWorkerMsg_ResumeAfterDownload(embedded_worker_id_));
176}
177
178ServiceWorkerStatusCode EmbeddedWorkerInstance::SendMessage(
179    const IPC::Message& message) {
180  DCHECK_NE(kInvalidEmbeddedWorkerThreadId, thread_id_);
181  if (status_ != RUNNING && status_ != STARTING)
182    return SERVICE_WORKER_ERROR_IPC_FAILED;
183  return registry_->Send(process_id_,
184                         new EmbeddedWorkerContextMsg_MessageToWorker(
185                             thread_id_, embedded_worker_id_, message));
186}
187
188EmbeddedWorkerInstance::EmbeddedWorkerInstance(
189    base::WeakPtr<ServiceWorkerContextCore> context,
190    int embedded_worker_id)
191    : context_(context),
192      registry_(context->embedded_worker_registry()),
193      embedded_worker_id_(embedded_worker_id),
194      status_(STOPPED),
195      process_id_(-1),
196      thread_id_(kInvalidEmbeddedWorkerThreadId),
197      worker_devtools_agent_route_id_(MSG_ROUTING_NONE),
198      weak_factory_(this) {
199}
200
201// static
202void EmbeddedWorkerInstance::RunProcessAllocated(
203    base::WeakPtr<EmbeddedWorkerInstance> instance,
204    base::WeakPtr<ServiceWorkerContextCore> context,
205    scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
206    const EmbeddedWorkerInstance::StatusCallback& callback,
207    ServiceWorkerStatusCode status,
208    int process_id) {
209  if (!context) {
210    callback.Run(SERVICE_WORKER_ERROR_ABORT);
211    return;
212  }
213  if (!instance) {
214    if (status == SERVICE_WORKER_OK) {
215      // We only have a process allocated if the status is OK.
216      context->process_manager()->ReleaseWorkerProcess(
217          params->embedded_worker_id);
218    }
219    callback.Run(SERVICE_WORKER_ERROR_ABORT);
220    return;
221  }
222  instance->ProcessAllocated(params.Pass(), callback, process_id, status);
223}
224
225void EmbeddedWorkerInstance::ProcessAllocated(
226    scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
227    const StatusCallback& callback,
228    int process_id,
229    ServiceWorkerStatusCode status) {
230  DCHECK_EQ(process_id_, -1);
231  TRACE_EVENT_ASYNC_END1("ServiceWorker",
232                         "EmbeddedWorkerInstance::ProcessAllocate",
233                         params.get(),
234                         "Status", status);
235  if (status != SERVICE_WORKER_OK) {
236    status_ = STOPPED;
237    callback.Run(status);
238    return;
239  }
240  const int64 service_worker_version_id = params->service_worker_version_id;
241  process_id_ = process_id;
242  GURL script_url(params->script_url);
243  RegisterToWorkerDevToolsManager(
244      process_id,
245      context_.get(),
246      context_,
247      service_worker_version_id,
248      script_url,
249      base::Bind(&EmbeddedWorkerInstance::SendStartWorker,
250                 weak_factory_.GetWeakPtr(),
251                 base::Passed(&params),
252                 callback));
253}
254
255void EmbeddedWorkerInstance::SendStartWorker(
256    scoped_ptr<EmbeddedWorkerMsg_StartWorker_Params> params,
257    const StatusCallback& callback,
258    int worker_devtools_agent_route_id,
259    bool wait_for_debugger) {
260  worker_devtools_agent_route_id_ = worker_devtools_agent_route_id;
261  params->worker_devtools_agent_route_id = worker_devtools_agent_route_id;
262  params->wait_for_debugger = wait_for_debugger;
263  registry_->SendStartWorker(params.Pass(), callback, process_id_);
264}
265
266void EmbeddedWorkerInstance::OnReadyForInspection() {
267  if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE)
268    NotifyWorkerReadyForInspection(process_id_,
269                                   worker_devtools_agent_route_id_);
270}
271
272void EmbeddedWorkerInstance::OnScriptLoaded(int thread_id) {
273  thread_id_ = thread_id;
274  if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE)
275    NotifyWorkerContextStarted(process_id_, worker_devtools_agent_route_id_);
276}
277
278void EmbeddedWorkerInstance::OnScriptLoadFailed() {
279}
280
281void EmbeddedWorkerInstance::OnStarted() {
282  // Stop is requested before OnStarted is sent back from the worker.
283  if (status_ == STOPPING)
284    return;
285  DCHECK(status_ == STARTING);
286  status_ = RUNNING;
287  FOR_EACH_OBSERVER(Listener, listener_list_, OnStarted());
288}
289
290void EmbeddedWorkerInstance::OnStopped() {
291  if (worker_devtools_agent_route_id_ != MSG_ROUTING_NONE)
292    NotifyWorkerDestroyed(process_id_, worker_devtools_agent_route_id_);
293  if (context_)
294    context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
295  status_ = STOPPED;
296  process_id_ = -1;
297  thread_id_ = -1;
298  worker_devtools_agent_route_id_ = MSG_ROUTING_NONE;
299  FOR_EACH_OBSERVER(Listener, listener_list_, OnStopped());
300}
301
302void EmbeddedWorkerInstance::OnPausedAfterDownload() {
303  // Stop can be requested before getting this far.
304  if (status_ == STOPPING)
305    return;
306  DCHECK(status_ == STARTING);
307  FOR_EACH_OBSERVER(Listener, listener_list_, OnPausedAfterDownload());
308}
309
310bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message) {
311  ListenerList::Iterator it(listener_list_);
312  while (Listener* listener = it.GetNext()) {
313    if (listener->OnMessageReceived(message))
314      return true;
315  }
316  return false;
317}
318
319void EmbeddedWorkerInstance::OnReportException(
320    const base::string16& error_message,
321    int line_number,
322    int column_number,
323    const GURL& source_url) {
324  FOR_EACH_OBSERVER(
325      Listener,
326      listener_list_,
327      OnReportException(error_message, line_number, column_number, source_url));
328}
329
330void EmbeddedWorkerInstance::OnReportConsoleMessage(
331    int source_identifier,
332    int message_level,
333    const base::string16& message,
334    int line_number,
335    const GURL& source_url) {
336  FOR_EACH_OBSERVER(
337      Listener,
338      listener_list_,
339      OnReportConsoleMessage(
340          source_identifier, message_level, message, line_number, source_url));
341}
342
343void EmbeddedWorkerInstance::AddListener(Listener* listener) {
344  listener_list_.AddObserver(listener);
345}
346
347void EmbeddedWorkerInstance::RemoveListener(Listener* listener) {
348  listener_list_.RemoveObserver(listener);
349}
350
351}  // namespace content
352