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