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/confirm_infobar_delegate.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 "components/infobars/core/infobar.h"
33 #include "content/public/browser/devtools_agent_host.h"
34 #include "content/public/browser/devtools_client_host.h"
35 #include "content/public/browser/devtools_http_handler.h"
36 #include "content/public/browser/devtools_manager.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/render_process_host.h"
40 #include "content/public/browser/render_view_host.h"
41 #include "content/public/browser/render_widget_host.h"
42 #include "content/public/browser/web_contents.h"
43 #include "content/public/common/content_client.h"
44 #include "content/public/common/url_utils.h"
45 #include "extensions/browser/event_router.h"
46 #include "extensions/browser/extension_host.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/browser/extension_registry_observer.h"
49 #include "extensions/browser/extension_system.h"
50 #include "extensions/common/error_utils.h"
51 #include "extensions/common/extension.h"
52 #include "grit/generated_resources.h"
53 #include "ui/base/l10n/l10n_util.h"
55 using content::DevToolsAgentHost;
56 using content::DevToolsClientHost;
57 using content::DevToolsHttpHandler;
58 using content::DevToolsManager;
59 using content::RenderProcessHost;
60 using content::RenderViewHost;
61 using content::RenderWidgetHost;
62 using content::WebContents;
64 namespace keys = debugger_api_constants;
65 namespace Attach = extensions::api::debugger::Attach;
66 namespace Detach = extensions::api::debugger::Detach;
67 namespace OnDetach = extensions::api::debugger::OnDetach;
68 namespace OnEvent = extensions::api::debugger::OnEvent;
69 namespace SendCommand = extensions::api::debugger::SendCommand;
71 namespace extensions {
72 class ExtensionRegistry;
74 // ExtensionDevToolsClientHost ------------------------------------------------
76 class ExtensionDevToolsClientHost : public DevToolsClientHost,
77 public content::NotificationObserver,
78 public ExtensionRegistryObserver {
80 ExtensionDevToolsClientHost(Profile* profile,
81 DevToolsAgentHost* agent_host,
82 const std::string& extension_id,
83 const std::string& extension_name,
84 const Debuggee& debuggee,
85 infobars::InfoBar* 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;
111 // ExtensionRegistryObserver implementation.
112 virtual void OnExtensionUnloaded(
113 content::BrowserContext* browser_context,
114 const Extension* extension,
115 UnloadedExtensionInfo::Reason reason) OVERRIDE;
118 scoped_refptr<DevToolsAgentHost> agent_host_;
119 std::string extension_id_;
121 content::NotificationRegistrar registrar_;
122 int last_request_id_;
123 typedef std::map<int, scoped_refptr<DebuggerSendCommandFunction> >
125 PendingRequests pending_requests_;
126 infobars::InfoBar* infobar_;
127 OnDetach::Reason detach_reason_;
129 // Listen to extension unloaded notification.
130 ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
131 extension_registry_observer_;
133 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost);
136 // The member function declarations come after the other class declarations, so
137 // they can call members on them.
142 // Helpers --------------------------------------------------------------------
144 void CopyDebuggee(Debuggee* dst, const Debuggee& src) {
146 dst->tab_id.reset(new int(*src.tab_id));
147 if (src.extension_id)
148 dst->extension_id.reset(new std::string(*src.extension_id));
150 dst->target_id.reset(new std::string(*src.target_id));
154 // ExtensionDevToolsInfoBarDelegate -------------------------------------------
156 class ExtensionDevToolsInfoBarDelegate : public ConfirmInfoBarDelegate {
158 // Creates an extension dev tools infobar and delegate and adds the infobar to
159 // the InfoBarService associated with |rvh|. Returns the infobar if it was
160 // successfully added.
161 static infobars::InfoBar* Create(RenderViewHost* rvh,
162 const std::string& client_name);
164 void set_client_host(ExtensionDevToolsClientHost* client_host) {
165 client_host_ = client_host;
169 explicit ExtensionDevToolsInfoBarDelegate(const std::string& client_name);
170 virtual ~ExtensionDevToolsInfoBarDelegate();
172 // ConfirmInfoBarDelegate:
173 virtual void InfoBarDismissed() OVERRIDE;
174 virtual Type GetInfoBarType() const OVERRIDE;
175 virtual bool ShouldExpireInternal(
176 const NavigationDetails& details) const OVERRIDE;
177 virtual base::string16 GetMessageText() const OVERRIDE;
178 virtual int GetButtons() const OVERRIDE;
179 virtual bool Cancel() OVERRIDE;
181 std::string client_name_;
182 ExtensionDevToolsClientHost* client_host_;
184 DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsInfoBarDelegate);
188 infobars::InfoBar* ExtensionDevToolsInfoBarDelegate::Create(
190 const std::string& client_name) {
194 WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
198 InfoBarService* infobar_service =
199 InfoBarService::FromWebContents(web_contents);
200 if (!infobar_service)
203 return infobar_service->AddInfoBar(ConfirmInfoBarDelegate::CreateInfoBar(
204 scoped_ptr<ConfirmInfoBarDelegate>(
205 new ExtensionDevToolsInfoBarDelegate(client_name))));
208 ExtensionDevToolsInfoBarDelegate::ExtensionDevToolsInfoBarDelegate(
209 const std::string& client_name)
210 : ConfirmInfoBarDelegate(),
211 client_name_(client_name),
215 ExtensionDevToolsInfoBarDelegate::~ExtensionDevToolsInfoBarDelegate() {
218 void ExtensionDevToolsInfoBarDelegate::InfoBarDismissed() {
220 client_host_->MarkAsDismissed();
223 infobars::InfoBarDelegate::Type
224 ExtensionDevToolsInfoBarDelegate::GetInfoBarType() const {
228 bool ExtensionDevToolsInfoBarDelegate::ShouldExpireInternal(
229 const NavigationDetails& details) const {
233 base::string16 ExtensionDevToolsInfoBarDelegate::GetMessageText() const {
234 return l10n_util::GetStringFUTF16(IDS_DEV_TOOLS_INFOBAR_LABEL,
235 base::UTF8ToUTF16(client_name_));
238 int ExtensionDevToolsInfoBarDelegate::GetButtons() const {
239 return BUTTON_CANCEL;
242 bool ExtensionDevToolsInfoBarDelegate::Cancel() {
248 // AttachedClientHosts --------------------------------------------------------
250 class AttachedClientHosts {
252 AttachedClientHosts();
253 ~AttachedClientHosts();
255 // Returns the singleton instance of this class.
256 static AttachedClientHosts* GetInstance();
258 void Add(ExtensionDevToolsClientHost* client_host);
259 void Remove(ExtensionDevToolsClientHost* client_host);
260 ExtensionDevToolsClientHost* Lookup(DevToolsAgentHost* agent_host,
261 const std::string& extension_id);
264 typedef std::set<ExtensionDevToolsClientHost*> ClientHosts;
265 ClientHosts client_hosts_;
267 DISALLOW_COPY_AND_ASSIGN(AttachedClientHosts);
270 AttachedClientHosts::AttachedClientHosts() {
273 AttachedClientHosts::~AttachedClientHosts() {
277 AttachedClientHosts* AttachedClientHosts::GetInstance() {
278 return Singleton<AttachedClientHosts>::get();
281 void AttachedClientHosts::Add(ExtensionDevToolsClientHost* client_host) {
282 client_hosts_.insert(client_host);
285 void AttachedClientHosts::Remove(ExtensionDevToolsClientHost* client_host) {
286 client_hosts_.erase(client_host);
289 ExtensionDevToolsClientHost* AttachedClientHosts::Lookup(
290 DevToolsAgentHost* agent_host,
291 const std::string& extension_id) {
292 DevToolsManager* manager = DevToolsManager::GetInstance();
293 for (ClientHosts::iterator it = client_hosts_.begin();
294 it != client_hosts_.end(); ++it) {
295 ExtensionDevToolsClientHost* client_host = *it;
296 if (manager->GetDevToolsAgentHostFor(client_host) == agent_host &&
297 client_host->extension_id() == extension_id)
306 // ExtensionDevToolsClientHost ------------------------------------------------
308 ExtensionDevToolsClientHost::ExtensionDevToolsClientHost(
310 DevToolsAgentHost* agent_host,
311 const std::string& extension_id,
312 const std::string& extension_name,
313 const Debuggee& debuggee,
314 infobars::InfoBar* infobar)
316 agent_host_(agent_host),
317 extension_id_(extension_id),
320 detach_reason_(OnDetach::REASON_TARGET_CLOSED),
321 extension_registry_observer_(this) {
322 CopyDebuggee(&debuggee_, debuggee);
324 AttachedClientHosts::GetInstance()->Add(this);
326 // ExtensionRegistryObserver listen extension unloaded and detach debugger
328 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
330 // RVH-based agents disconnect from their clients when the app is terminating
331 // but shared worker-based agents do not.
332 // Disconnect explicitly to make sure that |this| observer is not leaked.
333 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
334 content::NotificationService::AllSources());
336 // Attach to debugger and tell it we are ready.
337 DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
338 agent_host_.get(), this);
341 static_cast<ExtensionDevToolsInfoBarDelegate*>(
342 infobar_->delegate())->set_client_host(this);
344 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
345 content::Source<InfoBarService>(InfoBarService::FromWebContents(
346 WebContents::FromRenderViewHost(
347 agent_host_->GetRenderViewHost()))));
351 ExtensionDevToolsClientHost::~ExtensionDevToolsClientHost() {
352 // Ensure calling RemoveInfoBar() below won't result in Observe() trying to
354 registrar_.RemoveAll();
357 static_cast<ExtensionDevToolsInfoBarDelegate*>(
358 infobar_->delegate())->set_client_host(NULL);
359 InfoBarService* infobar_service = InfoBarService::FromWebContents(
360 WebContents::FromRenderViewHost(agent_host_->GetRenderViewHost()));
361 infobar_service->RemoveInfoBar(infobar_);
363 AttachedClientHosts::GetInstance()->Remove(this);
366 // DevToolsClientHost interface
367 void ExtensionDevToolsClientHost::InspectedContentsClosing() {
372 void ExtensionDevToolsClientHost::ReplacedWithAnotherClient() {
373 detach_reason_ = OnDetach::REASON_REPLACED_WITH_DEVTOOLS;
376 void ExtensionDevToolsClientHost::Close() {
377 DevToolsManager::GetInstance()->ClientHostClosing(this);
381 void ExtensionDevToolsClientHost::SendMessageToBackend(
382 DebuggerSendCommandFunction* function,
383 const std::string& method,
384 SendCommand::Params::CommandParams* command_params) {
385 base::DictionaryValue protocol_request;
386 int request_id = ++last_request_id_;
387 pending_requests_[request_id] = function;
388 protocol_request.SetInteger("id", request_id);
389 protocol_request.SetString("method", method);
390 if (command_params) {
391 protocol_request.Set("params",
392 command_params->additional_properties.DeepCopy());
395 std::string json_args;
396 base::JSONWriter::Write(&protocol_request, &json_args);
397 DevToolsManager::GetInstance()->DispatchOnInspectorBackend(this, json_args);
400 void ExtensionDevToolsClientHost::MarkAsDismissed() {
401 detach_reason_ = OnDetach::REASON_CANCELED_BY_USER;
404 void ExtensionDevToolsClientHost::SendDetachedEvent() {
405 if (!EventRouter::Get(profile_))
408 scoped_ptr<base::ListValue> args(OnDetach::Create(debuggee_,
410 scoped_ptr<Event> event(new Event(OnDetach::kEventName, args.Pass()));
411 event->restrict_to_browser_context = profile_;
412 EventRouter::Get(profile_)
413 ->DispatchEventToExtension(extension_id_, event.Pass());
416 void ExtensionDevToolsClientHost::OnExtensionUnloaded(
417 content::BrowserContext* browser_context,
418 const Extension* extension,
419 UnloadedExtensionInfo::Reason reason) {
420 if (extension->id() == extension_id_)
424 void ExtensionDevToolsClientHost::Observe(
426 const content::NotificationSource& source,
427 const content::NotificationDetails& details) {
429 case chrome::NOTIFICATION_APP_TERMINATING:
432 case chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED:
433 if (content::Details<infobars::InfoBar::RemovedDetails>(details)->first ==
445 void ExtensionDevToolsClientHost::DispatchOnInspectorFrontend(
446 const std::string& message) {
447 if (!EventRouter::Get(profile_))
450 scoped_ptr<base::Value> result(base::JSONReader::Read(message));
451 if (!result->IsType(base::Value::TYPE_DICTIONARY))
453 base::DictionaryValue* dictionary =
454 static_cast<base::DictionaryValue*>(result.get());
457 if (!dictionary->GetInteger("id", &id)) {
458 std::string method_name;
459 if (!dictionary->GetString("method", &method_name))
462 OnEvent::Params params;
463 base::DictionaryValue* params_value;
464 if (dictionary->GetDictionary("params", ¶ms_value))
465 params.additional_properties.Swap(params_value);
467 scoped_ptr<base::ListValue> args(
468 OnEvent::Create(debuggee_, method_name, params));
469 scoped_ptr<Event> event(new Event(OnEvent::kEventName, args.Pass()));
470 event->restrict_to_browser_context = profile_;
471 EventRouter::Get(profile_)
472 ->DispatchEventToExtension(extension_id_, event.Pass());
474 DebuggerSendCommandFunction* function = pending_requests_[id].get();
478 function->SendResponseBody(dictionary);
479 pending_requests_.erase(id);
484 // DebuggerFunction -----------------------------------------------------------
486 DebuggerFunction::DebuggerFunction()
487 : client_host_(NULL) {
490 DebuggerFunction::~DebuggerFunction() {
493 void DebuggerFunction::FormatErrorMessage(const std::string& format) {
494 if (debuggee_.tab_id)
495 error_ = ErrorUtils::FormatErrorMessage(
496 format, keys::kTabTargetType, base::IntToString(*debuggee_.tab_id));
497 else if (debuggee_.extension_id)
498 error_ = ErrorUtils::FormatErrorMessage(
499 format, keys::kBackgroundPageTargetType, *debuggee_.extension_id);
501 error_ = ErrorUtils::FormatErrorMessage(
502 format, keys::kOpaqueTargetType, *debuggee_.target_id);
505 bool DebuggerFunction::InitAgentHost() {
506 if (debuggee_.tab_id) {
507 WebContents* web_contents = NULL;
508 bool result = ExtensionTabUtil::GetTabById(*debuggee_.tab_id,
515 if (result && web_contents) {
516 if (content::HasWebUIScheme(web_contents->GetURL())) {
517 error_ = ErrorUtils::FormatErrorMessage(
518 keys::kAttachToWebUIError,
519 web_contents->GetURL().scheme());
522 agent_host_ = DevToolsAgentHost::GetOrCreateFor(web_contents);
524 } else if (debuggee_.extension_id) {
525 ExtensionHost* extension_host =
526 ExtensionSystem::Get(GetProfile())
528 ->GetBackgroundHostForExtension(*debuggee_.extension_id);
529 if (extension_host) {
530 agent_host_ = DevToolsAgentHost::GetOrCreateFor(
531 extension_host->render_view_host());
533 } else if (debuggee_.target_id) {
534 agent_host_ = DevToolsAgentHost::GetForId(*debuggee_.target_id);
536 error_ = keys::kInvalidTargetError;
540 if (!agent_host_.get()) {
541 FormatErrorMessage(keys::kNoTargetError);
547 bool DebuggerFunction::InitClientHost() {
548 if (!InitAgentHost())
551 client_host_ = AttachedClientHosts::GetInstance()->Lookup(
552 agent_host_.get(), GetExtension()->id());
555 FormatErrorMessage(keys::kNotAttachedError);
562 // DebuggerAttachFunction -----------------------------------------------------
564 DebuggerAttachFunction::DebuggerAttachFunction() {
567 DebuggerAttachFunction::~DebuggerAttachFunction() {
570 bool DebuggerAttachFunction::RunAsync() {
571 scoped_ptr<Attach::Params> params(Attach::Params::Create(*args_));
572 EXTENSION_FUNCTION_VALIDATE(params.get());
574 CopyDebuggee(&debuggee_, params->target);
575 if (!InitAgentHost())
578 if (!DevToolsHttpHandler::IsSupportedProtocolVersion(
579 params->required_version)) {
580 error_ = ErrorUtils::FormatErrorMessage(
581 keys::kProtocolVersionNotSupportedError,
582 params->required_version);
586 if (agent_host_->IsAttached()) {
587 FormatErrorMessage(keys::kAlreadyAttachedError);
591 infobars::InfoBar* infobar = NULL;
592 if (!CommandLine::ForCurrentProcess()->
593 HasSwitch(switches::kSilentDebuggerExtensionAPI)) {
594 // Do not attach to the target if for any reason the infobar cannot be shown
595 // for this WebContents instance.
596 infobar = ExtensionDevToolsInfoBarDelegate::Create(
597 agent_host_->GetRenderViewHost(), GetExtension()->name());
599 error_ = ErrorUtils::FormatErrorMessage(
600 keys::kSilentDebuggingRequired,
601 switches::kSilentDebuggerExtensionAPI);
606 new ExtensionDevToolsClientHost(GetProfile(),
608 GetExtension()->id(),
609 GetExtension()->name(),
617 // DebuggerDetachFunction -----------------------------------------------------
619 DebuggerDetachFunction::DebuggerDetachFunction() {
622 DebuggerDetachFunction::~DebuggerDetachFunction() {
625 bool DebuggerDetachFunction::RunAsync() {
626 scoped_ptr<Detach::Params> params(Detach::Params::Create(*args_));
627 EXTENSION_FUNCTION_VALIDATE(params.get());
629 CopyDebuggee(&debuggee_, params->target);
630 if (!InitClientHost())
633 client_host_->Close();
639 // DebuggerSendCommandFunction ------------------------------------------------
641 DebuggerSendCommandFunction::DebuggerSendCommandFunction() {
644 DebuggerSendCommandFunction::~DebuggerSendCommandFunction() {
647 bool DebuggerSendCommandFunction::RunAsync() {
648 scoped_ptr<SendCommand::Params> params(SendCommand::Params::Create(*args_));
649 EXTENSION_FUNCTION_VALIDATE(params.get());
651 CopyDebuggee(&debuggee_, params->target);
652 if (!InitClientHost())
655 client_host_->SendMessageToBackend(this, params->method,
656 params->command_params.get());
660 void DebuggerSendCommandFunction::SendResponseBody(
661 base::DictionaryValue* response) {
662 base::Value* error_body;
663 if (response->Get("error", &error_body)) {
664 base::JSONWriter::Write(error_body, &error_);
669 base::DictionaryValue* result_body;
670 SendCommand::Results::Result result;
671 if (response->GetDictionary("result", &result_body))
672 result.additional_properties.Swap(result_body);
674 results_ = SendCommand::Results::Create(result);
679 // DebuggerGetTargetsFunction -------------------------------------------------
683 const char kTargetIdField[] = "id";
684 const char kTargetTypeField[] = "type";
685 const char kTargetTitleField[] = "title";
686 const char kTargetAttachedField[] = "attached";
687 const char kTargetUrlField[] = "url";
688 const char kTargetFaviconUrlField[] = "faviconUrl";
689 const char kTargetTypePage[] = "page";
690 const char kTargetTypeBackgroundPage[] = "background_page";
691 const char kTargetTypeWorker[] = "worker";
692 const char kTargetTypeOther[] = "other";
693 const char kTargetTabIdField[] = "tabId";
694 const char kTargetExtensionIdField[] = "extensionId";
696 base::Value* SerializeTarget(const DevToolsTargetImpl& target) {
697 base::DictionaryValue* dictionary = new base::DictionaryValue();
699 dictionary->SetString(kTargetIdField, target.GetId());
700 dictionary->SetString(kTargetTitleField, target.GetTitle());
701 dictionary->SetBoolean(kTargetAttachedField, target.IsAttached());
702 dictionary->SetString(kTargetUrlField, target.GetURL().spec());
704 std::string type = target.GetType();
705 if (type == kTargetTypePage) {
706 dictionary->SetInteger(kTargetTabIdField, target.GetTabId());
707 } else if (type == kTargetTypeBackgroundPage) {
708 dictionary->SetString(kTargetExtensionIdField, target.GetExtensionId());
709 } else if (type != kTargetTypeWorker) {
710 // DevToolsTargetImpl may support more types than the debugger API.
711 type = kTargetTypeOther;
713 dictionary->SetString(kTargetTypeField, type);
715 GURL favicon_url = target.GetFaviconURL();
716 if (favicon_url.is_valid())
717 dictionary->SetString(kTargetFaviconUrlField, favicon_url.spec());
724 DebuggerGetTargetsFunction::DebuggerGetTargetsFunction() {
727 DebuggerGetTargetsFunction::~DebuggerGetTargetsFunction() {
730 bool DebuggerGetTargetsFunction::RunAsync() {
731 DevToolsTargetImpl::EnumerateAllTargets(
732 base::Bind(&DebuggerGetTargetsFunction::SendTargetList, this));
736 void DebuggerGetTargetsFunction::SendTargetList(
737 const std::vector<DevToolsTargetImpl*>& target_list) {
738 scoped_ptr<base::ListValue> result(new base::ListValue());
739 for (size_t i = 0; i < target_list.size(); ++i)
740 result->Append(SerializeTarget(*target_list[i]));
741 STLDeleteContainerPointers(target_list.begin(), target_list.end());
742 SetResult(result.release());
746 } // namespace extensions