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/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/devtools/devtools_target_impl.h"
23 #include "chrome/browser/extensions/api/debugger/debugger_api_constants.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/extensions/extension_tab_util.h"
26 #include "chrome/browser/infobars/confirm_infobar_delegate.h"
27 #include "chrome/browser/infobars/infobar.h"
28 #include "chrome/browser/infobars/infobar_service.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "content/public/browser/devtools_agent_host.h"
33 #include "content/public/browser/devtools_client_host.h"
34 #include "content/public/browser/devtools_http_handler.h"
35 #include "content/public/browser/devtools_manager.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_system.h"
47 #include "extensions/common/error_utils.h"
48 #include "extensions/common/extension.h"
49 #include "grit/generated_resources.h"
50 #include "ui/base/l10n/l10n_util.h"
52 using content::DevToolsAgentHost;
53 using content::DevToolsClientHost;
54 using content::DevToolsHttpHandler;
55 using content::DevToolsManager;
56 using content::RenderProcessHost;
57 using content::RenderViewHost;
58 using content::RenderWidgetHost;
59 using content::WebContents;
60 using extensions::ErrorUtils;
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;
70 // ExtensionDevToolsClientHost ------------------------------------------------
72 class ExtensionDevToolsClientHost : public DevToolsClientHost,
73 public content::NotificationObserver {
75 ExtensionDevToolsClientHost(
77 DevToolsAgentHost* agent_host,
78 const std::string& extension_id,
79 const std::string& extension_name,
80 const Debuggee& debuggee,
83 virtual ~ExtensionDevToolsClientHost();
85 const std::string& extension_id() { return extension_id_; }
87 void SendMessageToBackend(DebuggerSendCommandFunction* function,
88 const std::string& method,
89 SendCommand::Params::CommandParams* command_params);
91 // Marks connection as to-be-terminated by the user.
92 void MarkAsDismissed();
94 // DevToolsClientHost interface
95 virtual void InspectedContentsClosing() OVERRIDE;
96 virtual void DispatchOnInspectorFrontend(const std::string& message) OVERRIDE;
97 virtual void ReplacedWithAnotherClient() OVERRIDE;
100 void SendDetachedEvent();
102 // content::NotificationObserver implementation.
103 virtual void Observe(int type,
104 const content::NotificationSource& source,
105 const content::NotificationDetails& details) OVERRIDE;
108 scoped_refptr<DevToolsAgentHost> agent_host_;
109 std::string extension_id_;
111 content::NotificationRegistrar registrar_;
112 int last_request_id_;
113 typedef std::map<int, scoped_refptr<DebuggerSendCommandFunction> >
115 PendingRequests pending_requests_;
117 OnDetach::Reason detach_reason_;
119 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost);
122 // The member function declarations come after the other class declarations, so
123 // they can call members on them.
128 // Helpers --------------------------------------------------------------------
130 void CopyDebuggee(Debuggee* dst, const Debuggee& src) {
132 dst->tab_id.reset(new int(*src.tab_id));
133 if (src.extension_id)
134 dst->extension_id.reset(new std::string(*src.extension_id));
136 dst->target_id.reset(new std::string(*src.target_id));
140 // ExtensionDevToolsInfoBarDelegate -------------------------------------------
142 class ExtensionDevToolsInfoBarDelegate : public ConfirmInfoBarDelegate {
144 // Creates an extension dev tools infobar and delegate and adds the infobar to
145 // the InfoBarService associated with |rvh|. Returns the infobar if it was
146 // successfully added.
147 static InfoBar* Create(RenderViewHost* rvh, const std::string& client_name);
149 void set_client_host(ExtensionDevToolsClientHost* client_host) {
150 client_host_ = client_host;
154 explicit ExtensionDevToolsInfoBarDelegate(const std::string& client_name);
155 virtual ~ExtensionDevToolsInfoBarDelegate();
157 // ConfirmInfoBarDelegate:
158 virtual void InfoBarDismissed() OVERRIDE;
159 virtual Type GetInfoBarType() const OVERRIDE;
160 virtual bool ShouldExpireInternal(
161 const content::LoadCommittedDetails& details) const OVERRIDE;
162 virtual base::string16 GetMessageText() const OVERRIDE;
163 virtual int GetButtons() const OVERRIDE;
164 virtual bool Cancel() OVERRIDE;
166 std::string client_name_;
167 ExtensionDevToolsClientHost* client_host_;
169 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsInfoBarDelegate);
173 InfoBar* ExtensionDevToolsInfoBarDelegate::Create(
175 const std::string& client_name) {
179 WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
183 InfoBarService* infobar_service =
184 InfoBarService::FromWebContents(web_contents);
185 if (!infobar_service)
188 return infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
189 scoped_ptr<ConfirmInfoBarDelegate>(
190 new ExtensionDevToolsInfoBarDelegate(client_name))));
193 ExtensionDevToolsInfoBarDelegate::ExtensionDevToolsInfoBarDelegate(
194 const std::string& client_name)
195 : ConfirmInfoBarDelegate(),
196 client_name_(client_name),
200 ExtensionDevToolsInfoBarDelegate::~ExtensionDevToolsInfoBarDelegate() {
203 void ExtensionDevToolsInfoBarDelegate::InfoBarDismissed() {
205 client_host_->MarkAsDismissed();
208 InfoBarDelegate::Type ExtensionDevToolsInfoBarDelegate::GetInfoBarType() const {
212 bool ExtensionDevToolsInfoBarDelegate::ShouldExpireInternal(
213 const content::LoadCommittedDetails& details) const {
217 base::string16 ExtensionDevToolsInfoBarDelegate::GetMessageText() const {
218 return l10n_util::GetStringFUTF16(IDS_DEV_TOOLS_INFOBAR_LABEL,
219 base::UTF8ToUTF16(client_name_));
222 int ExtensionDevToolsInfoBarDelegate::GetButtons() const {
223 return BUTTON_CANCEL;
226 bool ExtensionDevToolsInfoBarDelegate::Cancel() {
232 // AttachedClientHosts --------------------------------------------------------
234 class AttachedClientHosts {
236 AttachedClientHosts();
237 ~AttachedClientHosts();
239 // Returns the singleton instance of this class.
240 static AttachedClientHosts* GetInstance();
242 void Add(ExtensionDevToolsClientHost* client_host);
243 void Remove(ExtensionDevToolsClientHost* client_host);
244 ExtensionDevToolsClientHost* Lookup(DevToolsAgentHost* agent_host,
245 const std::string& extension_id);
248 typedef std::set<ExtensionDevToolsClientHost*> ClientHosts;
249 ClientHosts client_hosts_;
251 DISALLOW_COPY_AND_ASSIGN(AttachedClientHosts);
254 AttachedClientHosts::AttachedClientHosts() {
257 AttachedClientHosts::~AttachedClientHosts() {
261 AttachedClientHosts* AttachedClientHosts::GetInstance() {
262 return Singleton<AttachedClientHosts>::get();
265 void AttachedClientHosts::Add(ExtensionDevToolsClientHost* client_host) {
266 client_hosts_.insert(client_host);
269 void AttachedClientHosts::Remove(ExtensionDevToolsClientHost* client_host) {
270 client_hosts_.erase(client_host);
273 ExtensionDevToolsClientHost* AttachedClientHosts::Lookup(
274 DevToolsAgentHost* agent_host,
275 const std::string& extension_id) {
276 DevToolsManager* manager = DevToolsManager::GetInstance();
277 for (ClientHosts::iterator it = client_hosts_.begin();
278 it != client_hosts_.end(); ++it) {
279 ExtensionDevToolsClientHost* client_host = *it;
280 if (manager->GetDevToolsAgentHostFor(client_host) == agent_host &&
281 client_host->extension_id() == extension_id)
290 // ExtensionDevToolsClientHost ------------------------------------------------
292 ExtensionDevToolsClientHost::ExtensionDevToolsClientHost(
294 DevToolsAgentHost* agent_host,
295 const std::string& extension_id,
296 const std::string& extension_name,
297 const Debuggee& debuggee,
300 agent_host_(agent_host),
301 extension_id_(extension_id),
304 detach_reason_(OnDetach::REASON_TARGET_CLOSED) {
305 CopyDebuggee(&debuggee_, debuggee);
307 AttachedClientHosts::GetInstance()->Add(this);
309 // Detach from debugger when extension unloads.
310 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
311 content::Source<Profile>(profile_));
313 // RVH-based agents disconnect from their clients when the app is terminating
314 // but shared worker-based agents do not.
315 // Disconnect explicitly to make sure that |this| observer is not leaked.
316 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
317 content::NotificationService::AllSources());
319 // Attach to debugger and tell it we are ready.
320 DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
321 agent_host_.get(), this);
324 static_cast<ExtensionDevToolsInfoBarDelegate*>(
325 infobar_->delegate())->set_client_host(this);
327 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
328 content::Source<InfoBarService>(InfoBarService::FromWebContents(
329 WebContents::FromRenderViewHost(
330 agent_host_->GetRenderViewHost()))));
334 ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
335 // Ensure calling RemoveInfoBar() below won't result in Observe() trying to
337 registrar_.RemoveAll();
340 static_cast<ExtensionDevToolsInfoBarDelegate*>(
341 infobar_->delegate())->set_client_host(NULL);
342 InfoBarService::FromWebContents(WebContents::FromRenderViewHost(
343 agent_host_->GetRenderViewHost()))->RemoveInfoBar(infobar_);
345 AttachedClientHosts::GetInstance()->Remove(this);
348 // DevToolsClientHost interface
349 void ExtensionDevToolsClientHost::InspectedContentsClosing() {
354 void ExtensionDevToolsClientHost::ReplacedWithAnotherClient() {
355 detach_reason_ = OnDetach::REASON_REPLACED_WITH_DEVTOOLS;
358 void ExtensionDevToolsClientHost::Close() {
359 DevToolsManager::GetInstance()->ClientHostClosing(this);
363 void ExtensionDevToolsClientHost::SendMessageToBackend(
364 DebuggerSendCommandFunction* function,
365 const std::string& method,
366 SendCommand::Params::CommandParams* command_params) {
367 base::DictionaryValue protocol_request;
368 int request_id = ++last_request_id_;
369 pending_requests_[request_id] = function;
370 protocol_request.SetInteger("id", request_id);
371 protocol_request.SetString("method", method);
372 if (command_params) {
373 protocol_request.Set("params",
374 command_params->additional_properties.DeepCopy());
377 std::string json_args;
378 base::JSONWriter::Write(&protocol_request, &json_args);
379 DevToolsManager::GetInstance()->DispatchOnInspectorBackend(this, json_args);
382 void ExtensionDevToolsClientHost::MarkAsDismissed() {
383 detach_reason_ = OnDetach::REASON_CANCELED_BY_USER;
386 void ExtensionDevToolsClientHost::SendDetachedEvent() {
387 if (!extensions::ExtensionSystem::Get(profile_)->event_router())
390 scoped_ptr<base::ListValue> args(OnDetach::Create(debuggee_,
392 scoped_ptr<extensions::Event> event(new extensions::Event(
393 OnDetach::kEventName, args.Pass()));
394 event->restrict_to_browser_context = profile_;
395 extensions::ExtensionSystem::Get(profile_)->event_router()->
396 DispatchEventToExtension(extension_id_, event.Pass());
399 void ExtensionDevToolsClientHost::Observe(
401 const content::NotificationSource& source,
402 const content::NotificationDetails& details) {
403 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED) {
404 if (content::Details<extensions::UnloadedExtensionInfo>(details)->
405 extension->id() == extension_id_)
407 } else if (type == chrome::NOTIFICATION_APP_TERMINATING) {
410 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
411 if (content::Details<InfoBar::RemovedDetails>(details)->first == infobar_) {
419 void ExtensionDevToolsClientHost::DispatchOnInspectorFrontend(
420 const std::string& message) {
421 if (!extensions::ExtensionSystem::Get(profile_)->event_router())
424 scoped_ptr<base::Value> result(base::JSONReader::Read(message));
425 if (!result->IsType(base::Value::TYPE_DICTIONARY))
427 base::DictionaryValue* dictionary =
428 static_cast<base::DictionaryValue*>(result.get());
431 if (!dictionary->GetInteger("id", &id)) {
432 std::string method_name;
433 if (!dictionary->GetString("method", &method_name))
436 OnEvent::Params params;
437 base::DictionaryValue* params_value;
438 if (dictionary->GetDictionary("params", ¶ms_value))
439 params.additional_properties.Swap(params_value);
441 scoped_ptr<base::ListValue> args(
442 OnEvent::Create(debuggee_, method_name, params));
443 scoped_ptr<extensions::Event> event(new extensions::Event(
444 OnEvent::kEventName, args.Pass()));
445 event->restrict_to_browser_context = profile_;
446 extensions::ExtensionSystem::Get(profile_)->event_router()->
447 DispatchEventToExtension(extension_id_, event.Pass());
449 DebuggerSendCommandFunction* function = pending_requests_[id].get();
453 function->SendResponseBody(dictionary);
454 pending_requests_.erase(id);
459 // DebuggerFunction -----------------------------------------------------------
461 DebuggerFunction::DebuggerFunction()
462 : client_host_(NULL) {
465 DebuggerFunction::~DebuggerFunction() {
468 void DebuggerFunction::FormatErrorMessage(const std::string& format) {
469 if (debuggee_.tab_id)
470 error_ = ErrorUtils::FormatErrorMessage(
471 format, keys::kTabTargetType, base::IntToString(*debuggee_.tab_id));
472 else if (debuggee_.extension_id)
473 error_ = ErrorUtils::FormatErrorMessage(
474 format, keys::kBackgroundPageTargetType, *debuggee_.extension_id);
476 error_ = ErrorUtils::FormatErrorMessage(
477 format, keys::kOpaqueTargetType, *debuggee_.target_id);
480 bool DebuggerFunction::InitAgentHost() {
481 if (debuggee_.tab_id) {
482 WebContents* web_contents = NULL;
483 bool result = extensions::ExtensionTabUtil::GetTabById(*debuggee_.tab_id,
490 if (result && web_contents) {
491 if (content::HasWebUIScheme(web_contents->GetURL())) {
492 error_ = ErrorUtils::FormatErrorMessage(
493 keys::kAttachToWebUIError,
494 web_contents->GetURL().scheme());
497 agent_host_ = DevToolsAgentHost::GetOrCreateFor(web_contents);
499 } else if (debuggee_.extension_id) {
500 extensions::ExtensionHost* extension_host =
501 extensions::ExtensionSystem::Get(GetProfile())
503 ->GetBackgroundHostForExtension(*debuggee_.extension_id);
504 if (extension_host) {
505 agent_host_ = DevToolsAgentHost::GetOrCreateFor(
506 extension_host->render_view_host());
508 } else if (debuggee_.target_id) {
509 agent_host_ = DevToolsAgentHost::GetForId(*debuggee_.target_id);
511 error_ = keys::kInvalidTargetError;
515 if (!agent_host_.get()) {
516 FormatErrorMessage(keys::kNoTargetError);
522 bool DebuggerFunction::InitClientHost() {
523 if (!InitAgentHost())
526 client_host_ = AttachedClientHosts::GetInstance()->Lookup(
527 agent_host_.get(), GetExtension()->id());
530 FormatErrorMessage(keys::kNotAttachedError);
537 // DebuggerAttachFunction -----------------------------------------------------
539 DebuggerAttachFunction::DebuggerAttachFunction() {
542 DebuggerAttachFunction::~DebuggerAttachFunction() {
545 bool DebuggerAttachFunction::RunImpl() {
546 scoped_ptr<Attach::Params> params(Attach::Params::Create(*args_));
547 EXTENSION_FUNCTION_VALIDATE(params.get());
549 CopyDebuggee(&debuggee_, params->target);
550 if (!InitAgentHost())
553 if (!DevToolsHttpHandler::IsSupportedProtocolVersion(
554 params->required_version)) {
555 error_ = ErrorUtils::FormatErrorMessage(
556 keys::kProtocolVersionNotSupportedError,
557 params->required_version);
561 if (agent_host_->IsAttached()) {
562 FormatErrorMessage(keys::kAlreadyAttachedError);
566 InfoBar* infobar = NULL;
567 if (!CommandLine::ForCurrentProcess()->
568 HasSwitch(switches::kSilentDebuggerExtensionAPI)) {
569 // Do not attach to the target if for any reason the infobar cannot be shown
570 // for this WebContents instance.
571 infobar = ExtensionDevToolsInfoBarDelegate::Create(
572 agent_host_->GetRenderViewHost(), GetExtension()->name());
574 error_ = ErrorUtils::FormatErrorMessage(
575 keys::kSilentDebuggingRequired,
576 switches::kSilentDebuggerExtensionAPI);
581 new ExtensionDevToolsClientHost(GetProfile(),
583 GetExtension()->id(),
584 GetExtension()->name(),
592 // DebuggerDetachFunction -----------------------------------------------------
594 DebuggerDetachFunction::DebuggerDetachFunction() {
597 DebuggerDetachFunction::~DebuggerDetachFunction() {
600 bool DebuggerDetachFunction::RunImpl() {
601 scoped_ptr<Detach::Params> params(Detach::Params::Create(*args_));
602 EXTENSION_FUNCTION_VALIDATE(params.get());
604 CopyDebuggee(&debuggee_, params->target);
605 if (!InitClientHost())
608 client_host_->Close();
614 // DebuggerSendCommandFunction ------------------------------------------------
616 DebuggerSendCommandFunction::DebuggerSendCommandFunction() {
619 DebuggerSendCommandFunction::~DebuggerSendCommandFunction() {
622 bool DebuggerSendCommandFunction::RunImpl() {
623 scoped_ptr<SendCommand::Params> params(SendCommand::Params::Create(*args_));
624 EXTENSION_FUNCTION_VALIDATE(params.get());
626 CopyDebuggee(&debuggee_, params->target);
627 if (!InitClientHost())
630 client_host_->SendMessageToBackend(this, params->method,
631 params->command_params.get());
635 void DebuggerSendCommandFunction::SendResponseBody(
636 base::DictionaryValue* response) {
637 base::Value* error_body;
638 if (response->Get("error", &error_body)) {
639 base::JSONWriter::Write(error_body, &error_);
644 base::DictionaryValue* result_body;
645 SendCommand::Results::Result result;
646 if (response->GetDictionary("result", &result_body))
647 result.additional_properties.Swap(result_body);
649 results_ = SendCommand::Results::Create(result);
654 // DebuggerGetTargetsFunction -------------------------------------------------
658 const char kTargetIdField[] = "id";
659 const char kTargetTypeField[] = "type";
660 const char kTargetTitleField[] = "title";
661 const char kTargetAttachedField[] = "attached";
662 const char kTargetUrlField[] = "url";
663 const char kTargetFaviconUrlField[] = "faviconUrl";
664 const char kTargetTypePage[] = "page";
665 const char kTargetTypeBackgroundPage[] = "background_page";
666 const char kTargetTypeWorker[] = "worker";
667 const char kTargetTypeOther[] = "other";
668 const char kTargetTabIdField[] = "tabId";
669 const char kTargetExtensionIdField[] = "extensionId";
671 base::Value* SerializeTarget(const DevToolsTargetImpl& target) {
672 base::DictionaryValue* dictionary = new base::DictionaryValue();
674 dictionary->SetString(kTargetIdField, target.GetId());
675 dictionary->SetString(kTargetTitleField, target.GetTitle());
676 dictionary->SetBoolean(kTargetAttachedField, target.IsAttached());
677 dictionary->SetString(kTargetUrlField, target.GetUrl().spec());
679 std::string type = target.GetType();
680 if (type == kTargetTypePage) {
681 dictionary->SetInteger(kTargetTabIdField, target.GetTabId());
682 } else if (type == kTargetTypeBackgroundPage) {
683 dictionary->SetString(kTargetExtensionIdField, target.GetExtensionId());
684 } else if (type != kTargetTypeWorker) {
685 // DevToolsTargetImpl may support more types than the debugger API.
686 type = kTargetTypeOther;
688 dictionary->SetString(kTargetTypeField, type);
690 GURL favicon_url = target.GetFaviconUrl();
691 if (favicon_url.is_valid())
692 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
699 DebuggerGetTargetsFunction::DebuggerGetTargetsFunction() {
702 DebuggerGetTargetsFunction::~DebuggerGetTargetsFunction() {
705 bool DebuggerGetTargetsFunction::RunImpl() {
706 DevToolsTargetImpl::EnumerateAllTargets(
707 base::Bind(&DebuggerGetTargetsFunction::SendTargetList, this));
711 void DebuggerGetTargetsFunction::SendTargetList(
712 const std::vector<DevToolsTargetImpl*>& target_list) {
713 scoped_ptr<base::ListValue> result(new base::ListValue());
714 for (size_t i = 0; i < target_list.size(); ++i)
715 result->Append(SerializeTarget(*target_list[i]));
716 STLDeleteContainerPointers(target_list.begin(), target_list.end());
717 SetResult(result.release());