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 "chrome/browser/task_manager/child_process_resource_provider.h"
6
7#include <vector>
8
9#include "base/i18n/rtl.h"
10#include "base/strings/string16.h"
11#include "chrome/browser/task_manager/resource_provider.h"
12#include "chrome/browser/task_manager/task_manager.h"
13#include "chrome/grit/generated_resources.h"
14#include "components/nacl/common/nacl_process_type.h"
15#include "content/public/browser/browser_child_process_host_iterator.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/child_process_data.h"
18#include "grit/theme_resources.h"
19#include "ui/base/l10n/l10n_util.h"
20#include "ui/base/resource/resource_bundle.h"
21#include "ui/gfx/image/image_skia.h"
22
23using content::BrowserChildProcessHostIterator;
24using content::BrowserThread;
25using content::WebContents;
26
27namespace task_manager {
28
29class ChildProcessResource : public Resource {
30 public:
31  ChildProcessResource(int process_type,
32                       const base::string16& name,
33                       base::ProcessHandle handle,
34                       int unique_process_id);
35  virtual ~ChildProcessResource();
36
37  // Resource methods:
38  virtual base::string16 GetTitle() const OVERRIDE;
39  virtual base::string16 GetProfileName() const OVERRIDE;
40  virtual gfx::ImageSkia GetIcon() const OVERRIDE;
41  virtual base::ProcessHandle GetProcess() const OVERRIDE;
42  virtual int GetUniqueChildProcessId() const OVERRIDE;
43  virtual Type GetType() const OVERRIDE;
44  virtual bool SupportNetworkUsage() const OVERRIDE;
45  virtual void SetSupportNetworkUsage() OVERRIDE;
46
47  // Returns the pid of the child process.
48  int process_id() const { return pid_; }
49
50 private:
51  // Returns a localized title for the child process.  For example, a plugin
52  // process would be "Plug-in: Flash" when name is "Flash".
53  base::string16 GetLocalizedTitle() const;
54
55  int process_type_;
56  base::string16 name_;
57  base::ProcessHandle handle_;
58  int pid_;
59  int unique_process_id_;
60  mutable base::string16 title_;
61  bool network_usage_support_;
62
63  // The icon painted for the child processs.
64  // TODO(jcampan): we should have plugin specific icons for well-known
65  // plugins.
66  static gfx::ImageSkia* default_icon_;
67
68  DISALLOW_COPY_AND_ASSIGN(ChildProcessResource);
69};
70
71gfx::ImageSkia* ChildProcessResource::default_icon_ = NULL;
72
73ChildProcessResource::ChildProcessResource(
74    int process_type,
75    const base::string16& name,
76    base::ProcessHandle handle,
77    int unique_process_id)
78    : process_type_(process_type),
79      name_(name),
80      handle_(handle),
81      unique_process_id_(unique_process_id),
82      network_usage_support_(false) {
83  // We cache the process id because it's not cheap to calculate, and it won't
84  // be available when we get the plugin disconnected notification.
85  pid_ = base::GetProcId(handle);
86  if (!default_icon_) {
87    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
88    default_icon_ = rb.GetImageSkiaNamed(IDR_PLUGINS_FAVICON);
89    // TODO(jabdelmalek): use different icon for web workers.
90  }
91}
92
93ChildProcessResource::~ChildProcessResource() {
94}
95
96// Resource methods:
97base::string16 ChildProcessResource::GetTitle() const {
98  if (title_.empty())
99    title_ = GetLocalizedTitle();
100
101  return title_;
102}
103
104base::string16 ChildProcessResource::GetProfileName() const {
105  return base::string16();
106}
107
108gfx::ImageSkia ChildProcessResource::GetIcon() const {
109  return *default_icon_;
110}
111
112base::ProcessHandle ChildProcessResource::GetProcess() const {
113  return handle_;
114}
115
116int ChildProcessResource::GetUniqueChildProcessId() const {
117  return unique_process_id_;
118}
119
120Resource::Type ChildProcessResource::GetType() const {
121  // Translate types to Resource::Type, since ChildProcessData's type
122  // is not available for all TaskManager resources.
123  switch (process_type_) {
124    case content::PROCESS_TYPE_PLUGIN:
125    case content::PROCESS_TYPE_PPAPI_PLUGIN:
126    case content::PROCESS_TYPE_PPAPI_BROKER:
127      return Resource::PLUGIN;
128    case content::PROCESS_TYPE_UTILITY:
129      return Resource::UTILITY;
130    case content::PROCESS_TYPE_ZYGOTE:
131      return Resource::ZYGOTE;
132    case content::PROCESS_TYPE_SANDBOX_HELPER:
133      return Resource::SANDBOX_HELPER;
134    case content::PROCESS_TYPE_GPU:
135      return Resource::GPU;
136    case PROCESS_TYPE_NACL_LOADER:
137    case PROCESS_TYPE_NACL_BROKER:
138      return Resource::NACL;
139    default:
140      return Resource::UNKNOWN;
141  }
142}
143
144bool ChildProcessResource::SupportNetworkUsage() const {
145  return network_usage_support_;
146}
147
148void ChildProcessResource::SetSupportNetworkUsage() {
149  network_usage_support_ = true;
150}
151
152base::string16 ChildProcessResource::GetLocalizedTitle() const {
153  base::string16 title = name_;
154  if (title.empty()) {
155    switch (process_type_) {
156      case content::PROCESS_TYPE_PLUGIN:
157      case content::PROCESS_TYPE_PPAPI_PLUGIN:
158      case content::PROCESS_TYPE_PPAPI_BROKER:
159        title = l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UNKNOWN_PLUGIN_NAME);
160        break;
161      default:
162        // Nothing to do for non-plugin processes.
163        break;
164    }
165  }
166
167  // Explicitly mark name as LTR if there is no strong RTL character,
168  // to avoid the wrong concatenation result similar to "!Yahoo Mail: the
169  // best web-based Email: NIGULP", in which "NIGULP" stands for the Hebrew
170  // or Arabic word for "plugin".
171  base::i18n::AdjustStringForLocaleDirection(&title);
172
173  switch (process_type_) {
174    case content::PROCESS_TYPE_UTILITY:
175      return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UTILITY_PREFIX);
176    case content::PROCESS_TYPE_GPU:
177      return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_GPU_PREFIX);
178    case content::PROCESS_TYPE_PLUGIN:
179    case content::PROCESS_TYPE_PPAPI_PLUGIN:
180      return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_PREFIX, title);
181    case content::PROCESS_TYPE_PPAPI_BROKER:
182      return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_BROKER_PREFIX,
183                                        title);
184    case PROCESS_TYPE_NACL_BROKER:
185      return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NACL_BROKER_PREFIX);
186    case PROCESS_TYPE_NACL_LOADER:
187      return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_NACL_PREFIX, title);
188    // These types don't need display names or get them from elsewhere.
189    case content::PROCESS_TYPE_BROWSER:
190    case content::PROCESS_TYPE_RENDERER:
191    case content::PROCESS_TYPE_ZYGOTE:
192    case content::PROCESS_TYPE_SANDBOX_HELPER:
193    case content::PROCESS_TYPE_MAX:
194      NOTREACHED();
195      break;
196    case content::PROCESS_TYPE_UNKNOWN:
197      NOTREACHED() << "Need localized name for child process type.";
198  }
199
200  return title;
201}
202
203////////////////////////////////////////////////////////////////////////////////
204// ChildProcessResourceProvider class
205////////////////////////////////////////////////////////////////////////////////
206
207ChildProcessResourceProvider::
208    ChildProcessResourceProvider(TaskManager* task_manager)
209    : task_manager_(task_manager),
210      updating_(false) {
211}
212
213ChildProcessResourceProvider::~ChildProcessResourceProvider() {
214}
215
216Resource* ChildProcessResourceProvider::GetResource(
217    int origin_pid,
218    int child_id,
219    int route_id) {
220  PidResourceMap::iterator iter = pid_to_resources_.find(origin_pid);
221  if (iter != pid_to_resources_.end())
222    return iter->second;
223  else
224    return NULL;
225}
226
227void ChildProcessResourceProvider::StartUpdating() {
228  DCHECK(!updating_);
229  updating_ = true;
230
231  // Get the existing child processes.
232  BrowserThread::PostTask(
233      BrowserThread::IO, FROM_HERE,
234      base::Bind(
235          &ChildProcessResourceProvider::RetrieveChildProcessData,
236          this));
237
238  BrowserChildProcessObserver::Add(this);
239}
240
241void ChildProcessResourceProvider::StopUpdating() {
242  DCHECK(updating_);
243  updating_ = false;
244
245  // Delete all the resources.
246  STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end());
247
248  resources_.clear();
249  pid_to_resources_.clear();
250
251  BrowserChildProcessObserver::Remove(this);
252}
253
254void ChildProcessResourceProvider::BrowserChildProcessHostConnected(
255    const content::ChildProcessData& data) {
256  DCHECK(updating_);
257
258  if (resources_.count(data.handle)) {
259    // The case may happen that we have added a child_process_info as part of
260    // the iteration performed during StartUpdating() call but the notification
261    // that it has connected was not fired yet. So when the notification
262    // happens, we already know about this plugin and just ignore it.
263    return;
264  }
265  AddToTaskManager(data);
266}
267
268void ChildProcessResourceProvider::
269    BrowserChildProcessHostDisconnected(
270        const content::ChildProcessData& data) {
271  DCHECK(updating_);
272
273  ChildProcessMap::iterator iter = resources_.find(data.handle);
274  if (iter == resources_.end()) {
275    // ChildProcessData disconnection notifications are asynchronous, so we
276    // might be notified for a plugin we don't know anything about (if it was
277    // closed before the task manager was shown and destroyed after that).
278    return;
279  }
280  // Remove the resource from the Task Manager.
281  ChildProcessResource* resource = iter->second;
282  task_manager_->RemoveResource(resource);
283  // Remove it from the provider.
284  resources_.erase(iter);
285  // Remove it from our pid map.
286  PidResourceMap::iterator pid_iter =
287      pid_to_resources_.find(resource->process_id());
288  DCHECK(pid_iter != pid_to_resources_.end());
289  if (pid_iter != pid_to_resources_.end())
290    pid_to_resources_.erase(pid_iter);
291
292  // Finally, delete the resource.
293  delete resource;
294}
295
296void ChildProcessResourceProvider::AddToTaskManager(
297    const content::ChildProcessData& child_process_data) {
298  ChildProcessResource* resource =
299      new ChildProcessResource(
300          child_process_data.process_type,
301          child_process_data.name,
302          child_process_data.handle,
303          child_process_data.id);
304  resources_[child_process_data.handle] = resource;
305  pid_to_resources_[resource->process_id()] = resource;
306  task_manager_->AddResource(resource);
307}
308
309// The ChildProcessData::Iterator has to be used from the IO thread.
310void ChildProcessResourceProvider::RetrieveChildProcessData() {
311  std::vector<content::ChildProcessData> child_processes;
312  for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
313    // Only add processes which are already started, since we need their handle.
314    if (iter.GetData().handle == base::kNullProcessHandle)
315      continue;
316    child_processes.push_back(iter.GetData());
317  }
318  // Now notify the UI thread that we have retrieved information about child
319  // processes.
320  BrowserThread::PostTask(
321      BrowserThread::UI, FROM_HERE,
322      base::Bind(
323          &ChildProcessResourceProvider::ChildProcessDataRetreived,
324          this, child_processes));
325}
326
327// This is called on the UI thread.
328void ChildProcessResourceProvider::ChildProcessDataRetreived(
329    const std::vector<content::ChildProcessData>& child_processes) {
330  for (size_t i = 0; i < child_processes.size(); ++i)
331    AddToTaskManager(child_processes[i]);
332
333  task_manager_->model()->NotifyDataReady();
334}
335
336}  // namespace task_manager
337