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 // Implements the Chrome Extensions Debugger API.
7 #include "chrome/browser/extensions/api/debugger/debugger_api.h"
12 #include "base/command_line.h"
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/singleton.h"
17 #include "base/scoped_observer.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/devtools/devtools_target_impl.h"
24 #include "chrome/browser/extensions/api/debugger/debugger_api_constants.h"
25 #include "chrome/browser/extensions/extension_service.h"
26 #include "chrome/browser/extensions/extension_tab_util.h"
27 #include "chrome/browser/infobars/infobar_service.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "components/infobars/core/confirm_infobar_delegate.h"
33 #include "components/infobars/core/infobar.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/devtools_agent_host.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/notification_source.h"
38 #include "content/public/browser/render_process_host.h"
39 #include "content/public/browser/render_view_host.h"
40 #include "content/public/browser/render_widget_host.h"
41 #include "content/public/browser/web_contents.h"
42 #include "content/public/common/content_client.h"
43 #include "content/public/common/url_utils.h"
44 #include "extensions/browser/event_router.h"
45 #include "extensions/browser/extension_host.h"
46 #include "extensions/browser/extension_registry.h"
47 #include "extensions/browser/extension_registry_observer.h"
48 #include "extensions/common/constants.h"
49 #include "extensions/common/error_utils.h"
50 #include "extensions/common/extension.h"
51 #include "extensions/common/manifest_constants.h"
52 #include "extensions/common/permissions/permissions_data.h"
53 #include "extensions/common/switches.h"
54 #include "ui/base/l10n/l10n_util.h"
56 using content::DevToolsAgentHost
;
57 using content::RenderProcessHost
;
58 using content::RenderViewHost
;
59 using content::RenderWidgetHost
;
60 using content::WebContents
;
62 namespace keys
= debugger_api_constants
;
63 namespace Attach
= extensions::api::debugger::Attach
;
64 namespace Detach
= extensions::api::debugger::Detach
;
65 namespace OnDetach
= extensions::api::debugger::OnDetach
;
66 namespace OnEvent
= extensions::api::debugger::OnEvent
;
67 namespace SendCommand
= extensions::api::debugger::SendCommand
;
69 namespace extensions
{
70 class ExtensionRegistry
;
72 // ExtensionDevToolsClientHost ------------------------------------------------
74 class ExtensionDevToolsClientHost
: public content::DevToolsAgentHostClient
,
75 public content::NotificationObserver
,
76 public ExtensionRegistryObserver
{
78 ExtensionDevToolsClientHost(Profile
* profile
,
79 DevToolsAgentHost
* agent_host
,
80 const std::string
& extension_id
,
81 const std::string
& extension_name
,
82 const Debuggee
& debuggee
,
83 infobars::InfoBar
* infobar
);
85 ~ExtensionDevToolsClientHost() override
;
87 const std::string
& extension_id() { return extension_id_
; }
88 DevToolsAgentHost
* agent_host() { return agent_host_
.get(); }
90 void SendMessageToBackend(DebuggerSendCommandFunction
* function
,
91 const std::string
& method
,
92 SendCommand::Params::CommandParams
* command_params
);
94 // Marks connection as to-be-terminated by the user.
95 void MarkAsDismissed();
97 // DevToolsAgentHostClient interface.
98 void AgentHostClosed(DevToolsAgentHost
* agent_host
,
99 bool replaced_with_another_client
) override
;
100 void DispatchProtocolMessage(DevToolsAgentHost
* agent_host
,
101 const std::string
& message
) override
;
104 void SendDetachedEvent();
106 // content::NotificationObserver implementation.
107 void Observe(int type
,
108 const content::NotificationSource
& source
,
109 const content::NotificationDetails
& details
) override
;
111 // ExtensionRegistryObserver implementation.
112 void OnExtensionUnloaded(content::BrowserContext
* browser_context
,
113 const Extension
* extension
,
114 UnloadedExtensionInfo::Reason reason
) override
;
117 scoped_refptr
<DevToolsAgentHost
> agent_host_
;
118 std::string extension_id_
;
120 content::NotificationRegistrar registrar_
;
121 int last_request_id_
;
122 typedef std::map
<int, scoped_refptr
<DebuggerSendCommandFunction
> >
124 PendingRequests pending_requests_
;
125 infobars::InfoBar
* infobar_
;
126 api::debugger::DetachReason detach_reason_
;
128 // Listen to extension unloaded notification.
129 ScopedObserver
<ExtensionRegistry
, ExtensionRegistryObserver
>
130 extension_registry_observer_
;
132 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost
);
135 // The member function declarations come after the other class declarations, so
136 // they can call members on them.
141 // Helpers --------------------------------------------------------------------
143 void CopyDebuggee(Debuggee
* dst
, const Debuggee
& src
) {
145 dst
->tab_id
.reset(new int(*src
.tab_id
));
146 if (src
.extension_id
)
147 dst
->extension_id
.reset(new std::string(*src
.extension_id
));
149 dst
->target_id
.reset(new std::string(*src
.target_id
));
153 // ExtensionDevToolsInfoBarDelegate -------------------------------------------
155 class ExtensionDevToolsInfoBarDelegate
: public ConfirmInfoBarDelegate
{
157 // Creates an extension dev tools infobar and delegate and adds the infobar to
158 // the InfoBarService associated with |rvh|. Returns the infobar if it was
159 // successfully added.
160 static infobars::InfoBar
* Create(WebContents
* web_contents
,
161 const std::string
& client_name
);
163 void set_client_host(ExtensionDevToolsClientHost
* client_host
) {
164 client_host_
= client_host
;
168 explicit ExtensionDevToolsInfoBarDelegate(const std::string
& client_name
);
169 ~ExtensionDevToolsInfoBarDelegate() override
;
171 // ConfirmInfoBarDelegate:
172 Type
GetInfoBarType() const override
;
173 bool ShouldExpire(const NavigationDetails
& details
) const override
;
174 void InfoBarDismissed() override
;
175 base::string16
GetMessageText() const override
;
176 int GetButtons() const override
;
177 bool Cancel() override
;
179 std::string client_name_
;
180 ExtensionDevToolsClientHost
* client_host_
;
182 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsInfoBarDelegate
);
186 infobars::InfoBar
* ExtensionDevToolsInfoBarDelegate::Create(
187 WebContents
* web_contents
,
188 const std::string
& client_name
) {
192 InfoBarService
* infobar_service
=
193 InfoBarService::FromWebContents(web_contents
);
194 if (!infobar_service
)
197 return infobar_service
->AddInfoBar(
198 infobar_service
->CreateConfirmInfoBar(scoped_ptr
<ConfirmInfoBarDelegate
>(
199 new ExtensionDevToolsInfoBarDelegate(client_name
))));
202 ExtensionDevToolsInfoBarDelegate::ExtensionDevToolsInfoBarDelegate(
203 const std::string
& client_name
)
204 : ConfirmInfoBarDelegate(),
205 client_name_(client_name
),
209 ExtensionDevToolsInfoBarDelegate::~ExtensionDevToolsInfoBarDelegate() {
212 infobars::InfoBarDelegate::Type
213 ExtensionDevToolsInfoBarDelegate::GetInfoBarType() const {
217 bool ExtensionDevToolsInfoBarDelegate::ShouldExpire(
218 const NavigationDetails
& details
) const {
222 void ExtensionDevToolsInfoBarDelegate::InfoBarDismissed() {
224 client_host_
->MarkAsDismissed();
227 base::string16
ExtensionDevToolsInfoBarDelegate::GetMessageText() const {
228 return l10n_util::GetStringFUTF16(IDS_DEV_TOOLS_INFOBAR_LABEL
,
229 base::UTF8ToUTF16(client_name_
));
232 int ExtensionDevToolsInfoBarDelegate::GetButtons() const {
233 return BUTTON_CANCEL
;
236 bool ExtensionDevToolsInfoBarDelegate::Cancel() {
242 // AttachedClientHosts --------------------------------------------------------
244 class AttachedClientHosts
{
246 AttachedClientHosts();
247 ~AttachedClientHosts();
249 // Returns the singleton instance of this class.
250 static AttachedClientHosts
* GetInstance();
252 void Add(ExtensionDevToolsClientHost
* client_host
);
253 void Remove(ExtensionDevToolsClientHost
* client_host
);
254 ExtensionDevToolsClientHost
* Lookup(DevToolsAgentHost
* agent_host
,
255 const std::string
& extension_id
);
258 typedef std::set
<ExtensionDevToolsClientHost
*> ClientHosts
;
259 ClientHosts client_hosts_
;
261 DISALLOW_COPY_AND_ASSIGN(AttachedClientHosts
);
264 AttachedClientHosts::AttachedClientHosts() {
267 AttachedClientHosts::~AttachedClientHosts() {
271 AttachedClientHosts
* AttachedClientHosts::GetInstance() {
272 return Singleton
<AttachedClientHosts
>::get();
275 void AttachedClientHosts::Add(ExtensionDevToolsClientHost
* client_host
) {
276 client_hosts_
.insert(client_host
);
279 void AttachedClientHosts::Remove(ExtensionDevToolsClientHost
* client_host
) {
280 client_hosts_
.erase(client_host
);
283 ExtensionDevToolsClientHost
* AttachedClientHosts::Lookup(
284 DevToolsAgentHost
* agent_host
,
285 const std::string
& extension_id
) {
286 for (ClientHosts::iterator it
= client_hosts_
.begin();
287 it
!= client_hosts_
.end(); ++it
) {
288 ExtensionDevToolsClientHost
* client_host
= *it
;
289 if (client_host
->agent_host() == agent_host
&&
290 client_host
->extension_id() == extension_id
)
299 // ExtensionDevToolsClientHost ------------------------------------------------
301 ExtensionDevToolsClientHost::ExtensionDevToolsClientHost(
303 DevToolsAgentHost
* agent_host
,
304 const std::string
& extension_id
,
305 const std::string
& extension_name
,
306 const Debuggee
& debuggee
,
307 infobars::InfoBar
* infobar
)
309 agent_host_(agent_host
),
310 extension_id_(extension_id
),
313 detach_reason_(api::debugger::DETACH_REASON_TARGET_CLOSED
),
314 extension_registry_observer_(this) {
315 CopyDebuggee(&debuggee_
, debuggee
);
317 AttachedClientHosts::GetInstance()->Add(this);
319 // ExtensionRegistryObserver listen extension unloaded and detach debugger
321 extension_registry_observer_
.Add(ExtensionRegistry::Get(profile_
));
323 // RVH-based agents disconnect from their clients when the app is terminating
324 // but shared worker-based agents do not.
325 // Disconnect explicitly to make sure that |this| observer is not leaked.
326 registrar_
.Add(this, chrome::NOTIFICATION_APP_TERMINATING
,
327 content::NotificationService::AllSources());
329 // Attach to debugger and tell it we are ready.
330 agent_host_
->AttachClient(this);
333 static_cast<ExtensionDevToolsInfoBarDelegate
*>(
334 infobar_
->delegate())->set_client_host(this);
337 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
338 content::Source
<InfoBarService
>(
339 InfoBarService::FromWebContents(agent_host_
->GetWebContents())));
343 ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
344 // Ensure calling RemoveInfoBar() below won't result in Observe() trying to
346 registrar_
.RemoveAll();
349 static_cast<ExtensionDevToolsInfoBarDelegate
*>(
350 infobar_
->delegate())->set_client_host(NULL
);
351 InfoBarService
* infobar_service
=
352 InfoBarService::FromWebContents(agent_host_
->GetWebContents());
353 infobar_service
->RemoveInfoBar(infobar_
);
355 AttachedClientHosts::GetInstance()->Remove(this);
358 // DevToolsAgentHostClient implementation.
359 void ExtensionDevToolsClientHost::AgentHostClosed(
360 DevToolsAgentHost
* agent_host
, bool replaced_with_another_client
) {
361 DCHECK(agent_host
== agent_host_
.get());
362 if (replaced_with_another_client
)
363 detach_reason_
= api::debugger::DETACH_REASON_REPLACED_WITH_DEVTOOLS
;
368 void ExtensionDevToolsClientHost::Close() {
369 agent_host_
->DetachClient();
373 void ExtensionDevToolsClientHost::SendMessageToBackend(
374 DebuggerSendCommandFunction
* function
,
375 const std::string
& method
,
376 SendCommand::Params::CommandParams
* command_params
) {
377 base::DictionaryValue protocol_request
;
378 int request_id
= ++last_request_id_
;
379 pending_requests_
[request_id
] = function
;
380 protocol_request
.SetInteger("id", request_id
);
381 protocol_request
.SetString("method", method
);
382 if (command_params
) {
383 protocol_request
.Set("params",
384 command_params
->additional_properties
.DeepCopy());
387 std::string json_args
;
388 base::JSONWriter::Write(protocol_request
, &json_args
);
389 agent_host_
->DispatchProtocolMessage(json_args
);
392 void ExtensionDevToolsClientHost::MarkAsDismissed() {
393 detach_reason_
= api::debugger::DETACH_REASON_CANCELED_BY_USER
;
396 void ExtensionDevToolsClientHost::SendDetachedEvent() {
397 if (!EventRouter::Get(profile_
))
400 scoped_ptr
<base::ListValue
> args(OnDetach::Create(debuggee_
,
402 scoped_ptr
<Event
> event(
403 new Event(events::DEBUGGER_ON_DETACH
, OnDetach::kEventName
, args
.Pass()));
404 event
->restrict_to_browser_context
= profile_
;
405 EventRouter::Get(profile_
)
406 ->DispatchEventToExtension(extension_id_
, event
.Pass());
409 void ExtensionDevToolsClientHost::OnExtensionUnloaded(
410 content::BrowserContext
* browser_context
,
411 const Extension
* extension
,
412 UnloadedExtensionInfo::Reason reason
) {
413 if (extension
->id() == extension_id_
)
417 void ExtensionDevToolsClientHost::Observe(
419 const content::NotificationSource
& source
,
420 const content::NotificationDetails
& details
) {
422 case chrome::NOTIFICATION_APP_TERMINATING
:
425 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
:
426 if (content::Details
<infobars::InfoBar::RemovedDetails
>(details
)->first
==
438 void ExtensionDevToolsClientHost::DispatchProtocolMessage(
439 DevToolsAgentHost
* agent_host
, const std::string
& message
) {
440 DCHECK(agent_host
== agent_host_
.get());
441 if (!EventRouter::Get(profile_
))
444 scoped_ptr
<base::Value
> result
= base::JSONReader::Read(message
);
445 if (!result
->IsType(base::Value::TYPE_DICTIONARY
))
447 base::DictionaryValue
* dictionary
=
448 static_cast<base::DictionaryValue
*>(result
.get());
451 if (!dictionary
->GetInteger("id", &id
)) {
452 std::string method_name
;
453 if (!dictionary
->GetString("method", &method_name
))
456 OnEvent::Params params
;
457 base::DictionaryValue
* params_value
;
458 if (dictionary
->GetDictionary("params", ¶ms_value
))
459 params
.additional_properties
.Swap(params_value
);
461 scoped_ptr
<base::ListValue
> args(
462 OnEvent::Create(debuggee_
, method_name
, params
));
463 scoped_ptr
<Event
> event(
464 new Event(events::DEBUGGER_ON_EVENT
, OnEvent::kEventName
, args
.Pass()));
465 event
->restrict_to_browser_context
= profile_
;
466 EventRouter::Get(profile_
)
467 ->DispatchEventToExtension(extension_id_
, event
.Pass());
469 DebuggerSendCommandFunction
* function
= pending_requests_
[id
].get();
473 function
->SendResponseBody(dictionary
);
474 pending_requests_
.erase(id
);
479 // DebuggerFunction -----------------------------------------------------------
481 DebuggerFunction::DebuggerFunction()
482 : client_host_(NULL
) {
485 DebuggerFunction::~DebuggerFunction() {
488 void DebuggerFunction::FormatErrorMessage(const std::string
& format
) {
489 if (debuggee_
.tab_id
)
490 error_
= ErrorUtils::FormatErrorMessage(
491 format
, keys::kTabTargetType
, base::IntToString(*debuggee_
.tab_id
));
492 else if (debuggee_
.extension_id
)
493 error_
= ErrorUtils::FormatErrorMessage(
494 format
, keys::kBackgroundPageTargetType
, *debuggee_
.extension_id
);
496 error_
= ErrorUtils::FormatErrorMessage(
497 format
, keys::kOpaqueTargetType
, *debuggee_
.target_id
);
500 bool DebuggerFunction::InitAgentHost() {
501 if (debuggee_
.tab_id
) {
502 WebContents
* web_contents
= NULL
;
503 bool result
= ExtensionTabUtil::GetTabById(*debuggee_
.tab_id
,
510 if (result
&& web_contents
) {
511 // TODO(rdevlin.cronin) This should definitely be GetLastCommittedURL().
512 GURL url
= web_contents
->GetVisibleURL();
513 if (PermissionsData::IsRestrictedUrl(url
, extension(), &error_
))
515 agent_host_
= DevToolsAgentHost::GetOrCreateFor(web_contents
);
517 } else if (debuggee_
.extension_id
) {
518 ExtensionHost
* extension_host
=
519 ProcessManager::Get(GetProfile())
520 ->GetBackgroundHostForExtension(*debuggee_
.extension_id
);
521 if (extension_host
) {
522 if (PermissionsData::IsRestrictedUrl(extension_host
->GetURL(),
528 DevToolsAgentHost::GetOrCreateFor(extension_host
->host_contents());
530 } else if (debuggee_
.target_id
) {
531 agent_host_
= DevToolsAgentHost::GetForId(*debuggee_
.target_id
);
532 if (agent_host_
.get()) {
533 if (PermissionsData::IsRestrictedUrl(agent_host_
->GetURL(),
536 agent_host_
= nullptr;
541 error_
= keys::kInvalidTargetError
;
545 if (!agent_host_
.get()) {
546 FormatErrorMessage(keys::kNoTargetError
);
552 bool DebuggerFunction::InitClientHost() {
553 if (!InitAgentHost())
556 client_host_
= AttachedClientHosts::GetInstance()->Lookup(agent_host_
.get(),
560 FormatErrorMessage(keys::kNotAttachedError
);
567 // DebuggerAttachFunction -----------------------------------------------------
569 DebuggerAttachFunction::DebuggerAttachFunction() {
572 DebuggerAttachFunction::~DebuggerAttachFunction() {
575 bool DebuggerAttachFunction::RunAsync() {
576 scoped_ptr
<Attach::Params
> params(Attach::Params::Create(*args_
));
577 EXTENSION_FUNCTION_VALIDATE(params
.get());
579 CopyDebuggee(&debuggee_
, params
->target
);
580 if (!InitAgentHost())
583 if (!DevToolsAgentHost::IsSupportedProtocolVersion(
584 params
->required_version
)) {
585 error_
= ErrorUtils::FormatErrorMessage(
586 keys::kProtocolVersionNotSupportedError
,
587 params
->required_version
);
591 if (agent_host_
->IsAttached()) {
592 FormatErrorMessage(keys::kAlreadyAttachedError
);
596 infobars::InfoBar
* infobar
= NULL
;
597 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
598 ::switches::kSilentDebuggerExtensionAPI
)) {
599 // Do not attach to the target if for any reason the infobar cannot be shown
600 // for this WebContents instance.
601 infobar
= ExtensionDevToolsInfoBarDelegate::Create(
602 agent_host_
->GetWebContents(), extension()->name());
604 error_
= ErrorUtils::FormatErrorMessage(
605 keys::kSilentDebuggingRequired
,
606 ::switches::kSilentDebuggerExtensionAPI
);
611 new ExtensionDevToolsClientHost(GetProfile(),
622 // DebuggerDetachFunction -----------------------------------------------------
624 DebuggerDetachFunction::DebuggerDetachFunction() {
627 DebuggerDetachFunction::~DebuggerDetachFunction() {
630 bool DebuggerDetachFunction::RunAsync() {
631 scoped_ptr
<Detach::Params
> params(Detach::Params::Create(*args_
));
632 EXTENSION_FUNCTION_VALIDATE(params
.get());
634 CopyDebuggee(&debuggee_
, params
->target
);
635 if (!InitClientHost())
638 client_host_
->Close();
644 // DebuggerSendCommandFunction ------------------------------------------------
646 DebuggerSendCommandFunction::DebuggerSendCommandFunction() {
649 DebuggerSendCommandFunction::~DebuggerSendCommandFunction() {
652 bool DebuggerSendCommandFunction::RunAsync() {
653 scoped_ptr
<SendCommand::Params
> params(SendCommand::Params::Create(*args_
));
654 EXTENSION_FUNCTION_VALIDATE(params
.get());
656 CopyDebuggee(&debuggee_
, params
->target
);
657 if (!InitClientHost())
660 client_host_
->SendMessageToBackend(this, params
->method
,
661 params
->command_params
.get());
665 void DebuggerSendCommandFunction::SendResponseBody(
666 base::DictionaryValue
* response
) {
667 base::Value
* error_body
;
668 if (response
->Get("error", &error_body
)) {
669 base::JSONWriter::Write(*error_body
, &error_
);
674 base::DictionaryValue
* result_body
;
675 SendCommand::Results::Result result
;
676 if (response
->GetDictionary("result", &result_body
))
677 result
.additional_properties
.Swap(result_body
);
679 results_
= SendCommand::Results::Create(result
);
684 // DebuggerGetTargetsFunction -------------------------------------------------
688 const char kTargetIdField
[] = "id";
689 const char kTargetTypeField
[] = "type";
690 const char kTargetTitleField
[] = "title";
691 const char kTargetAttachedField
[] = "attached";
692 const char kTargetUrlField
[] = "url";
693 const char kTargetFaviconUrlField
[] = "faviconUrl";
694 const char kTargetTypePage
[] = "page";
695 const char kTargetTypeBackgroundPage
[] = "background_page";
696 const char kTargetTypeWorker
[] = "worker";
697 const char kTargetTypeOther
[] = "other";
698 const char kTargetTabIdField
[] = "tabId";
699 const char kTargetExtensionIdField
[] = "extensionId";
701 base::Value
* SerializeTarget(const DevToolsTargetImpl
& target
) {
702 base::DictionaryValue
* dictionary
= new base::DictionaryValue();
704 dictionary
->SetString(kTargetIdField
, target
.GetId());
705 dictionary
->SetString(kTargetTitleField
, target
.GetTitle());
706 dictionary
->SetBoolean(kTargetAttachedField
, target
.IsAttached());
707 dictionary
->SetString(kTargetUrlField
, target
.GetURL().spec());
709 std::string type
= target
.GetType();
710 if (type
== kTargetTypePage
) {
711 dictionary
->SetInteger(kTargetTabIdField
, target
.GetTabId());
712 } else if (type
== kTargetTypeBackgroundPage
) {
713 dictionary
->SetString(kTargetExtensionIdField
, target
.GetExtensionId());
714 } else if (type
!= kTargetTypeWorker
) {
715 // DevToolsTargetImpl may support more types than the debugger API.
716 type
= kTargetTypeOther
;
718 dictionary
->SetString(kTargetTypeField
, type
);
720 GURL favicon_url
= target
.GetFaviconURL();
721 if (favicon_url
.is_valid())
722 dictionary
->SetString(kTargetFaviconUrlField
, favicon_url
.spec());
729 DebuggerGetTargetsFunction::DebuggerGetTargetsFunction() {
732 DebuggerGetTargetsFunction::~DebuggerGetTargetsFunction() {
735 bool DebuggerGetTargetsFunction::RunAsync() {
736 std::vector
<DevToolsTargetImpl
*> list
= DevToolsTargetImpl::EnumerateAll();
737 content::BrowserThread::PostTask(
738 content::BrowserThread::UI
,
740 base::Bind(&DebuggerGetTargetsFunction::SendTargetList
, this, list
));
744 void DebuggerGetTargetsFunction::SendTargetList(
745 const std::vector
<DevToolsTargetImpl
*>& target_list
) {
746 scoped_ptr
<base::ListValue
> result(new base::ListValue());
747 for (size_t i
= 0; i
< target_list
.size(); ++i
)
748 result
->Append(SerializeTarget(*target_list
[i
]));
749 STLDeleteContainerPointers(target_list
.begin(), target_list
.end());
750 SetResult(result
.release());
754 } // namespace extensions