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/event_router.h"
25 #include "chrome/browser/extensions/extension_host.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/extensions/extension_system.h"
28 #include "chrome/browser/extensions/extension_tab_util.h"
29 #include "chrome/browser/infobars/confirm_infobar_delegate.h"
30 #include "chrome/browser/infobars/infobar.h"
31 #include "chrome/browser/infobars/infobar_service.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/common/extensions/extension.h"
36 #include "content/public/browser/devtools_agent_host.h"
37 #include "content/public/browser/devtools_client_host.h"
38 #include "content/public/browser/devtools_http_handler.h"
39 #include "content/public/browser/devtools_manager.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/notification_source.h"
42 #include "content/public/browser/render_process_host.h"
43 #include "content/public/browser/render_view_host.h"
44 #include "content/public/browser/render_widget_host.h"
45 #include "content/public/browser/web_contents.h"
46 #include "content/public/common/content_client.h"
47 #include "content/public/common/url_utils.h"
48 #include "extensions/common/error_utils.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 class ExtensionDevToolsInfoBarDelegate;
74 // ExtensionDevToolsClientHost ------------------------------------------------
76 class ExtensionDevToolsClientHost : public DevToolsClientHost,
77 public content::NotificationObserver {
79 ExtensionDevToolsClientHost(
81 DevToolsAgentHost* agent_host,
82 const std::string& extension_id,
83 const std::string& extension_name,
84 const Debuggee& debuggee,
85 ExtensionDevToolsInfoBarDelegate* infobar);
87 virtual ~ExtensionDevToolsClientHost();
89 const std::string& extension_id() { return extension_id_; }
91 void SendMessageToBackend(DebuggerSendCommandFunction* function,
92 const std::string& method,
93 SendCommand::Params::CommandParams* command_params);
95 // Marks connection as to-be-terminated by the user.
96 void MarkAsDismissed();
98 // DevToolsClientHost interface
99 virtual void InspectedContentsClosing() OVERRIDE;
100 virtual void DispatchOnInspectorFrontend(const std::string& message) OVERRIDE;
101 virtual void ReplacedWithAnotherClient() OVERRIDE;
104 void SendDetachedEvent();
106 // content::NotificationObserver implementation.
107 virtual void Observe(int type,
108 const content::NotificationSource& source,
109 const content::NotificationDetails& details) OVERRIDE;
112 scoped_refptr<DevToolsAgentHost> agent_host_;
113 std::string extension_id_;
115 content::NotificationRegistrar registrar_;
116 int last_request_id_;
117 typedef std::map<int, scoped_refptr<DebuggerSendCommandFunction> >
119 PendingRequests pending_requests_;
120 ExtensionDevToolsInfoBarDelegate* infobar_;
121 OnDetach::Reason detach_reason_;
123 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost);
126 // The member function declarations come after the other class declarations, so
127 // they can call members on them.
132 // Helpers --------------------------------------------------------------------
134 void CopyDebuggee(Debuggee* dst, const Debuggee& src) {
136 dst->tab_id.reset(new int(*src.tab_id));
137 if (src.extension_id)
138 dst->extension_id.reset(new std::string(*src.extension_id));
140 dst->target_id.reset(new std::string(*src.target_id));
144 // ExtensionDevToolsInfoBarDelegate -------------------------------------------
146 class ExtensionDevToolsInfoBarDelegate : public ConfirmInfoBarDelegate {
148 // Creates an extension dev tools infobar delegate and adds it to the
149 // InfoBarService associated with |rvh|. Returns the delegate if it was
150 // successfully added.
151 static ExtensionDevToolsInfoBarDelegate* Create(
153 const std::string& client_name);
155 void set_client_host(ExtensionDevToolsClientHost* client_host) {
156 client_host_ = client_host;
160 ExtensionDevToolsInfoBarDelegate(InfoBarService* infobar_service,
161 const std::string& client_name);
162 virtual ~ExtensionDevToolsInfoBarDelegate();
164 // ConfirmInfoBarDelegate:
165 virtual void InfoBarDismissed() OVERRIDE;
166 virtual Type GetInfoBarType() const OVERRIDE;
167 virtual bool ShouldExpireInternal(
168 const content::LoadCommittedDetails& details) const OVERRIDE;
169 virtual string16 GetMessageText() const OVERRIDE;
170 virtual int GetButtons() const OVERRIDE;
171 virtual bool Cancel() OVERRIDE;
173 std::string client_name_;
174 ExtensionDevToolsClientHost* client_host_;
176 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsInfoBarDelegate);
180 ExtensionDevToolsInfoBarDelegate* ExtensionDevToolsInfoBarDelegate::Create(
182 const std::string& client_name) {
186 WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
190 InfoBarService* infobar_service =
191 InfoBarService::FromWebContents(web_contents);
192 if (!infobar_service)
195 return static_cast<ExtensionDevToolsInfoBarDelegate*>(
196 infobar_service->AddInfoBar(scoped_ptr<InfoBarDelegate>(
197 new ExtensionDevToolsInfoBarDelegate(infobar_service, client_name))));
200 ExtensionDevToolsInfoBarDelegate::ExtensionDevToolsInfoBarDelegate(
201 InfoBarService* infobar_service,
202 const std::string& client_name)
203 : ConfirmInfoBarDelegate(infobar_service),
204 client_name_(client_name),
208 ExtensionDevToolsInfoBarDelegate::~ExtensionDevToolsInfoBarDelegate() {
211 void ExtensionDevToolsInfoBarDelegate::InfoBarDismissed() {
213 client_host_->MarkAsDismissed();
216 InfoBarDelegate::Type ExtensionDevToolsInfoBarDelegate::GetInfoBarType() const {
220 bool ExtensionDevToolsInfoBarDelegate::ShouldExpireInternal(
221 const content::LoadCommittedDetails& details) const {
225 string16 ExtensionDevToolsInfoBarDelegate::GetMessageText() const {
226 return l10n_util::GetStringFUTF16(IDS_DEV_TOOLS_INFOBAR_LABEL,
227 UTF8ToUTF16(client_name_));
230 int ExtensionDevToolsInfoBarDelegate::GetButtons() const {
231 return BUTTON_CANCEL;
234 bool ExtensionDevToolsInfoBarDelegate::Cancel() {
240 // AttachedClientHosts --------------------------------------------------------
242 class AttachedClientHosts {
244 AttachedClientHosts();
245 ~AttachedClientHosts();
247 // Returns the singleton instance of this class.
248 static AttachedClientHosts* GetInstance();
250 void Add(ExtensionDevToolsClientHost* client_host);
251 void Remove(ExtensionDevToolsClientHost* client_host);
252 ExtensionDevToolsClientHost* Lookup(DevToolsAgentHost* agent_host,
253 const std::string& extension_id);
256 typedef std::set<ExtensionDevToolsClientHost*> ClientHosts;
257 ClientHosts client_hosts_;
259 DISALLOW_COPY_AND_ASSIGN(AttachedClientHosts);
262 AttachedClientHosts::AttachedClientHosts() {
265 AttachedClientHosts::~AttachedClientHosts() {
269 AttachedClientHosts* AttachedClientHosts::GetInstance() {
270 return Singleton<AttachedClientHosts>::get();
273 void AttachedClientHosts::Add(ExtensionDevToolsClientHost* client_host) {
274 client_hosts_.insert(client_host);
277 void AttachedClientHosts::Remove(ExtensionDevToolsClientHost* client_host) {
278 client_hosts_.erase(client_host);
281 ExtensionDevToolsClientHost* AttachedClientHosts::Lookup(
282 DevToolsAgentHost* agent_host,
283 const std::string& extension_id) {
284 DevToolsManager* manager = DevToolsManager::GetInstance();
285 for (ClientHosts::iterator it = client_hosts_.begin();
286 it != client_hosts_.end(); ++it) {
287 ExtensionDevToolsClientHost* client_host = *it;
288 if (manager->GetDevToolsAgentHostFor(client_host) == agent_host &&
289 client_host->extension_id() == extension_id)
298 // ExtensionDevToolsClientHost ------------------------------------------------
300 ExtensionDevToolsClientHost::ExtensionDevToolsClientHost(
302 DevToolsAgentHost* agent_host,
303 const std::string& extension_id,
304 const std::string& extension_name,
305 const Debuggee& debuggee,
306 ExtensionDevToolsInfoBarDelegate* infobar)
308 agent_host_(agent_host),
309 extension_id_(extension_id),
312 detach_reason_(OnDetach::REASON_TARGET_CLOSED) {
313 CopyDebuggee(&debuggee_, debuggee);
315 AttachedClientHosts::GetInstance()->Add(this);
317 // Detach from debugger when extension unloads.
318 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
319 content::Source<Profile>(profile_));
321 // RVH-based agents disconnect from their clients when the app is terminating
322 // but shared worker-based agents do not.
323 // Disconnect explicitly to make sure that |this| observer is not leaked.
324 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
325 content::NotificationService::AllSources());
327 // Attach to debugger and tell it we are ready.
328 DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
329 agent_host_.get(), this);
332 infobar_->set_client_host(this);
334 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
335 content::Source<InfoBarService>(InfoBarService::FromWebContents(
336 WebContents::FromRenderViewHost(
337 agent_host_->GetRenderViewHost()))));
341 ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
342 // Ensure calling RemoveInfoBar() below won't result in Observe() trying to
344 registrar_.RemoveAll();
347 infobar_->set_client_host(NULL);
348 InfoBarService::FromWebContents(WebContents::FromRenderViewHost(
349 agent_host_->GetRenderViewHost()))->RemoveInfoBar(infobar_);
351 AttachedClientHosts::GetInstance()->Remove(this);
354 // DevToolsClientHost interface
355 void ExtensionDevToolsClientHost::InspectedContentsClosing() {
360 void ExtensionDevToolsClientHost::ReplacedWithAnotherClient() {
361 detach_reason_ = OnDetach::REASON_REPLACED_WITH_DEVTOOLS;
364 void ExtensionDevToolsClientHost::Close() {
365 DevToolsManager::GetInstance()->ClientHostClosing(this);
369 void ExtensionDevToolsClientHost::SendMessageToBackend(
370 DebuggerSendCommandFunction* function,
371 const std::string& method,
372 SendCommand::Params::CommandParams* command_params) {
373 base::DictionaryValue protocol_request;
374 int request_id = ++last_request_id_;
375 pending_requests_[request_id] = function;
376 protocol_request.SetInteger("id", request_id);
377 protocol_request.SetString("method", method);
378 if (command_params) {
379 protocol_request.Set("params",
380 command_params->additional_properties.DeepCopy());
383 std::string json_args;
384 base::JSONWriter::Write(&protocol_request, &json_args);
385 DevToolsManager::GetInstance()->DispatchOnInspectorBackend(this, json_args);
388 void ExtensionDevToolsClientHost::MarkAsDismissed() {
389 detach_reason_ = OnDetach::REASON_CANCELED_BY_USER;
392 void ExtensionDevToolsClientHost::SendDetachedEvent() {
393 if (!extensions::ExtensionSystem::Get(profile_)->event_router())
396 scoped_ptr<base::ListValue> args(OnDetach::Create(debuggee_,
398 scoped_ptr<extensions::Event> event(new extensions::Event(
399 OnDetach::kEventName, args.Pass()));
400 event->restrict_to_profile = profile_;
401 extensions::ExtensionSystem::Get(profile_)->event_router()->
402 DispatchEventToExtension(extension_id_, event.Pass());
405 void ExtensionDevToolsClientHost::Observe(
407 const content::NotificationSource& source,
408 const content::NotificationDetails& details) {
409 if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
410 if (content::Details<extensions::UnloadedExtensionInfo>(details)->
411 extension->id() == extension_id_)
413 } else if (type == chrome::NOTIFICATION_APP_TERMINATING) {
416 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
417 if (content::Details<InfoBarRemovedDetails>(details)->first == infobar_) {
425 void ExtensionDevToolsClientHost::DispatchOnInspectorFrontend(
426 const std::string& message) {
427 if (!extensions::ExtensionSystem::Get(profile_)->event_router())
430 scoped_ptr<Value> result(base::JSONReader::Read(message));
431 if (!result->IsType(Value::TYPE_DICTIONARY))
433 base::DictionaryValue* dictionary =
434 static_cast<base::DictionaryValue*>(result.get());
437 if (!dictionary->GetInteger("id", &id)) {
438 std::string method_name;
439 if (!dictionary->GetString("method", &method_name))
442 OnEvent::Params params;
443 base::DictionaryValue* params_value;
444 if (dictionary->GetDictionary("params", ¶ms_value))
445 params.additional_properties.Swap(params_value);
447 scoped_ptr<ListValue> args(OnEvent::Create(debuggee_, method_name, params));
448 scoped_ptr<extensions::Event> event(new extensions::Event(
449 OnEvent::kEventName, args.Pass()));
450 event->restrict_to_profile = profile_;
451 extensions::ExtensionSystem::Get(profile_)->event_router()->
452 DispatchEventToExtension(extension_id_, event.Pass());
454 DebuggerSendCommandFunction* function = pending_requests_[id].get();
458 function->SendResponseBody(dictionary);
459 pending_requests_.erase(id);
464 // DebuggerFunction -----------------------------------------------------------
466 DebuggerFunction::DebuggerFunction()
467 : client_host_(NULL) {
470 DebuggerFunction::~DebuggerFunction() {
473 void DebuggerFunction::FormatErrorMessage(const std::string& format) {
474 if (debuggee_.tab_id)
475 error_ = ErrorUtils::FormatErrorMessage(
476 format, keys::kTabTargetType, base::IntToString(*debuggee_.tab_id));
477 else if (debuggee_.extension_id)
478 error_ = ErrorUtils::FormatErrorMessage(
479 format, keys::kBackgroundPageTargetType, *debuggee_.extension_id);
481 error_ = ErrorUtils::FormatErrorMessage(
482 format, keys::kOpaqueTargetType, *debuggee_.target_id);
485 bool DebuggerFunction::InitAgentHost() {
486 if (debuggee_.tab_id) {
487 WebContents* web_contents = NULL;
488 bool result = ExtensionTabUtil::GetTabById(*debuggee_.tab_id,
495 if (result && web_contents) {
496 if (content::HasWebUIScheme(web_contents->GetURL())) {
497 error_ = ErrorUtils::FormatErrorMessage(
498 keys::kAttachToWebUIError,
499 web_contents->GetURL().scheme());
502 agent_host_ = DevToolsAgentHost::GetOrCreateFor(
503 web_contents->GetRenderViewHost());
505 } else if (debuggee_.extension_id) {
506 extensions::ExtensionHost* extension_host =
507 extensions::ExtensionSystem::Get(GetProfile())
509 ->GetBackgroundHostForExtension(*debuggee_.extension_id);
510 if (extension_host) {
511 agent_host_ = DevToolsAgentHost::GetOrCreateFor(
512 extension_host->render_view_host());
514 } else if (debuggee_.target_id) {
515 agent_host_ = DevToolsAgentHost::GetForId(*debuggee_.target_id);
517 error_ = keys::kInvalidTargetError;
521 if (!agent_host_.get()) {
522 FormatErrorMessage(keys::kNoTargetError);
528 bool DebuggerFunction::InitClientHost() {
529 if (!InitAgentHost())
532 client_host_ = AttachedClientHosts::GetInstance()->Lookup(
533 agent_host_.get(), GetExtension()->id());
536 FormatErrorMessage(keys::kNotAttachedError);
543 // DebuggerAttachFunction -----------------------------------------------------
545 DebuggerAttachFunction::DebuggerAttachFunction() {
548 DebuggerAttachFunction::~DebuggerAttachFunction() {
551 bool DebuggerAttachFunction::RunImpl() {
552 scoped_ptr<Attach::Params> params(Attach::Params::Create(*args_));
553 EXTENSION_FUNCTION_VALIDATE(params.get());
555 CopyDebuggee(&debuggee_, params->target);
556 if (!InitAgentHost())
559 if (!DevToolsHttpHandler::IsSupportedProtocolVersion(
560 params->required_version)) {
561 error_ = ErrorUtils::FormatErrorMessage(
562 keys::kProtocolVersionNotSupportedError,
563 params->required_version);
567 if (agent_host_->IsAttached()) {
568 FormatErrorMessage(keys::kAlreadyAttachedError);
572 ExtensionDevToolsInfoBarDelegate* infobar = NULL;
573 if (!CommandLine::ForCurrentProcess()->
574 HasSwitch(switches::kSilentDebuggerExtensionAPI)) {
575 // Do not attach to the target if for any reason the infobar cannot be shown
576 // for this WebContents instance.
577 infobar = ExtensionDevToolsInfoBarDelegate::Create(
578 agent_host_->GetRenderViewHost(), GetExtension()->name());
580 error_ = ErrorUtils::FormatErrorMessage(
581 keys::kSilentDebuggingRequired,
582 switches::kSilentDebuggerExtensionAPI);
587 new ExtensionDevToolsClientHost(GetProfile(),
589 GetExtension()->id(),
590 GetExtension()->name(),
598 // DebuggerDetachFunction -----------------------------------------------------
600 DebuggerDetachFunction::DebuggerDetachFunction() {
603 DebuggerDetachFunction::~DebuggerDetachFunction() {
606 bool DebuggerDetachFunction::RunImpl() {
607 scoped_ptr<Detach::Params> params(Detach::Params::Create(*args_));
608 EXTENSION_FUNCTION_VALIDATE(params.get());
610 CopyDebuggee(&debuggee_, params->target);
611 if (!InitClientHost())
614 client_host_->Close();
620 // DebuggerSendCommandFunction ------------------------------------------------
622 DebuggerSendCommandFunction::DebuggerSendCommandFunction() {
625 DebuggerSendCommandFunction::~DebuggerSendCommandFunction() {
628 bool DebuggerSendCommandFunction::RunImpl() {
629 scoped_ptr<SendCommand::Params> params(SendCommand::Params::Create(*args_));
630 EXTENSION_FUNCTION_VALIDATE(params.get());
632 CopyDebuggee(&debuggee_, params->target);
633 if (!InitClientHost())
636 client_host_->SendMessageToBackend(this, params->method,
637 params->command_params.get());
641 void DebuggerSendCommandFunction::SendResponseBody(
642 base::DictionaryValue* response) {
644 if (response->Get("error", &error_body)) {
645 base::JSONWriter::Write(error_body, &error_);
650 base::DictionaryValue* result_body;
651 SendCommand::Results::Result result;
652 if (response->GetDictionary("result", &result_body))
653 result.additional_properties.Swap(result_body);
655 results_ = SendCommand::Results::Create(result);
660 // DebuggerGetTargetsFunction -------------------------------------------------
664 const char kTargetIdField[] = "id";
665 const char kTargetTypeField[] = "type";
666 const char kTargetTitleField[] = "title";
667 const char kTargetAttachedField[] = "attached";
668 const char kTargetUrlField[] = "url";
669 const char kTargetFaviconUrlField[] = "faviconUrl";
670 const char kTargetTypePage[] = "page";
671 const char kTargetTypeBackgroundPage[] = "background_page";
672 const char kTargetTypeWorker[] = "worker";
673 const char kTargetTypeOther[] = "other";
674 const char kTargetTabIdField[] = "tabId";
675 const char kTargetExtensionIdField[] = "extensionId";
677 base::Value* SerializeTarget(const DevToolsTargetImpl& target) {
678 base::DictionaryValue* dictionary = new base::DictionaryValue();
680 dictionary->SetString(kTargetIdField, target.GetId());
681 dictionary->SetString(kTargetTitleField, target.GetTitle());
682 dictionary->SetBoolean(kTargetAttachedField, target.IsAttached());
683 dictionary->SetString(kTargetUrlField, target.GetUrl().spec());
685 std::string type = target.GetType();
686 if (type == kTargetTypePage) {
687 dictionary->SetInteger(kTargetTabIdField, target.GetTabId());
688 } else if (type == kTargetTypeBackgroundPage) {
689 dictionary->SetString(kTargetExtensionIdField, target.GetExtensionId());
690 } else if (type != kTargetTypeWorker) {
691 // DevToolsTargetImpl may support more types than the debugger API.
692 type = kTargetTypeOther;
694 dictionary->SetString(kTargetTypeField, type);
696 GURL favicon_url = target.GetFaviconUrl();
697 if (favicon_url.is_valid())
698 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
705 DebuggerGetTargetsFunction::DebuggerGetTargetsFunction() {
708 DebuggerGetTargetsFunction::~DebuggerGetTargetsFunction() {
711 bool DebuggerGetTargetsFunction::RunImpl() {
712 DevToolsTargetImpl::EnumerateAllTargets(
713 base::Bind(&DebuggerGetTargetsFunction::SendTargetList, this));
717 void DebuggerGetTargetsFunction::SendTargetList(
718 const std::vector<DevToolsTargetImpl*>& target_list) {
719 scoped_ptr<base::ListValue> result(new base::ListValue());
720 for (size_t i = 0; i < target_list.size(); ++i)
721 result->Append(SerializeTarget(*target_list[i]));
722 STLDeleteContainerPointers(target_list.begin(), target_list.end());
723 SetResult(result.release());