blob: b29f3c6b77dbf9a21d901d7518638e3c36cdd6f7 [file] [log] [blame]
// Copyright (c) 2012 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/extensions/api/processes/processes_api.h"
#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/processes/processes_api_constants.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_function_registry.h"
#include "chrome/browser/extensions/extension_function_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/task_manager/resource_provider.h"
#include "chrome/browser/task_manager/task_manager.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/result_codes.h"
#include "extensions/common/error_utils.h"
namespace extensions {
namespace keys = processes_api_constants;
namespace errors = processes_api_constants;
namespace {
#if defined(ENABLE_TASK_MANAGER)
base::DictionaryValue* CreateCacheData(
const WebKit::WebCache::ResourceTypeStat& stat) {
base::DictionaryValue* cache = new base::DictionaryValue();
cache->SetDouble(keys::kCacheSize, static_cast<double>(stat.size));
cache->SetDouble(keys::kCacheLiveSize, static_cast<double>(stat.liveSize));
return cache;
}
void SetProcessType(base::DictionaryValue* result,
TaskManagerModel* model,
int index) {
// Determine process type.
std::string type = keys::kProcessTypeOther;
task_manager::Resource::Type resource_type = model->GetResourceType(index);
switch (resource_type) {
case task_manager::Resource::BROWSER:
type = keys::kProcessTypeBrowser;
break;
case task_manager::Resource::RENDERER:
type = keys::kProcessTypeRenderer;
break;
case task_manager::Resource::EXTENSION:
type = keys::kProcessTypeExtension;
break;
case task_manager::Resource::NOTIFICATION:
type = keys::kProcessTypeNotification;
break;
case task_manager::Resource::PLUGIN:
type = keys::kProcessTypePlugin;
break;
case task_manager::Resource::WORKER:
type = keys::kProcessTypeWorker;
break;
case task_manager::Resource::NACL:
type = keys::kProcessTypeNacl;
break;
case task_manager::Resource::UTILITY:
type = keys::kProcessTypeUtility;
break;
case task_manager::Resource::GPU:
type = keys::kProcessTypeGPU;
break;
case task_manager::Resource::ZYGOTE:
case task_manager::Resource::SANDBOX_HELPER:
case task_manager::Resource::UNKNOWN:
type = keys::kProcessTypeOther;
break;
default:
NOTREACHED() << "Unknown resource type.";
}
result->SetString(keys::kTypeKey, type);
}
base::ListValue* GetTabsForProcess(int process_id) {
base::ListValue* tabs_list = new base::ListValue();
// The tabs list only makes sense for render processes, so if we don't find
// one, just return the empty list.
content::RenderProcessHost* rph =
content::RenderProcessHost::FromID(process_id);
if (rph == NULL)
return tabs_list;
int tab_id = -1;
// We need to loop through all the RVHs to ensure we collect the set of all
// tabs using this renderer process.
content::RenderWidgetHost::List widgets =
content::RenderWidgetHost::GetRenderWidgetHosts();
for (size_t i = 0; i < widgets.size(); ++i) {
if (widgets[i]->GetProcess()->GetID() != process_id)
continue;
if (!widgets[i]->IsRenderView())
continue;
content::RenderViewHost* host = content::RenderViewHost::From(widgets[i]);
content::WebContents* contents =
content::WebContents::FromRenderViewHost(host);
if (contents) {
tab_id = ExtensionTabUtil::GetTabId(contents);
if (tab_id != -1)
tabs_list->Append(new base::FundamentalValue(tab_id));
}
}
return tabs_list;
}
// This function creates a Process object to be returned to the extensions
// using these APIs. For memory details, which are not added by this function,
// the callers need to use AddMemoryDetails.
base::DictionaryValue* CreateProcessFromModel(int process_id,
TaskManagerModel* model,
int index,
bool include_optional) {
base::DictionaryValue* result = new base::DictionaryValue();
size_t mem;
result->SetInteger(keys::kIdKey, process_id);
result->SetInteger(keys::kOsProcessIdKey, model->GetProcessId(index));
SetProcessType(result, model, index);
result->SetString(keys::kProfileKey,
model->GetResourceProfileName(index));
result->Set(keys::kTabsListKey, GetTabsForProcess(process_id));
// If we don't need to include the optional properties, just return now.
if (!include_optional)
return result;
result->SetDouble(keys::kCpuKey, model->GetCPUUsage(index));
if (model->GetV8Memory(index, &mem))
result->SetDouble(keys::kJsMemoryAllocatedKey,
static_cast<double>(mem));
if (model->GetV8MemoryUsed(index, &mem))
result->SetDouble(keys::kJsMemoryUsedKey,
static_cast<double>(mem));
if (model->GetSqliteMemoryUsedBytes(index, &mem))
result->SetDouble(keys::kSqliteMemoryKey,
static_cast<double>(mem));
WebKit::WebCache::ResourceTypeStats cache_stats;
if (model->GetWebCoreCacheStats(index, &cache_stats)) {
result->Set(keys::kImageCacheKey,
CreateCacheData(cache_stats.images));
result->Set(keys::kScriptCacheKey,
CreateCacheData(cache_stats.scripts));
result->Set(keys::kCssCacheKey,
CreateCacheData(cache_stats.cssStyleSheets));
}
// Network and FPS are reported by the TaskManager per resource (tab), not
// per process, therefore we need to iterate through the group of resources
// and aggregate the data.
float fps = 0, tmp = 0;
int64 net = 0;
int length = model->GetGroupRangeForResource(index).second;
for (int i = 0; i < length; ++i) {
net += model->GetNetworkUsage(index + i);
if (model->GetFPS(index + i, &tmp))
fps += tmp;
}
result->SetDouble(keys::kFPSKey, static_cast<double>(fps));
result->SetDouble(keys::kNetworkKey, static_cast<double>(net));
return result;
}
// Since memory details are expensive to gather, we don't do it by default.
// This function is a helper to add memory details data to an existing
// Process object representation.
void AddMemoryDetails(base::DictionaryValue* result,
TaskManagerModel* model,
int index) {
size_t mem;
int64 pr_mem = model->GetPrivateMemory(index, &mem) ?
static_cast<int64>(mem) : -1;
result->SetDouble(keys::kPrivateMemoryKey, static_cast<double>(pr_mem));
}
#endif // defined(ENABLE_TASK_MANAGER)
} // namespace
ProcessesEventRouter::ProcessesEventRouter(Profile* profile)
: profile_(profile),
listeners_(0),
task_manager_listening_(false) {
#if defined(ENABLE_TASK_MANAGER)
model_ = TaskManager::GetInstance()->model();
model_->AddObserver(this);
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_HANG,
content::NotificationService::AllSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllSources());
#endif // defined(ENABLE_TASK_MANAGER)
}
ProcessesEventRouter::~ProcessesEventRouter() {
#if defined(ENABLE_TASK_MANAGER)
registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_HANG,
content::NotificationService::AllSources());
registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::NotificationService::AllSources());
if (task_manager_listening_)
model_->StopListening();
model_->RemoveObserver(this);
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::ListenerAdded() {
#if defined(ENABLE_TASK_MANAGER)
// The task manager has its own ref count to balance other callers of
// StartUpdating/StopUpdating.
model_->StartUpdating();
#endif // defined(ENABLE_TASK_MANAGER)
++listeners_;
}
void ProcessesEventRouter::ListenerRemoved() {
DCHECK_GT(listeners_, 0);
--listeners_;
#if defined(ENABLE_TASK_MANAGER)
// The task manager has its own ref count to balance other callers of
// StartUpdating/StopUpdating.
model_->StopUpdating();
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::StartTaskManagerListening() {
#if defined(ENABLE_TASK_MANAGER)
if (!task_manager_listening_) {
model_->StartListening();
task_manager_listening_ = true;
}
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_HANG:
ProcessHangEvent(
content::Source<content::RenderWidgetHost>(source).ptr());
break;
case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
ProcessClosedEvent(
content::Source<content::RenderProcessHost>(source).ptr(),
content::Details<content::RenderProcessHost::RendererClosedDetails>(
details).ptr());
break;
default:
NOTREACHED() << "Unexpected observe of type " << type;
}
return;
}
void ProcessesEventRouter::OnItemsAdded(int start, int length) {
#if defined(ENABLE_TASK_MANAGER)
DCHECK_EQ(length, 1);
int index = start;
std::string event(keys::kOnCreated);
if (!HasEventListeners(event))
return;
// If the item being added is not the first one in the group, find the base
// index and use it for retrieving the process data.
if (!model_->IsResourceFirstInGroup(start)) {
index = model_->GetGroupIndexForResource(start);
}
scoped_ptr<base::ListValue> args(new base::ListValue());
base::DictionaryValue* process = CreateProcessFromModel(
model_->GetUniqueChildProcessId(index), model_, index, false);
DCHECK(process != NULL);
if (process == NULL)
return;
args->Append(process);
DispatchEvent(keys::kOnCreated, args.Pass());
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::OnItemsChanged(int start, int length) {
#if defined(ENABLE_TASK_MANAGER)
// If we don't have any listeners, return immediately.
if (listeners_ == 0)
return;
if (!model_)
return;
// We need to know which type of onUpdated events to fire and whether to
// collect memory or not.
std::string updated_event(keys::kOnUpdated);
std::string updated_event_memory(keys::kOnUpdatedWithMemory);
bool updated = HasEventListeners(updated_event);
bool updated_memory = HasEventListeners(updated_event_memory);
DCHECK(updated || updated_memory);
IDMap<base::DictionaryValue> processes_map;
for (int i = start; i < start + length; i++) {
if (model_->IsResourceFirstInGroup(i)) {
int id = model_->GetUniqueChildProcessId(i);
base::DictionaryValue* process = CreateProcessFromModel(id, model_, i, true);
processes_map.AddWithID(process, i);
}
}
int id;
std::string idkey(keys::kIdKey);
base::DictionaryValue* processes = new base::DictionaryValue();
if (updated) {
IDMap<base::DictionaryValue>::iterator it(&processes_map);
for (; !it.IsAtEnd(); it.Advance()) {
if (!it.GetCurrentValue()->GetInteger(idkey, &id))
continue;
// Store each process indexed by the string version of its id.
processes->Set(base::IntToString(id), it.GetCurrentValue());
}
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(processes);
DispatchEvent(keys::kOnUpdated, args.Pass());
}
if (updated_memory) {
IDMap<base::DictionaryValue>::iterator it(&processes_map);
for (; !it.IsAtEnd(); it.Advance()) {
if (!it.GetCurrentValue()->GetInteger(idkey, &id))
continue;
AddMemoryDetails(it.GetCurrentValue(), model_, it.GetCurrentKey());
// Store each process indexed by the string version of its id if we didn't
// already insert it as part of the onUpdated processing above.
if (!updated)
processes->Set(base::IntToString(id), it.GetCurrentValue());
}
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(processes);
DispatchEvent(keys::kOnUpdatedWithMemory, args.Pass());
}
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::OnItemsToBeRemoved(int start, int length) {
#if defined(ENABLE_TASK_MANAGER)
DCHECK_EQ(length, 1);
// Process exit for renderer processes has the data about exit code and
// termination status, therefore we will rely on notifications and not on
// the Task Manager data. We do use the rest of this method for non-renderer
// processes.
if (model_->GetResourceType(start) == task_manager::Resource::RENDERER)
return;
// The callback function parameters.
scoped_ptr<base::ListValue> args(new base::ListValue());
// First arg: The id of the process that was closed.
args->Append(new base::FundamentalValue(
model_->GetUniqueChildProcessId(start)));
// Second arg: The exit type for the process.
args->Append(new base::FundamentalValue(0));
// Third arg: The exit code for the process.
args->Append(new base::FundamentalValue(0));
DispatchEvent(keys::kOnExited, args.Pass());
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::ProcessHangEvent(content::RenderWidgetHost* widget) {
#if defined(ENABLE_TASK_MANAGER)
std::string event(keys::kOnUnresponsive);
if (!HasEventListeners(event))
return;
base::DictionaryValue* process = NULL;
int count = model_->ResourceCount();
int id = widget->GetProcess()->GetID();
for (int i = 0; i < count; ++i) {
if (model_->IsResourceFirstInGroup(i)) {
if (id == model_->GetUniqueChildProcessId(i)) {
process = CreateProcessFromModel(id, model_, i, false);
break;
}
}
}
DCHECK(process);
if (process == NULL)
return;
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(process);
DispatchEvent(keys::kOnUnresponsive, args.Pass());
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::ProcessClosedEvent(
content::RenderProcessHost* rph,
content::RenderProcessHost::RendererClosedDetails* details) {
#if defined(ENABLE_TASK_MANAGER)
// The callback function parameters.
scoped_ptr<base::ListValue> args(new base::ListValue());
// First arg: The id of the process that was closed.
args->Append(new base::FundamentalValue(rph->GetID()));
// Second arg: The exit type for the process.
args->Append(new base::FundamentalValue(details->status));
// Third arg: The exit code for the process.
args->Append(new base::FundamentalValue(details->exit_code));
DispatchEvent(keys::kOnExited, args.Pass());
#endif // defined(ENABLE_TASK_MANAGER)
}
void ProcessesEventRouter::DispatchEvent(
const char* event_name,
scoped_ptr<base::ListValue> event_args) {
if (extensions::ExtensionSystem::Get(profile_)->event_router()) {
scoped_ptr<extensions::Event> event(new extensions::Event(
event_name, event_args.Pass()));
extensions::ExtensionSystem::Get(profile_)->event_router()->
BroadcastEvent(event.Pass());
}
}
bool ProcessesEventRouter::HasEventListeners(const std::string& event_name) {
extensions::EventRouter* router =
extensions::ExtensionSystem::Get(profile_)->event_router();
if (router && router->HasEventListener(event_name))
return true;
return false;
}
ProcessesAPI::ProcessesAPI(Profile* profile) : profile_(profile) {
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, processes_api_constants::kOnUpdated);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, processes_api_constants::kOnUpdatedWithMemory);
ExtensionFunctionRegistry* registry =
ExtensionFunctionRegistry::GetInstance();
registry->RegisterFunction<extensions::GetProcessIdForTabFunction>();
registry->RegisterFunction<extensions::TerminateFunction>();
registry->RegisterFunction<extensions::GetProcessInfoFunction>();
}
ProcessesAPI::~ProcessesAPI() {
}
void ProcessesAPI::Shutdown() {
ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
}
static base::LazyInstance<ProfileKeyedAPIFactory<ProcessesAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
ProfileKeyedAPIFactory<ProcessesAPI>* ProcessesAPI::GetFactoryInstance() {
return &g_factory.Get();
}
// static
ProcessesAPI* ProcessesAPI::Get(Profile* profile) {
return ProfileKeyedAPIFactory<ProcessesAPI>::GetForProfile(profile);
}
ProcessesEventRouter* ProcessesAPI::processes_event_router() {
if (!processes_event_router_)
processes_event_router_.reset(new ProcessesEventRouter(profile_));
return processes_event_router_.get();
}
void ProcessesAPI::OnListenerAdded(const EventListenerInfo& details) {
// We lazily tell the TaskManager to start updating when listeners to the
// processes.onUpdated or processes.onUpdatedWithMemory events arrive.
processes_event_router()->ListenerAdded();
}
void ProcessesAPI::OnListenerRemoved(const EventListenerInfo& details) {
// If a processes.onUpdated or processes.onUpdatedWithMemory event listener
// is removed (or a process with one exits), then we let the extension API
// know that it has one fewer listener.
processes_event_router()->ListenerRemoved();
}
GetProcessIdForTabFunction::GetProcessIdForTabFunction() : tab_id_(-1) {
}
bool GetProcessIdForTabFunction::RunImpl() {
#if defined(ENABLE_TASK_MANAGER)
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id_));
// Add a reference, which is balanced in GetProcessIdForTab to keep the object
// around and allow for the callback to be invoked.
AddRef();
// If the task manager is already listening, just post a task to execute
// which will invoke the callback once we have returned from this function.
// Otherwise, wait for the notification that the task manager is done with
// the data gathering.
if (ProcessesAPI::Get(profile_)->processes_event_router()->
is_task_manager_listening()) {
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&GetProcessIdForTabFunction::GetProcessIdForTab, this));
} else {
TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
base::Bind(&GetProcessIdForTabFunction::GetProcessIdForTab, this));
ProcessesAPI::Get(profile_)->processes_event_router()->
StartTaskManagerListening();
}
return true;
#else
error_ = errors::kExtensionNotSupported;
return false;
#endif // defined(ENABLE_TASK_MANAGER)
}
void GetProcessIdForTabFunction::GetProcessIdForTab() {
content::WebContents* contents = NULL;
int tab_index = -1;
if (!ExtensionTabUtil::GetTabById(tab_id_, profile(), include_incognito(),
NULL, NULL, &contents, &tab_index)) {
error_ = ErrorUtils::FormatErrorMessage(
extensions::tabs_constants::kTabNotFoundError,
base::IntToString(tab_id_));
SetResult(new base::FundamentalValue(-1));
SendResponse(false);
} else {
int process_id = contents->GetRenderProcessHost()->GetID();
SetResult(new base::FundamentalValue(process_id));
SendResponse(true);
}
// Balance the AddRef in the RunImpl.
Release();
}
TerminateFunction::TerminateFunction() : process_id_(-1) {
}
bool TerminateFunction::RunImpl() {
#if defined(ENABLE_TASK_MANAGER)
EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &process_id_));
// Add a reference, which is balanced in TerminateProcess to keep the object
// around and allow for the callback to be invoked.
AddRef();
// If the task manager is already listening, just post a task to execute
// which will invoke the callback once we have returned from this function.
// Otherwise, wait for the notification that the task manager is done with
// the data gathering.
if (ProcessesAPI::Get(profile_)->processes_event_router()->
is_task_manager_listening()) {
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&TerminateFunction::TerminateProcess, this));
} else {
TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
base::Bind(&TerminateFunction::TerminateProcess, this));
ProcessesAPI::Get(profile_)->processes_event_router()->
StartTaskManagerListening();
}
return true;
#else
error_ = errors::kExtensionNotSupported;
return false;
#endif // defined(ENABLE_TASK_MANAGER)
}
void TerminateFunction::TerminateProcess() {
TaskManagerModel* model = TaskManager::GetInstance()->model();
int count = model->ResourceCount();
bool killed = false;
bool found = false;
for (int i = 0; i < count; ++i) {
if (model->IsResourceFirstInGroup(i)) {
if (process_id_ == model->GetUniqueChildProcessId(i)) {
found = true;
killed = base::KillProcess(model->GetProcess(i),
content::RESULT_CODE_KILLED, true);
UMA_HISTOGRAM_COUNTS("ChildProcess.KilledByExtensionAPI", 1);
break;
}
}
}
if (!found) {
error_ = ErrorUtils::FormatErrorMessage(errors::kProcessNotFound,
base::IntToString(process_id_));
SendResponse(false);
} else {
SetResult(new base::FundamentalValue(killed));
SendResponse(true);
}
// Balance the AddRef in the RunImpl.
Release();
}
GetProcessInfoFunction::GetProcessInfoFunction()
#if defined(ENABLE_TASK_MANAGER)
: memory_(false)
#endif
{
}
GetProcessInfoFunction::~GetProcessInfoFunction() {
}
bool GetProcessInfoFunction::RunImpl() {
#if defined(ENABLE_TASK_MANAGER)
Value* processes = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &processes));
EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &memory_));
EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
processes, &process_ids_));
// Add a reference, which is balanced in GatherProcessInfo to keep the object
// around and allow for the callback to be invoked.
AddRef();
// If the task manager is already listening, just post a task to execute
// which will invoke the callback once we have returned from this function.
// Otherwise, wait for the notification that the task manager is done with
// the data gathering.
if (ProcessesAPI::Get(profile_)->processes_event_router()->
is_task_manager_listening()) {
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
&GetProcessInfoFunction::GatherProcessInfo, this));
} else {
TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
base::Bind(&GetProcessInfoFunction::GatherProcessInfo, this));
ProcessesAPI::Get(profile_)->processes_event_router()->
StartTaskManagerListening();
}
return true;
#else
error_ = errors::kExtensionNotSupported;
return false;
#endif // defined(ENABLE_TASK_MANAGER)
}
void GetProcessInfoFunction::GatherProcessInfo() {
#if defined(ENABLE_TASK_MANAGER)
TaskManagerModel* model = TaskManager::GetInstance()->model();
base::DictionaryValue* processes = new base::DictionaryValue();
// If there are no process IDs specified, it means we need to return all of
// the ones we know of.
if (process_ids_.size() == 0) {
int resources = model->ResourceCount();
for (int i = 0; i < resources; ++i) {
if (model->IsResourceFirstInGroup(i)) {
int id = model->GetUniqueChildProcessId(i);
base::DictionaryValue* d = CreateProcessFromModel(id, model, i, false);
if (memory_)
AddMemoryDetails(d, model, i);
processes->Set(base::IntToString(id), d);
}
}
} else {
int resources = model->ResourceCount();
for (int i = 0; i < resources; ++i) {
if (model->IsResourceFirstInGroup(i)) {
int id = model->GetUniqueChildProcessId(i);
std::vector<int>::iterator proc_id = std::find(process_ids_.begin(),
process_ids_.end(), id);
if (proc_id != process_ids_.end()) {
base::DictionaryValue* d =
CreateProcessFromModel(id, model, i, false);
if (memory_)
AddMemoryDetails(d, model, i);
processes->Set(base::IntToString(id), d);
process_ids_.erase(proc_id);
if (process_ids_.size() == 0)
break;
}
}
}
DCHECK_EQ(process_ids_.size(), 0U);
}
SetResult(processes);
SendResponse(true);
// Balance the AddRef in the RunImpl.
Release();
#endif // defined(ENABLE_TASK_MANAGER)
}
} // namespace extensions