1 // Copyright (c) 2012 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.
5 #include "chrome/browser/extensions/api/processes/processes_api.h"
7 #include "base/callback.h"
8 #include "base/json/json_writer.h"
9 #include "base/lazy_instance.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/extensions/api/processes/processes_api_constants.h"
17 #include "chrome/browser/extensions/api/tabs/tabs_constants.h"
18 #include "chrome/browser/extensions/event_router.h"
19 #include "chrome/browser/extensions/extension_function_registry.h"
20 #include "chrome/browser/extensions/extension_function_util.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/extensions/extension_tab_util.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/task_manager/resource_provider.h"
26 #include "chrome/browser/task_manager/task_manager.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_source.h"
30 #include "content/public/browser/notification_types.h"
31 #include "content/public/browser/render_process_host.h"
32 #include "content/public/browser/render_view_host.h"
33 #include "content/public/browser/render_widget_host.h"
34 #include "content/public/browser/render_widget_host_iterator.h"
35 #include "content/public/browser/web_contents.h"
36 #include "content/public/common/result_codes.h"
37 #include "extensions/common/error_utils.h"
39 namespace extensions {
41 namespace keys = processes_api_constants;
42 namespace errors = processes_api_constants;
46 #if defined(ENABLE_TASK_MANAGER)
48 base::DictionaryValue* CreateCacheData(
49 const WebKit::WebCache::ResourceTypeStat& stat) {
51 base::DictionaryValue* cache = new base::DictionaryValue();
52 cache->SetDouble(keys::kCacheSize, static_cast<double>(stat.size));
53 cache->SetDouble(keys::kCacheLiveSize, static_cast<double>(stat.liveSize));
57 void SetProcessType(base::DictionaryValue* result,
58 TaskManagerModel* model,
60 // Determine process type.
61 std::string type = keys::kProcessTypeOther;
62 task_manager::Resource::Type resource_type = model->GetResourceType(index);
63 switch (resource_type) {
64 case task_manager::Resource::BROWSER:
65 type = keys::kProcessTypeBrowser;
67 case task_manager::Resource::RENDERER:
68 type = keys::kProcessTypeRenderer;
70 case task_manager::Resource::EXTENSION:
71 type = keys::kProcessTypeExtension;
73 case task_manager::Resource::NOTIFICATION:
74 type = keys::kProcessTypeNotification;
76 case task_manager::Resource::PLUGIN:
77 type = keys::kProcessTypePlugin;
79 case task_manager::Resource::WORKER:
80 type = keys::kProcessTypeWorker;
82 case task_manager::Resource::NACL:
83 type = keys::kProcessTypeNacl;
85 case task_manager::Resource::UTILITY:
86 type = keys::kProcessTypeUtility;
88 case task_manager::Resource::GPU:
89 type = keys::kProcessTypeGPU;
91 case task_manager::Resource::ZYGOTE:
92 case task_manager::Resource::SANDBOX_HELPER:
93 case task_manager::Resource::UNKNOWN:
94 type = keys::kProcessTypeOther;
97 NOTREACHED() << "Unknown resource type.";
99 result->SetString(keys::kTypeKey, type);
102 base::ListValue* GetTabsForProcess(int process_id) {
103 base::ListValue* tabs_list = new base::ListValue();
105 // The tabs list only makes sense for render processes, so if we don't find
106 // one, just return the empty list.
107 content::RenderProcessHost* rph =
108 content::RenderProcessHost::FromID(process_id);
113 // We need to loop through all the RVHs to ensure we collect the set of all
114 // tabs using this renderer process.
115 scoped_ptr<content::RenderWidgetHostIterator> widgets(
116 content::RenderWidgetHost::GetRenderWidgetHosts());
117 while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
118 if (widget->GetProcess()->GetID() != process_id)
120 if (!widget->IsRenderView())
123 content::RenderViewHost* host = content::RenderViewHost::From(widget);
124 content::WebContents* contents =
125 content::WebContents::FromRenderViewHost(host);
127 tab_id = ExtensionTabUtil::GetTabId(contents);
129 tabs_list->Append(new base::FundamentalValue(tab_id));
136 // This function creates a Process object to be returned to the extensions
137 // using these APIs. For memory details, which are not added by this function,
138 // the callers need to use AddMemoryDetails.
139 base::DictionaryValue* CreateProcessFromModel(int process_id,
140 TaskManagerModel* model,
142 bool include_optional) {
143 base::DictionaryValue* result = new base::DictionaryValue();
146 result->SetInteger(keys::kIdKey, process_id);
147 result->SetInteger(keys::kOsProcessIdKey, model->GetProcessId(index));
148 SetProcessType(result, model, index);
149 result->SetString(keys::kProfileKey,
150 model->GetResourceProfileName(index));
152 result->Set(keys::kTabsListKey, GetTabsForProcess(process_id));
154 // If we don't need to include the optional properties, just return now.
155 if (!include_optional)
158 result->SetDouble(keys::kCpuKey, model->GetCPUUsage(index));
160 if (model->GetV8Memory(index, &mem))
161 result->SetDouble(keys::kJsMemoryAllocatedKey,
162 static_cast<double>(mem));
164 if (model->GetV8MemoryUsed(index, &mem))
165 result->SetDouble(keys::kJsMemoryUsedKey,
166 static_cast<double>(mem));
168 if (model->GetSqliteMemoryUsedBytes(index, &mem))
169 result->SetDouble(keys::kSqliteMemoryKey,
170 static_cast<double>(mem));
172 WebKit::WebCache::ResourceTypeStats cache_stats;
173 if (model->GetWebCoreCacheStats(index, &cache_stats)) {
174 result->Set(keys::kImageCacheKey,
175 CreateCacheData(cache_stats.images));
176 result->Set(keys::kScriptCacheKey,
177 CreateCacheData(cache_stats.scripts));
178 result->Set(keys::kCssCacheKey,
179 CreateCacheData(cache_stats.cssStyleSheets));
182 // Network and FPS are reported by the TaskManager per resource (tab), not
183 // per process, therefore we need to iterate through the group of resources
184 // and aggregate the data.
185 float fps = 0, tmp = 0;
187 int length = model->GetGroupRangeForResource(index).second;
188 for (int i = 0; i < length; ++i) {
189 net += model->GetNetworkUsage(index + i);
190 if (model->GetFPS(index + i, &tmp))
193 result->SetDouble(keys::kFPSKey, static_cast<double>(fps));
194 result->SetDouble(keys::kNetworkKey, static_cast<double>(net));
199 // Since memory details are expensive to gather, we don't do it by default.
200 // This function is a helper to add memory details data to an existing
201 // Process object representation.
202 void AddMemoryDetails(base::DictionaryValue* result,
203 TaskManagerModel* model,
206 int64 pr_mem = model->GetPrivateMemory(index, &mem) ?
207 static_cast<int64>(mem) : -1;
208 result->SetDouble(keys::kPrivateMemoryKey, static_cast<double>(pr_mem));
211 #endif // defined(ENABLE_TASK_MANAGER)
215 ProcessesEventRouter::ProcessesEventRouter(Profile* profile)
218 task_manager_listening_(false) {
219 #if defined(ENABLE_TASK_MANAGER)
220 model_ = TaskManager::GetInstance()->model();
221 model_->AddObserver(this);
223 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_HANG,
224 content::NotificationService::AllSources());
225 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
226 content::NotificationService::AllSources());
227 #endif // defined(ENABLE_TASK_MANAGER)
230 ProcessesEventRouter::~ProcessesEventRouter() {
231 #if defined(ENABLE_TASK_MANAGER)
232 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_HANG,
233 content::NotificationService::AllSources());
234 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
235 content::NotificationService::AllSources());
237 if (task_manager_listening_)
238 model_->StopListening();
240 model_->RemoveObserver(this);
241 #endif // defined(ENABLE_TASK_MANAGER)
244 void ProcessesEventRouter::ListenerAdded() {
245 #if defined(ENABLE_TASK_MANAGER)
246 // The task manager has its own ref count to balance other callers of
247 // StartUpdating/StopUpdating.
248 model_->StartUpdating();
249 #endif // defined(ENABLE_TASK_MANAGER)
253 void ProcessesEventRouter::ListenerRemoved() {
254 DCHECK_GT(listeners_, 0);
256 #if defined(ENABLE_TASK_MANAGER)
257 // The task manager has its own ref count to balance other callers of
258 // StartUpdating/StopUpdating.
259 model_->StopUpdating();
260 #endif // defined(ENABLE_TASK_MANAGER)
263 void ProcessesEventRouter::StartTaskManagerListening() {
264 #if defined(ENABLE_TASK_MANAGER)
265 if (!task_manager_listening_) {
266 model_->StartListening();
267 task_manager_listening_ = true;
269 #endif // defined(ENABLE_TASK_MANAGER)
272 void ProcessesEventRouter::Observe(
274 const content::NotificationSource& source,
275 const content::NotificationDetails& details) {
278 case content::NOTIFICATION_RENDERER_PROCESS_HANG:
280 content::Source<content::RenderWidgetHost>(source).ptr());
282 case content::NOTIFICATION_RENDERER_PROCESS_CLOSED:
284 content::Source<content::RenderProcessHost>(source).ptr(),
285 content::Details<content::RenderProcessHost::RendererClosedDetails>(
289 NOTREACHED() << "Unexpected observe of type " << type;
294 void ProcessesEventRouter::OnItemsAdded(int start, int length) {
295 #if defined(ENABLE_TASK_MANAGER)
296 DCHECK_EQ(length, 1);
299 std::string event(keys::kOnCreated);
300 if (!HasEventListeners(event))
303 // If the item being added is not the first one in the group, find the base
304 // index and use it for retrieving the process data.
305 if (!model_->IsResourceFirstInGroup(start)) {
306 index = model_->GetGroupIndexForResource(start);
309 scoped_ptr<base::ListValue> args(new base::ListValue());
310 base::DictionaryValue* process = CreateProcessFromModel(
311 model_->GetUniqueChildProcessId(index), model_, index, false);
312 DCHECK(process != NULL);
317 args->Append(process);
319 DispatchEvent(keys::kOnCreated, args.Pass());
320 #endif // defined(ENABLE_TASK_MANAGER)
323 void ProcessesEventRouter::OnItemsChanged(int start, int length) {
324 #if defined(ENABLE_TASK_MANAGER)
325 // If we don't have any listeners, return immediately.
332 // We need to know which type of onUpdated events to fire and whether to
333 // collect memory or not.
334 std::string updated_event(keys::kOnUpdated);
335 std::string updated_event_memory(keys::kOnUpdatedWithMemory);
336 bool updated = HasEventListeners(updated_event);
337 bool updated_memory = HasEventListeners(updated_event_memory);
339 DCHECK(updated || updated_memory);
341 IDMap<base::DictionaryValue> processes_map;
342 for (int i = start; i < start + length; i++) {
343 if (model_->IsResourceFirstInGroup(i)) {
344 int id = model_->GetUniqueChildProcessId(i);
345 base::DictionaryValue* process = CreateProcessFromModel(id, model_, i,
347 processes_map.AddWithID(process, i);
352 std::string idkey(keys::kIdKey);
353 base::DictionaryValue* processes = new base::DictionaryValue();
356 IDMap<base::DictionaryValue>::iterator it(&processes_map);
357 for (; !it.IsAtEnd(); it.Advance()) {
358 if (!it.GetCurrentValue()->GetInteger(idkey, &id))
361 // Store each process indexed by the string version of its id.
362 processes->Set(base::IntToString(id), it.GetCurrentValue());
365 scoped_ptr<base::ListValue> args(new base::ListValue());
366 args->Append(processes);
367 DispatchEvent(keys::kOnUpdated, args.Pass());
370 if (updated_memory) {
371 IDMap<base::DictionaryValue>::iterator it(&processes_map);
372 for (; !it.IsAtEnd(); it.Advance()) {
373 if (!it.GetCurrentValue()->GetInteger(idkey, &id))
376 AddMemoryDetails(it.GetCurrentValue(), model_, it.GetCurrentKey());
378 // Store each process indexed by the string version of its id if we didn't
379 // already insert it as part of the onUpdated processing above.
381 processes->Set(base::IntToString(id), it.GetCurrentValue());
384 scoped_ptr<base::ListValue> args(new base::ListValue());
385 args->Append(processes);
386 DispatchEvent(keys::kOnUpdatedWithMemory, args.Pass());
388 #endif // defined(ENABLE_TASK_MANAGER)
391 void ProcessesEventRouter::OnItemsToBeRemoved(int start, int length) {
392 #if defined(ENABLE_TASK_MANAGER)
393 DCHECK_EQ(length, 1);
395 // Process exit for renderer processes has the data about exit code and
396 // termination status, therefore we will rely on notifications and not on
397 // the Task Manager data. We do use the rest of this method for non-renderer
399 if (model_->GetResourceType(start) == task_manager::Resource::RENDERER)
402 // The callback function parameters.
403 scoped_ptr<base::ListValue> args(new base::ListValue());
405 // First arg: The id of the process that was closed.
406 args->Append(new base::FundamentalValue(
407 model_->GetUniqueChildProcessId(start)));
409 // Second arg: The exit type for the process.
410 args->Append(new base::FundamentalValue(0));
412 // Third arg: The exit code for the process.
413 args->Append(new base::FundamentalValue(0));
415 DispatchEvent(keys::kOnExited, args.Pass());
416 #endif // defined(ENABLE_TASK_MANAGER)
419 void ProcessesEventRouter::ProcessHangEvent(content::RenderWidgetHost* widget) {
420 #if defined(ENABLE_TASK_MANAGER)
421 std::string event(keys::kOnUnresponsive);
422 if (!HasEventListeners(event))
425 base::DictionaryValue* process = NULL;
426 int count = model_->ResourceCount();
427 int id = widget->GetProcess()->GetID();
429 for (int i = 0; i < count; ++i) {
430 if (model_->IsResourceFirstInGroup(i)) {
431 if (id == model_->GetUniqueChildProcessId(i)) {
432 process = CreateProcessFromModel(id, model_, i, false);
442 scoped_ptr<base::ListValue> args(new base::ListValue());
443 args->Append(process);
445 DispatchEvent(keys::kOnUnresponsive, args.Pass());
446 #endif // defined(ENABLE_TASK_MANAGER)
449 void ProcessesEventRouter::ProcessClosedEvent(
450 content::RenderProcessHost* rph,
451 content::RenderProcessHost::RendererClosedDetails* details) {
452 #if defined(ENABLE_TASK_MANAGER)
453 // The callback function parameters.
454 scoped_ptr<base::ListValue> args(new base::ListValue());
456 // First arg: The id of the process that was closed.
457 args->Append(new base::FundamentalValue(rph->GetID()));
459 // Second arg: The exit type for the process.
460 args->Append(new base::FundamentalValue(details->status));
462 // Third arg: The exit code for the process.
463 args->Append(new base::FundamentalValue(details->exit_code));
465 DispatchEvent(keys::kOnExited, args.Pass());
466 #endif // defined(ENABLE_TASK_MANAGER)
469 void ProcessesEventRouter::DispatchEvent(
470 const std::string& event_name,
471 scoped_ptr<base::ListValue> event_args) {
472 if (extensions::ExtensionSystem::Get(profile_)->event_router()) {
473 scoped_ptr<extensions::Event> event(new extensions::Event(
474 event_name, event_args.Pass()));
475 extensions::ExtensionSystem::Get(profile_)->event_router()->
476 BroadcastEvent(event.Pass());
480 bool ProcessesEventRouter::HasEventListeners(const std::string& event_name) {
481 extensions::EventRouter* router =
482 extensions::ExtensionSystem::Get(profile_)->event_router();
483 if (router && router->HasEventListener(event_name))
488 ProcessesAPI::ProcessesAPI(Profile* profile) : profile_(profile) {
489 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
490 this, processes_api_constants::kOnUpdated);
491 ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
492 this, processes_api_constants::kOnUpdatedWithMemory);
493 ExtensionFunctionRegistry* registry =
494 ExtensionFunctionRegistry::GetInstance();
495 registry->RegisterFunction<extensions::GetProcessIdForTabFunction>();
496 registry->RegisterFunction<extensions::TerminateFunction>();
497 registry->RegisterFunction<extensions::GetProcessInfoFunction>();
500 ProcessesAPI::~ProcessesAPI() {
503 void ProcessesAPI::Shutdown() {
504 ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
507 static base::LazyInstance<ProfileKeyedAPIFactory<ProcessesAPI> >
508 g_factory = LAZY_INSTANCE_INITIALIZER;
511 ProfileKeyedAPIFactory<ProcessesAPI>* ProcessesAPI::GetFactoryInstance() {
512 return &g_factory.Get();
516 ProcessesAPI* ProcessesAPI::Get(Profile* profile) {
517 return ProfileKeyedAPIFactory<ProcessesAPI>::GetForProfile(profile);
520 ProcessesEventRouter* ProcessesAPI::processes_event_router() {
521 if (!processes_event_router_)
522 processes_event_router_.reset(new ProcessesEventRouter(profile_));
523 return processes_event_router_.get();
526 void ProcessesAPI::OnListenerAdded(const EventListenerInfo& details) {
527 // We lazily tell the TaskManager to start updating when listeners to the
528 // processes.onUpdated or processes.onUpdatedWithMemory events arrive.
529 processes_event_router()->ListenerAdded();
532 void ProcessesAPI::OnListenerRemoved(const EventListenerInfo& details) {
533 // If a processes.onUpdated or processes.onUpdatedWithMemory event listener
534 // is removed (or a process with one exits), then we let the extension API
535 // know that it has one fewer listener.
536 processes_event_router()->ListenerRemoved();
539 GetProcessIdForTabFunction::GetProcessIdForTabFunction() : tab_id_(-1) {
542 bool GetProcessIdForTabFunction::RunImpl() {
543 #if defined(ENABLE_TASK_MANAGER)
544 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id_));
546 // Add a reference, which is balanced in GetProcessIdForTab to keep the object
547 // around and allow for the callback to be invoked.
550 // If the task manager is already listening, just post a task to execute
551 // which will invoke the callback once we have returned from this function.
552 // Otherwise, wait for the notification that the task manager is done with
553 // the data gathering.
554 if (ProcessesAPI::Get(GetProfile())
555 ->processes_event_router()
556 ->is_task_manager_listening()) {
557 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
558 &GetProcessIdForTabFunction::GetProcessIdForTab, this));
560 TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
561 base::Bind(&GetProcessIdForTabFunction::GetProcessIdForTab, this));
563 ProcessesAPI::Get(GetProfile())
564 ->processes_event_router()
565 ->StartTaskManagerListening();
570 error_ = errors::kExtensionNotSupported;
572 #endif // defined(ENABLE_TASK_MANAGER)
575 void GetProcessIdForTabFunction::GetProcessIdForTab() {
576 content::WebContents* contents = NULL;
578 if (!ExtensionTabUtil::GetTabById(tab_id_,
585 error_ = ErrorUtils::FormatErrorMessage(
586 extensions::tabs_constants::kTabNotFoundError,
587 base::IntToString(tab_id_));
588 SetResult(new base::FundamentalValue(-1));
591 int process_id = contents->GetRenderProcessHost()->GetID();
592 SetResult(new base::FundamentalValue(process_id));
596 // Balance the AddRef in the RunImpl.
600 TerminateFunction::TerminateFunction() : process_id_(-1) {
603 bool TerminateFunction::RunImpl() {
604 #if defined(ENABLE_TASK_MANAGER)
605 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &process_id_));
607 // Add a reference, which is balanced in TerminateProcess to keep the object
608 // around and allow for the callback to be invoked.
611 // If the task manager is already listening, just post a task to execute
612 // which will invoke the callback once we have returned from this function.
613 // Otherwise, wait for the notification that the task manager is done with
614 // the data gathering.
615 if (ProcessesAPI::Get(GetProfile())
616 ->processes_event_router()
617 ->is_task_manager_listening()) {
618 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
619 &TerminateFunction::TerminateProcess, this));
621 TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
622 base::Bind(&TerminateFunction::TerminateProcess, this));
624 ProcessesAPI::Get(GetProfile())
625 ->processes_event_router()
626 ->StartTaskManagerListening();
631 error_ = errors::kExtensionNotSupported;
633 #endif // defined(ENABLE_TASK_MANAGER)
637 void TerminateFunction::TerminateProcess() {
638 TaskManagerModel* model = TaskManager::GetInstance()->model();
640 int count = model->ResourceCount();
644 for (int i = 0; i < count; ++i) {
645 if (model->IsResourceFirstInGroup(i)) {
646 if (process_id_ == model->GetUniqueChildProcessId(i)) {
648 killed = base::KillProcess(model->GetProcess(i),
649 content::RESULT_CODE_KILLED, true);
650 UMA_HISTOGRAM_COUNTS("ChildProcess.KilledByExtensionAPI", 1);
657 error_ = ErrorUtils::FormatErrorMessage(errors::kProcessNotFound,
658 base::IntToString(process_id_));
661 SetResult(new base::FundamentalValue(killed));
665 // Balance the AddRef in the RunImpl.
669 GetProcessInfoFunction::GetProcessInfoFunction()
670 #if defined(ENABLE_TASK_MANAGER)
676 GetProcessInfoFunction::~GetProcessInfoFunction() {
679 bool GetProcessInfoFunction::RunImpl() {
680 #if defined(ENABLE_TASK_MANAGER)
681 Value* processes = NULL;
683 EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &processes));
684 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &memory_));
686 EXTENSION_FUNCTION_VALIDATE(extensions::ReadOneOrMoreIntegers(
687 processes, &process_ids_));
689 // Add a reference, which is balanced in GatherProcessInfo to keep the object
690 // around and allow for the callback to be invoked.
693 // If the task manager is already listening, just post a task to execute
694 // which will invoke the callback once we have returned from this function.
695 // Otherwise, wait for the notification that the task manager is done with
696 // the data gathering.
697 if (ProcessesAPI::Get(GetProfile())
698 ->processes_event_router()
699 ->is_task_manager_listening()) {
700 base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
701 &GetProcessInfoFunction::GatherProcessInfo, this));
703 TaskManager::GetInstance()->model()->RegisterOnDataReadyCallback(
704 base::Bind(&GetProcessInfoFunction::GatherProcessInfo, this));
706 ProcessesAPI::Get(GetProfile())
707 ->processes_event_router()
708 ->StartTaskManagerListening();
713 error_ = errors::kExtensionNotSupported;
715 #endif // defined(ENABLE_TASK_MANAGER)
718 void GetProcessInfoFunction::GatherProcessInfo() {
719 #if defined(ENABLE_TASK_MANAGER)
720 TaskManagerModel* model = TaskManager::GetInstance()->model();
721 base::DictionaryValue* processes = new base::DictionaryValue();
723 // If there are no process IDs specified, it means we need to return all of
724 // the ones we know of.
725 if (process_ids_.size() == 0) {
726 int resources = model->ResourceCount();
727 for (int i = 0; i < resources; ++i) {
728 if (model->IsResourceFirstInGroup(i)) {
729 int id = model->GetUniqueChildProcessId(i);
730 base::DictionaryValue* d = CreateProcessFromModel(id, model, i, false);
732 AddMemoryDetails(d, model, i);
733 processes->Set(base::IntToString(id), d);
737 int resources = model->ResourceCount();
738 for (int i = 0; i < resources; ++i) {
739 if (model->IsResourceFirstInGroup(i)) {
740 int id = model->GetUniqueChildProcessId(i);
741 std::vector<int>::iterator proc_id = std::find(process_ids_.begin(),
742 process_ids_.end(), id);
743 if (proc_id != process_ids_.end()) {
744 base::DictionaryValue* d =
745 CreateProcessFromModel(id, model, i, false);
747 AddMemoryDetails(d, model, i);
748 processes->Set(base::IntToString(id), d);
750 process_ids_.erase(proc_id);
751 if (process_ids_.size() == 0)
756 DCHECK_EQ(process_ids_.size(), 0U);
759 SetResult(processes);
762 // Balance the AddRef in the RunImpl.
764 #endif // defined(ENABLE_TASK_MANAGER)
767 } // namespace extensions