| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/task_manager/child_process_resource_provider.h" |
| |
| #include <vector> |
| |
| #include "base/i18n/rtl.h" |
| #include "base/strings/string16.h" |
| #include "chrome/browser/task_manager/resource_provider.h" |
| #include "chrome/browser/task_manager/task_manager.h" |
| #include "components/nacl/common/nacl_process_type.h" |
| #include "content/public/browser/browser_child_process_host_iterator.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/child_process_data.h" |
| #include "grit/generated_resources.h" |
| #include "grit/theme_resources.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| using content::BrowserChildProcessHostIterator; |
| using content::BrowserThread; |
| using content::WebContents; |
| |
| namespace task_manager { |
| |
| class ChildProcessResource : public Resource { |
| public: |
| ChildProcessResource(int process_type, |
| const string16& name, |
| base::ProcessHandle handle, |
| int unique_process_id); |
| virtual ~ChildProcessResource(); |
| |
| // Resource methods: |
| virtual string16 GetTitle() const OVERRIDE; |
| virtual string16 GetProfileName() const OVERRIDE; |
| virtual gfx::ImageSkia GetIcon() const OVERRIDE; |
| virtual base::ProcessHandle GetProcess() const OVERRIDE; |
| virtual int GetUniqueChildProcessId() const OVERRIDE; |
| virtual Type GetType() const OVERRIDE; |
| virtual bool SupportNetworkUsage() const OVERRIDE; |
| virtual void SetSupportNetworkUsage() OVERRIDE; |
| |
| // Returns the pid of the child process. |
| int process_id() const { return pid_; } |
| |
| private: |
| // Returns a localized title for the child process. For example, a plugin |
| // process would be "Plug-in: Flash" when name is "Flash". |
| string16 GetLocalizedTitle() const; |
| |
| int process_type_; |
| string16 name_; |
| base::ProcessHandle handle_; |
| int pid_; |
| int unique_process_id_; |
| mutable string16 title_; |
| bool network_usage_support_; |
| |
| // The icon painted for the child processs. |
| // TODO(jcampan): we should have plugin specific icons for well-known |
| // plugins. |
| static gfx::ImageSkia* default_icon_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ChildProcessResource); |
| }; |
| |
| gfx::ImageSkia* ChildProcessResource::default_icon_ = NULL; |
| |
| ChildProcessResource::ChildProcessResource( |
| int process_type, |
| const string16& name, |
| base::ProcessHandle handle, |
| int unique_process_id) |
| : process_type_(process_type), |
| name_(name), |
| handle_(handle), |
| unique_process_id_(unique_process_id), |
| network_usage_support_(false) { |
| // We cache the process id because it's not cheap to calculate, and it won't |
| // be available when we get the plugin disconnected notification. |
| pid_ = base::GetProcId(handle); |
| if (!default_icon_) { |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| default_icon_ = rb.GetImageSkiaNamed(IDR_PLUGINS_FAVICON); |
| // TODO(jabdelmalek): use different icon for web workers. |
| } |
| } |
| |
| ChildProcessResource::~ChildProcessResource() { |
| } |
| |
| // Resource methods: |
| string16 ChildProcessResource::GetTitle() const { |
| if (title_.empty()) |
| title_ = GetLocalizedTitle(); |
| |
| return title_; |
| } |
| |
| string16 ChildProcessResource::GetProfileName() const { |
| return string16(); |
| } |
| |
| gfx::ImageSkia ChildProcessResource::GetIcon() const { |
| return *default_icon_; |
| } |
| |
| base::ProcessHandle ChildProcessResource::GetProcess() const { |
| return handle_; |
| } |
| |
| int ChildProcessResource::GetUniqueChildProcessId() const { |
| return unique_process_id_; |
| } |
| |
| Resource::Type ChildProcessResource::GetType() const { |
| // Translate types to Resource::Type, since ChildProcessData's type |
| // is not available for all TaskManager resources. |
| switch (process_type_) { |
| case content::PROCESS_TYPE_PLUGIN: |
| case content::PROCESS_TYPE_PPAPI_PLUGIN: |
| case content::PROCESS_TYPE_PPAPI_BROKER: |
| return Resource::PLUGIN; |
| case content::PROCESS_TYPE_UTILITY: |
| return Resource::UTILITY; |
| case content::PROCESS_TYPE_ZYGOTE: |
| return Resource::ZYGOTE; |
| case content::PROCESS_TYPE_SANDBOX_HELPER: |
| return Resource::SANDBOX_HELPER; |
| case content::PROCESS_TYPE_GPU: |
| return Resource::GPU; |
| case PROCESS_TYPE_NACL_LOADER: |
| case PROCESS_TYPE_NACL_BROKER: |
| return Resource::NACL; |
| default: |
| return Resource::UNKNOWN; |
| } |
| } |
| |
| bool ChildProcessResource::SupportNetworkUsage() const { |
| return network_usage_support_; |
| } |
| |
| void ChildProcessResource::SetSupportNetworkUsage() { |
| network_usage_support_ = true; |
| } |
| |
| string16 ChildProcessResource::GetLocalizedTitle() const { |
| string16 title = name_; |
| if (title.empty()) { |
| switch (process_type_) { |
| case content::PROCESS_TYPE_PLUGIN: |
| case content::PROCESS_TYPE_PPAPI_PLUGIN: |
| case content::PROCESS_TYPE_PPAPI_BROKER: |
| title = l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UNKNOWN_PLUGIN_NAME); |
| break; |
| default: |
| // Nothing to do for non-plugin processes. |
| break; |
| } |
| } |
| |
| // Explicitly mark name as LTR if there is no strong RTL character, |
| // to avoid the wrong concatenation result similar to "!Yahoo Mail: the |
| // best web-based Email: NIGULP", in which "NIGULP" stands for the Hebrew |
| // or Arabic word for "plugin". |
| base::i18n::AdjustStringForLocaleDirection(&title); |
| |
| switch (process_type_) { |
| case content::PROCESS_TYPE_UTILITY: |
| return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UTILITY_PREFIX); |
| case content::PROCESS_TYPE_GPU: |
| return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_GPU_PREFIX); |
| case content::PROCESS_TYPE_PLUGIN: |
| case content::PROCESS_TYPE_PPAPI_PLUGIN: |
| return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_PREFIX, title); |
| case content::PROCESS_TYPE_PPAPI_BROKER: |
| return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_BROKER_PREFIX, |
| title); |
| case PROCESS_TYPE_NACL_BROKER: |
| return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NACL_BROKER_PREFIX); |
| case PROCESS_TYPE_NACL_LOADER: |
| return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_NACL_PREFIX, title); |
| // These types don't need display names or get them from elsewhere. |
| case content::PROCESS_TYPE_BROWSER: |
| case content::PROCESS_TYPE_RENDERER: |
| case content::PROCESS_TYPE_ZYGOTE: |
| case content::PROCESS_TYPE_SANDBOX_HELPER: |
| case content::PROCESS_TYPE_MAX: |
| NOTREACHED(); |
| break; |
| |
| case content::PROCESS_TYPE_WORKER: |
| NOTREACHED() << "Workers are not handled by this provider."; |
| break; |
| case content::PROCESS_TYPE_UNKNOWN: |
| NOTREACHED() << "Need localized name for child process type."; |
| } |
| |
| return title; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ChildProcessResourceProvider class |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| ChildProcessResourceProvider:: |
| ChildProcessResourceProvider(TaskManager* task_manager) |
| : task_manager_(task_manager), |
| updating_(false) { |
| } |
| |
| ChildProcessResourceProvider::~ChildProcessResourceProvider() { |
| } |
| |
| Resource* ChildProcessResourceProvider::GetResource( |
| int origin_pid, |
| int render_process_host_id, |
| int routing_id) { |
| PidResourceMap::iterator iter = pid_to_resources_.find(origin_pid); |
| if (iter != pid_to_resources_.end()) |
| return iter->second; |
| else |
| return NULL; |
| } |
| |
| void ChildProcessResourceProvider::StartUpdating() { |
| DCHECK(!updating_); |
| updating_ = true; |
| |
| // Get the existing child processes. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind( |
| &ChildProcessResourceProvider::RetrieveChildProcessData, |
| this)); |
| |
| BrowserChildProcessObserver::Add(this); |
| } |
| |
| void ChildProcessResourceProvider::StopUpdating() { |
| DCHECK(updating_); |
| updating_ = false; |
| |
| // Delete all the resources. |
| STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end()); |
| |
| resources_.clear(); |
| pid_to_resources_.clear(); |
| |
| BrowserChildProcessObserver::Remove(this); |
| } |
| |
| void ChildProcessResourceProvider::BrowserChildProcessHostConnected( |
| const content::ChildProcessData& data) { |
| DCHECK(updating_); |
| |
| // Workers are handled by WorkerResourceProvider. |
| if (data.process_type == content::PROCESS_TYPE_WORKER) |
| return; |
| if (resources_.count(data.handle)) { |
| // The case may happen that we have added a child_process_info as part of |
| // the iteration performed during StartUpdating() call but the notification |
| // that it has connected was not fired yet. So when the notification |
| // happens, we already know about this plugin and just ignore it. |
| return; |
| } |
| AddToTaskManager(data); |
| } |
| |
| void ChildProcessResourceProvider:: |
| BrowserChildProcessHostDisconnected( |
| const content::ChildProcessData& data) { |
| DCHECK(updating_); |
| |
| if (data.process_type == content::PROCESS_TYPE_WORKER) |
| return; |
| ChildProcessMap::iterator iter = resources_.find(data.handle); |
| if (iter == resources_.end()) { |
| // ChildProcessData disconnection notifications are asynchronous, so we |
| // might be notified for a plugin we don't know anything about (if it was |
| // closed before the task manager was shown and destroyed after that). |
| return; |
| } |
| // Remove the resource from the Task Manager. |
| ChildProcessResource* resource = iter->second; |
| task_manager_->RemoveResource(resource); |
| // Remove it from the provider. |
| resources_.erase(iter); |
| // Remove it from our pid map. |
| PidResourceMap::iterator pid_iter = |
| pid_to_resources_.find(resource->process_id()); |
| DCHECK(pid_iter != pid_to_resources_.end()); |
| if (pid_iter != pid_to_resources_.end()) |
| pid_to_resources_.erase(pid_iter); |
| |
| // Finally, delete the resource. |
| delete resource; |
| } |
| |
| void ChildProcessResourceProvider::AddToTaskManager( |
| const content::ChildProcessData& child_process_data) { |
| ChildProcessResource* resource = |
| new ChildProcessResource( |
| child_process_data.process_type, |
| child_process_data.name, |
| child_process_data.handle, |
| child_process_data.id); |
| resources_[child_process_data.handle] = resource; |
| pid_to_resources_[resource->process_id()] = resource; |
| task_manager_->AddResource(resource); |
| } |
| |
| // The ChildProcessData::Iterator has to be used from the IO thread. |
| void ChildProcessResourceProvider::RetrieveChildProcessData() { |
| std::vector<content::ChildProcessData> child_processes; |
| for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) { |
| // Only add processes which are already started, since we need their handle. |
| if (iter.GetData().handle == base::kNullProcessHandle) |
| continue; |
| if (iter.GetData().process_type == content::PROCESS_TYPE_WORKER) |
| continue; |
| child_processes.push_back(iter.GetData()); |
| } |
| // Now notify the UI thread that we have retrieved information about child |
| // processes. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &ChildProcessResourceProvider::ChildProcessDataRetreived, |
| this, child_processes)); |
| } |
| |
| // This is called on the UI thread. |
| void ChildProcessResourceProvider::ChildProcessDataRetreived( |
| const std::vector<content::ChildProcessData>& child_processes) { |
| for (size_t i = 0; i < child_processes.size(); ++i) |
| AddToTaskManager(child_processes[i]); |
| |
| task_manager_->model()->NotifyDataReady(); |
| } |
| |
| } // namespace task_manager |