diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/task_manager.cc | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/task_manager.cc')
-rw-r--r-- | chrome/browser/task_manager.cc | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/chrome/browser/task_manager.cc b/chrome/browser/task_manager.cc new file mode 100644 index 0000000..26731c9 --- /dev/null +++ b/chrome/browser/task_manager.cc @@ -0,0 +1,955 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/task_manager.h" + +#include "base/process_util.h" +#include "base/stats_table.h" +#include "base/string_util.h" +#include "base/timer.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/render_process_host.h" +#include "chrome/browser/standard_layout.h" +#include "chrome/browser/task_manager_resource_providers.h" +#include "chrome/browser/tab_util.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" +#include "chrome/views/accelerator.h" +#include "chrome/views/background.h" +#include "chrome/views/link.h" +#include "chrome/views/native_button.h" +#include "chrome/views/window.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" + +#include "generated_resources.h" + +// The task manager window default size. +static const int kDefaultWidth = 460; +static const int kDefaultHeight = 270; + +// The delay between updates of the information (in ms). +static const int kUpdateTimeMs = 1000; + +// An id for the most important column, made sufficiently large so as not to +// collide with anything else. +static const int64 kNuthMagicNumber = 1737350766; +static const int kBitMask = 0x7FFFFFFF; +static const int kGoatsTeleportedColumn = + (94024 * kNuthMagicNumber) & kBitMask; + +//////////////////////////////////////////////////////////////////////////////// +// TaskManagerUpdateTask class. +// +// Used to periodically updates the task manager contents. +// +//////////////////////////////////////////////////////////////////////////////// + +class TaskManagerUpdateTask : public Task { + public: + explicit TaskManagerUpdateTask(TaskManagerTableModel* model) : model_(model) { + } + void Run() { + if (model_) model_->Refresh(); + } + + private: + TaskManagerTableModel* model_; + DISALLOW_EVIL_CONSTRUCTORS(TaskManagerUpdateTask); +}; + +//////////////////////////////////////////////////////////////////////////////// +// TaskManagerTableModel class +//////////////////////////////////////////////////////////////////////////////// + +// static +int TaskManagerTableModel::goats_teleported_ = 0; + +TaskManagerTableModel::TaskManagerTableModel(TaskManager* task_manager) + : observer_(NULL), + timer_(NULL), + ui_loop_(MessageLoop::current()), + is_updating_(false) { + + TaskManagerBrowserProcessResourceProvider* browser_provider = + new TaskManagerBrowserProcessResourceProvider(task_manager); + browser_provider->AddRef(); + providers_.push_back(browser_provider); + TaskManagerWebContentsResourceProvider* wc_provider = + new TaskManagerWebContentsResourceProvider(task_manager); + wc_provider->AddRef(); + providers_.push_back(wc_provider); + TaskManagerPluginProcessResourceProvider* plugin_provider = + new TaskManagerPluginProcessResourceProvider(task_manager); + plugin_provider->AddRef(); + providers_.push_back(plugin_provider); + update_task_.reset(new TaskManagerUpdateTask(this)); +} + +TaskManagerTableModel::~TaskManagerTableModel() { + DCHECK(timer_ == NULL); + for (ResourceProviderList::iterator iter = providers_.begin(); + iter != providers_.end(); ++iter) { + (*iter)->Release(); + } +} + +int TaskManagerTableModel::RowCount() { + return static_cast<int>(resources_.size()); +} + +std::wstring TaskManagerTableModel::GetText(int row, int col_id) { + // Let's find out if we are the first item in our group. + TaskManager::Resource* resource = resources_[row]; + ResourceList* group = group_map_[resource->GetProcess()]; + DCHECK(group && !group->empty()); + bool first_in_group = ((*group)[0] == resource); + process_util::ProcessMetrics* process_metrics = NULL; + if (first_in_group) { + MetricsMap::iterator iter = metrics_map_.find(resource->GetProcess()); + DCHECK(iter != metrics_map_.end()); + process_metrics = iter->second; + } + + switch (col_id) { + case IDS_TASK_MANAGER_PAGE_COLUMN: // Process + return resource->GetTitle(); + + // Only the first item from a group shows the process info. + case IDS_TASK_MANAGER_NET_COLUMN: { // Net + int64 net_usage = GetNetworkUsageForResource(resources_[row]); + if (net_usage == 0 && !resource->SupportNetworkUsage()) { + return l10n_util::GetString(IDS_TASK_MANAGER_NA_CELL_TEXT); + } else { + if (net_usage == 0) + return std::wstring(L"0"); + return FormatSpeed(net_usage, GetByteDisplayUnits(net_usage), true); + } + } + + case IDS_TASK_MANAGER_CPU_COLUMN: // CPU + if (first_in_group) + return IntToWString(process_metrics->GetCPUUsage()); + return std::wstring(); + + case IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN: // Memory + // We report committed (working set + paged) private usage. This is NOT + // going to match what Windows Task Manager shows (which is working set). + if (first_in_group) { + size_t private_kbytes = process_metrics->GetPrivateBytes() / 1024; + return l10n_util::GetStringF(IDS_TASK_MANAGER_MEM_CELL_TEXT, + FormatNumber(private_kbytes)); + } + return std::wstring(); + + case IDS_TASK_MANAGER_SHARED_MEM_COLUMN: // Memory + if (first_in_group) { + process_util::WorkingSetKBytes ws_usage; + process_metrics->GetWorkingSetKBytes(&ws_usage); + size_t shared_kbytes = ws_usage.shared; + return l10n_util::GetStringF(IDS_TASK_MANAGER_MEM_CELL_TEXT, + FormatNumber(shared_kbytes)); + } + return std::wstring(); + + case IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN: // Memory + // Memory = working_set.private + working_set.shareable. + // We exclude the shared memory. + if (first_in_group) { + size_t total_kbytes = process_metrics->GetWorkingSetSize() / 1024; + process_util::WorkingSetKBytes ws_usage; + process_metrics->GetWorkingSetKBytes(&ws_usage); + total_kbytes -= ws_usage.shared; + return l10n_util::GetStringF(IDS_TASK_MANAGER_MEM_CELL_TEXT, + FormatNumber(total_kbytes)); + } + return std::wstring(); + + case IDS_TASK_MANAGER_PROCESS_ID_COLUMN: + if (first_in_group) + return IntToWString(process_util::GetProcId(resource->GetProcess())); + return std::wstring(); + case kGoatsTeleportedColumn: // Goats Teleported. + goats_teleported_ += rand(); + return FormatNumber(goats_teleported_); + + default: + StatsTable* table = StatsTable::current(); + if (table != NULL) { + const wchar_t* counter = table->GetRowName(col_id); + if (counter != NULL && counter[0] != '\0') { + int val = table->GetCounterValue(counter, + process_util::GetProcId(resource->GetProcess())); + return IntToWString(val); + } else { + NOTREACHED() << "Invalid column."; + } + } + return std::wstring(L"0"); + } +} + +SkBitmap TaskManagerTableModel::GetIcon(int row) { + DCHECK(row < RowCount()); + return resources_[row]->GetIcon(); +} + +void TaskManagerTableModel::GetGroupRangeForItem(int item, + ChromeViews::GroupRange* range) { + DCHECK((item >= 0) && (item < RowCount())) << + " invalid item "<< item << " (items count=" << RowCount() << ")"; + + TaskManager::Resource* resource = resources_[item]; + GroupMap::iterator group_iter = group_map_.find(resource->GetProcess()); + DCHECK(group_iter != group_map_.end()); + ResourceList* group = group_iter->second; + DCHECK(group); + if (group->size() == 1) { + range->start = item; + range->length = 1; + } else { + ResourceList::iterator iter = + std::find(resources_.begin(), resources_.end(), (*group)[0]); + DCHECK(iter != resources_.end()); + range->start = static_cast<int>(iter - resources_.begin()); + range->length = static_cast<int>(group->size()); + } +} + +HANDLE TaskManagerTableModel::GetProcessAt(int index) { + DCHECK(index < RowCount()); + return resources_[index]->GetProcess(); +} + +void TaskManagerTableModel::StartUpdating() { + DCHECK(!is_updating_); + is_updating_ = true; + DCHECK(timer_ == NULL); + TimerManager* tm = MessageLoop::current()->timer_manager(); + timer_ = tm->StartTimer(kUpdateTimeMs, update_task_.get(), true); + + // Register jobs notifications so we can compute network usage (it must be + // done from the IO thread). + Thread* thread = g_browser_process->io_thread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &TaskManagerTableModel::RegisterForJobDoneNotifications)); + + // Notify resource providers that we are updating. + for (ResourceProviderList::iterator iter = providers_.begin(); + iter != providers_.end(); ++iter) { + (*iter)->StartUpdating(); + } +} + +void TaskManagerTableModel::StopUpdating() { + DCHECK(is_updating_); + is_updating_ = false; + MessageLoop::current()->timer_manager()->StopTimer(timer_); + delete timer_; + timer_ = NULL; + + // Notify resource providers that we are done updating. + for (ResourceProviderList::const_iterator iter = providers_.begin(); + iter != providers_.end(); ++iter) { + (*iter)->StopUpdating(); + } + + // Unregister jobs notification (must be done from the IO thread). + Thread* thread = g_browser_process->io_thread(); + if (thread) + thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &TaskManagerTableModel::UnregisterForJobDoneNotifications)); +} + +void TaskManagerTableModel::AddResourceProvider( + TaskManager::ResourceProvider* provider) { + DCHECK(provider); + providers_.push_back(provider); +} + +void TaskManagerTableModel::RemoveResourceProvider( + TaskManager::ResourceProvider* provider) { + DCHECK(provider); + ResourceProviderList::iterator iter = std::find(providers_.begin(), + providers_.end(), + provider); + DCHECK(iter != providers_.end()); + providers_.erase(iter); +} + +void TaskManagerTableModel::RegisterForJobDoneNotifications() { + g_url_request_job_tracker.AddObserver(this); +} + +void TaskManagerTableModel::UnregisterForJobDoneNotifications() { + g_url_request_job_tracker.RemoveObserver(this); +} + +void TaskManagerTableModel::AddResource(TaskManager::Resource* resource) { + HANDLE process = resource->GetProcess(); + + ResourceList* group_entries = NULL; + GroupMap::const_iterator group_iter = group_map_.find(process); + int new_entry_index = 0; + if (group_iter == group_map_.end()) { + group_entries = new ResourceList(); + group_map_[process] = group_entries; + group_entries->push_back(resource); + + // Not part of a group, just put at the end of the list. + resources_.push_back(resource); + new_entry_index = static_cast<int>(resources_.size() - 1); + } else { + group_entries = group_iter->second; + group_entries->push_back(resource); + + // Insert the new entry right after the last entry of its group. + ResourceList::iterator iter = + std::find(resources_.begin(), + resources_.end(), + (*group_entries)[group_entries->size() - 2]); + DCHECK(iter != resources_.end()); + new_entry_index = static_cast<int>(iter - resources_.begin()); + resources_.insert(++iter, resource); + } + process_util::ProcessMetrics* pm = + process_util::ProcessMetrics::CreateProcessMetrics(process); + metrics_map_[process] = pm; + + // Notify the table that the contents have changed for it to redraw. + DCHECK(observer_); + observer_->OnItemsAdded(new_entry_index, 1); +} + +void TaskManagerTableModel::RemoveResource(TaskManager::Resource* resource) { + HANDLE process = resource->GetProcess(); + + // Find the associated group. + GroupMap::iterator group_iter = group_map_.find(process); + DCHECK(group_iter != group_map_.end()); + ResourceList* group_entries = group_iter->second; + + // Remove the entry from the group map. + ResourceList::iterator iter = std::find(group_entries->begin(), + group_entries->end(), + resource); + DCHECK(iter != group_entries->end()); + group_entries->erase(iter); + + // If there are no more entries for that process, do the clean-up. + if (group_entries->empty()) { + delete group_entries; + group_map_.erase(process); + + // Nobody is using this process, we don't need the process metrics anymore. + MetricsMap::iterator pm_iter = metrics_map_.find(process); + DCHECK(pm_iter != metrics_map_.end()); + if (pm_iter != metrics_map_.end()) { + delete pm_iter->second; + metrics_map_.erase(process); + } + } + + // Remove the entry from the model list. + iter = std::find(resources_.begin(), resources_.end(), resource); + DCHECK(iter != resources_.end()); + int index = static_cast<int>(iter - resources_.begin()); + resources_.erase(iter); + + // Remove the entry from the network maps. + ResourceValueMap::iterator net_iter = + current_byte_count_map_.find(resource); + if (net_iter != current_byte_count_map_.end()) + current_byte_count_map_.erase(net_iter); + net_iter = displayed_network_usage_map_.find(resource); + if (net_iter != displayed_network_usage_map_.end()) + displayed_network_usage_map_.erase(net_iter); + + // Notify the table that the contents have changed. + observer_->OnItemsRemoved(index, 1); +} + +void TaskManagerTableModel::Clear() { + int size = RowCount(); + if (size > 0) { + resources_.clear(); + + // Clear the groups. + for (GroupMap::iterator iter = group_map_.begin(); + iter != group_map_.end(); ++iter) { + delete iter->second; + } + group_map_.clear(); + + // Clear the process metrics. + for (MetricsMap::iterator iter = metrics_map_.begin(); + iter != metrics_map_.end(); ++iter) { + delete iter->second; + } + metrics_map_.clear(); + + // Clear the network maps. + current_byte_count_map_.clear(); + displayed_network_usage_map_.clear(); + + observer_->OnItemsRemoved(0, size); + } +} + +// Called by the timer when we need to refresh the row contents. +void TaskManagerTableModel::Refresh() { + // Compute the new network usage values. + displayed_network_usage_map_.clear(); + for (ResourceValueMap::iterator iter = current_byte_count_map_.begin(); + iter != current_byte_count_map_.end(); + ++iter) { + if (kUpdateTimeMs > 1000) { + int divider = (kUpdateTimeMs / 1000); + displayed_network_usage_map_[iter->first] = iter->second / divider; + } else { + displayed_network_usage_map_[iter->first] = iter->second * + (1000 / kUpdateTimeMs); + } + + // Then we reset the current byte count. + iter->second = 0; + } + if (resources_.size() > 0) + observer_->OnItemsChanged(0, RowCount()); +} + +void TaskManagerTableModel::SetObserver( + ChromeViews::TableModelObserver* observer) { + observer_ = observer; +} + +int64 TaskManagerTableModel::GetNetworkUsageForResource( + TaskManager::Resource* resource) { + ResourceValueMap::iterator iter = displayed_network_usage_map_.find(resource); + if (iter == displayed_network_usage_map_.end()) + return 0; + return iter->second; +} + +void TaskManagerTableModel::BytesRead(BytesReadParam param) { + if (!is_updating_) { + // A notification sneaked in while we were stopping the updating, just + // ignore it. + return; + } + + if (param.byte_count == 0) { + // Nothing to do if no bytes were actually read. + return; + } + + // TODO(jcampan): this should be improved once we have a better way of + // linking a network notification back to the object that initiated it. + TaskManager::Resource* resource; + for (ResourceProviderList::iterator iter = providers_.begin(); + iter != providers_.end(); iter++) { + resource = (*iter)->GetResource(param.origin_pid, + param.render_process_host_id, + param.routing_id); + if (resource) + break; + } + if (resource == NULL) { + // We may not have that resource anymore (example: close a tab while a + // a network resource is being retrieved), in which case we just ignore the + // notification. + return; + } + + // We do support network usage, mark the resource as such so it can report 0 + // instead of N/A. + if (!resource->SupportNetworkUsage()) + resource->SetSupportNetworkUsage(); + + ResourceValueMap::const_iterator iter_res = + current_byte_count_map_.find(resource); + if (iter_res == current_byte_count_map_.end()) + current_byte_count_map_[resource] = param.byte_count; + else + current_byte_count_map_[resource] = iter_res->second + param.byte_count; +} + + +// In order to retrieve the network usage, we register for URLRequestJob +// notifications. Every time we get notified some bytes were read we bump a +// counter of read bytes for the associated resource. When the timer ticks, +// we'll compute the actual network usage (see the Refresh method). +void TaskManagerTableModel::OnJobAdded(URLRequestJob* job) { +} + +void TaskManagerTableModel::OnJobRemoved(URLRequestJob* job) { +} + +void TaskManagerTableModel::OnJobDone(URLRequestJob* job, + const URLRequestStatus& status) { +} + +void TaskManagerTableModel::OnJobRedirect(URLRequestJob* job, + const GURL& location, + int status_code) { +} + +void TaskManagerTableModel::OnBytesRead(URLRequestJob* job, int byte_count) { + int render_process_host_id, routing_id; + if (tab_util::GetTabContentsID(job->request(), + &render_process_host_id, &routing_id)) { + // This happens in the IO thread, post it to the UI thread. + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( + this, &TaskManagerTableModel::BytesRead, + BytesReadParam(job->request()->origin_pid(), + render_process_host_id, routing_id, + byte_count))); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// TaskManagerContents class +// +// The view containing the different widgets. +// +//////////////////////////////////////////////////////////////////////////////// + +class TaskManagerContents : public ChromeViews::View, + public ChromeViews::NativeButton::Listener, + public ChromeViews::TableViewObserver, + public ChromeViews::LinkController, + public ChromeViews::ContextMenuController, + public Menu::Delegate { + public: + TaskManagerContents(TaskManager* task_manager, + TaskManagerTableModel* table_model); + virtual ~TaskManagerContents(); + + void Init(TaskManagerTableModel* table_model); + virtual void Layout(); + virtual void GetPreferredSize(CSize* out); + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void ViewHierarchyChanged(bool is_add, ChromeViews::View* parent, + ChromeViews::View* child); + void GetSelection(std::vector<int>* selection); + + // NativeButton::Listener implementation. + virtual void ButtonPressed(ChromeViews::NativeButton* sender); + + // ChromeViews::TableViewObserver implementation. + virtual void OnSelectionChanged(); + + // ChromeViews::LinkController implementation. + virtual void LinkActivated(ChromeViews::Link* source, int event_flags); + + // Called by the column picker to pick up any new stat counters that + // may have appeared since last time. + void UpdateStatsCounters(); + + // Menu::Delegate + virtual void ShowContextMenu(ChromeViews::View* source, + int x, + int y, + bool is_mouse_gesture); + virtual bool IsItemChecked(int id) const; + virtual void ExecuteCommand(int id); + + private: + scoped_ptr<ChromeViews::NativeButton> kill_button_; + scoped_ptr<ChromeViews::Link> about_memory_link_; + ChromeViews::GroupTableView* tab_table_; + + TaskManager* task_manager_; + + // all possible columns, not necessarily visible + std::vector<ChromeViews::TableColumn> columns_; + + DISALLOW_EVIL_CONSTRUCTORS(TaskManagerContents); +}; + +TaskManagerContents::TaskManagerContents(TaskManager* task_manager, + TaskManagerTableModel* table_model) + : task_manager_(task_manager) { + Init(table_model); +} + +TaskManagerContents::~TaskManagerContents() { +} + +void TaskManagerContents::Init(TaskManagerTableModel* table_model) { + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_PAGE_COLUMN, + ChromeViews::TableColumn::LEFT, -1, 1)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_SHARED_MEM_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_CPU_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_NET_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + columns_.push_back( + ChromeViews::TableColumn( + IDS_TASK_MANAGER_PROCESS_ID_COLUMN, + ChromeViews::TableColumn::RIGHT, -1, 0)); + + tab_table_ = new ChromeViews::GroupTableView(table_model, + columns_, + ChromeViews::ICON_AND_TEXT, + false, true, true); + + // Hide some columns by default + tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_PROCESS_ID_COLUMN, false); + tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_SHARED_MEM_COLUMN, false); + tab_table_->SetColumnVisibility(IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN, false); + + UpdateStatsCounters(); + ChromeViews::TableColumn col(kGoatsTeleportedColumn, L"Goats Teleported", + ChromeViews::TableColumn::RIGHT, -1, 0); + columns_.push_back(col); + tab_table_->AddColumn(col); + tab_table_->SetObserver(this); + SetContextMenuController(this); + kill_button_.reset(new ChromeViews::NativeButton( + l10n_util::GetString(IDS_TASK_MANAGER_KILL))); + kill_button_->SetListener(this); + about_memory_link_.reset(new ChromeViews::Link( + l10n_util::GetString(IDS_TASK_MANAGER_ABOUT_MEMORY_LINK))); + about_memory_link_->SetController(this); + + AddChildView(tab_table_); + + // Makes sure our state is consistent. + OnSelectionChanged(); +} + +void TaskManagerContents::UpdateStatsCounters() { + StatsTable* stats = StatsTable::current(); + if (stats != NULL) { + int max = stats->GetMaxCounters(); + // skip the first row (it's header data) + for (int i = 1; i < max; i++) { + const wchar_t* row = stats->GetRowName(i); + if (row != NULL && row[0] != L'\0' && !tab_table_->HasColumn(i)) { + // TODO(erikkay): Use l10n to get display names for stats. Right + // now we're just displaying the internal counter name. Perhaps + // stat names not in the string table would be filtered out. + // TODO(erikkay): Width is hard-coded right now, so many column + // names are clipped. + ChromeViews::TableColumn col(i, row, ChromeViews::TableColumn::RIGHT, + 90, 0); + columns_.push_back(col); + tab_table_->AddColumn(col); + } + } + } +} + +void TaskManagerContents::DidChangeBounds(const CRect& previous, + const CRect& current) { + Layout(); +} + +void TaskManagerContents::ViewHierarchyChanged(bool is_add, + ChromeViews::View* parent, + ChromeViews::View* child) { + // Since we want the Kill button and the Memory Details link to show up in + // the same visual row as the close button, which is provided by the + // framework, we must add the buttons to the non-client view, which is the + // parent of this view. Similarly, when we're removed from the view + // hierarchy, we must take care to clean up those items as well. + if (child == this) { + if (is_add) { + parent->AddChildView(kill_button_.get()); + parent->AddChildView(about_memory_link_.get()); + } else { + parent->RemoveChildView(kill_button_.get()); + parent->RemoveChildView(about_memory_link_.get()); + // Note that these items aren't deleted here, since this object is owned + // by the TaskManager, whose lifetime surpasses the window, and the next + // time we are inserted into a window these items will need to be valid. + } + } +} + +void TaskManagerContents::Layout() { + // kPanelHorizMargin is too big. + const int kTableButtonSpacing = 12; + CRect bounds; + GetLocalBounds(&bounds, true); + int x = bounds.left; + int y = bounds.top; + + CSize size; + kill_button_->GetPreferredSize(&size); + int prefered_width = size.cx; + int prefered_height = size.cy; + + tab_table_->SetBounds( + x + kPanelHorizMargin, + y + kPanelVertMargin, + bounds.Width() - 2 * kPanelHorizMargin, + bounds.Height() - 2 * kPanelVertMargin - prefered_height); + + // y-coordinate of button top left. + CRect parent_bounds; + GetParent()->GetLocalBounds(&parent_bounds, false); + int y_buttons = parent_bounds.bottom - prefered_height - kButtonVEdgeMargin; + + kill_button_->SetBounds( + x + bounds.Width() - prefered_width - kPanelHorizMargin, + y_buttons, + prefered_width, + prefered_height); + + about_memory_link_->GetPreferredSize(&size); + int link_prefered_width = size.cx; + int link_prefered_height = size.cy; + // center between the two buttons horizontally, and line up with + // bottom of buttons vertically. + int link_y_offset = std::max(0, prefered_height - link_prefered_height) / 2; + about_memory_link_->SetBounds( + x + kPanelHorizMargin, + y_buttons + prefered_height - link_prefered_height - link_y_offset, + link_prefered_width, + link_prefered_height); +} + +void TaskManagerContents::GetPreferredSize(CSize* out) { + out->cx = kDefaultWidth; + out->cy = kDefaultHeight; +} + +void TaskManagerContents::GetSelection(std::vector<int>* selection) { + DCHECK(selection); + for (ChromeViews::TableSelectionIterator iter = tab_table_->SelectionBegin(); + iter != tab_table_->SelectionEnd(); ++iter) { + // The TableView returns the selection starting from the end. + selection->insert(selection->begin(), *iter); + } +} + +// NativeButton::Listener implementation. +void TaskManagerContents::ButtonPressed(ChromeViews::NativeButton* sender) { + if (sender == kill_button_) + task_manager_->KillSelectedProcesses(); +} + +// ChromeViews::TableViewObserver implementation. +void TaskManagerContents::OnSelectionChanged() { + kill_button_->SetEnabled(!task_manager_->BrowserProcessIsSelected() && + tab_table_->SelectedRowCount() > 0); +} + +// ChromeViews::LinkController implementation +void TaskManagerContents::LinkActivated(ChromeViews::Link* source, + int event_flags) { + DCHECK(source == about_memory_link_); + Browser* browser = BrowserList::GetLastActive(); + DCHECK(browser); + browser->OpenURL(GURL("about:memory"), NEW_FOREGROUND_TAB, + PageTransition::LINK); +} + +void TaskManagerContents::ShowContextMenu(ChromeViews::View* source, + int x, + int y, + bool is_mouse_gesture) { + UpdateStatsCounters(); + Menu menu(this, Menu::TOPLEFT, source->GetViewContainer()->GetHWND()); + for (std::vector<ChromeViews::TableColumn>::iterator i = + columns_.begin(); i != columns_.end(); ++i) { + menu.AppendMenuItem(i->id, i->title, Menu::CHECKBOX); + } + menu.RunMenuAt(x, y); +} + +bool TaskManagerContents::IsItemChecked(int id) const { + return tab_table_->IsColumnVisible(id); +} + +void TaskManagerContents::ExecuteCommand(int id) { + tab_table_->SetColumnVisibility(id, !tab_table_->IsColumnVisible(id)); +} + +//////////////////////////////////////////////////////////////////////////////// +// TaskManager class +//////////////////////////////////////////////////////////////////////////////// + +// static +void TaskManager::RegisterPrefs(PrefService* prefs) { + prefs->RegisterDictionaryPref(prefs::kTaskManagerWindowPlacement); +} + +TaskManager::TaskManager() : window_(NULL) { + table_model_ = new TaskManagerTableModel(this); + contents_.reset(new TaskManagerContents(this, table_model_)); +} + +TaskManager::~TaskManager() { +} + +// static +void TaskManager::Open() { + TaskManager* task_manager = GetInstance(); + if (task_manager->window_) { + task_manager->window_->MoveToFront(true); + } else { + task_manager->window_ = + ChromeViews::Window::CreateChromeWindow( + NULL, gfx::Rect(), task_manager->contents_.get(), task_manager); + task_manager->table_model_->StartUpdating(); + task_manager->window_->Show(); + } +} + +void TaskManager::Close() { + window_ = NULL; + table_model_->StopUpdating(); + table_model_->Clear(); +} + +bool TaskManager::BrowserProcessIsSelected() { + if (!contents_.get()) + return false; + std::vector<int> selection; + contents_->GetSelection(&selection); + for (std::vector<int>::const_iterator iter = selection.begin(); + iter != selection.end(); ++iter) { + // If some of the selection is out of bounds, ignore. This may happen when + // killing a process that manages several pages. + if (*iter >= table_model_->RowCount()) + continue; + if (table_model_->GetProcessAt(*iter) == GetCurrentProcess()) + return true; + } + return false; +} + +void TaskManager::KillSelectedProcesses() { + std::vector<int> selection; + contents_->GetSelection(&selection); + for (std::vector<int>::const_iterator iter = selection.begin(); + iter != selection.end(); ++iter) { + HANDLE process = table_model_->GetProcessAt(*iter); + DCHECK(process); + TerminateProcess(process, 0); + } +} + +void TaskManager::AddResourceProvider(ResourceProvider* provider) { + table_model_->AddResourceProvider(provider); +} + +void TaskManager::RemoveResourceProvider(ResourceProvider* provider) { + table_model_->RemoveResourceProvider(provider); +} + +void TaskManager::AddResource(Resource* resource) { + table_model_->AddResource(resource); +} + +void TaskManager::RemoveResource(Resource* resource) { + table_model_->RemoveResource(resource); +} + +// DialogDelegate implementation: +bool TaskManager::CanResize() const { + return true; +} + +bool TaskManager::CanMaximize() const { + return true; +} + +bool TaskManager::ShouldShowWindowIcon() const { + return false; +} + +bool TaskManager::IsAlwaysOnTop() const { + return true; +} + +bool TaskManager::HasAlwaysOnTopMenu() const { + return true; +}; + +std::wstring TaskManager::GetWindowTitle() const { + return l10n_util::GetString(IDS_TASK_MANAGER_TITLE); +} + +void TaskManager::SaveWindowPosition(const CRect& bounds, + bool maximized, + bool always_on_top) { + window_->SaveWindowPositionToPrefService(g_browser_process->local_state(), + prefs::kTaskManagerWindowPlacement, + bounds, maximized, always_on_top); +} + +bool TaskManager::RestoreWindowPosition(CRect* bounds, + bool* maximized, + bool* always_on_top) { + return window_->RestoreWindowPositionFromPrefService( + g_browser_process->local_state(), + prefs::kTaskManagerWindowPlacement, + bounds, maximized, always_on_top); +} + +int TaskManager::GetDialogButtons() const { + return DIALOGBUTTON_NONE; +} + +void TaskManager::WindowClosing() { + // Remove the view from its parent to trigger the contents' + // ViewHierarchyChanged notification to unhook the extra buttons from the + // non-client view. + contents_->GetParent()->RemoveChildView(contents_.get()); + Close(); +} + +// static +TaskManager* TaskManager::GetInstance() { + return Singleton<TaskManager>::get(); +} |