inspect_ui.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)// found in the LICENSE file. 45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/inspect_ui.h" 65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include <set> 85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) 95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/bind.h" 105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/bind_helpers.h" 115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/json/json_writer.h" 125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/memory/ref_counted_memory.h" 135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/string_number_conversions.h" 145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/string_util.h" 155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/utf_string_conversions.h" 165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "base/values.h" 175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/debugger/devtools_window.h" 185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/extensions/extension_service.h" 195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/profiles/profile.h" 205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/tab_contents/tab_contents.h" 215c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 225c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/chrome_url_data_manager.h" 235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/browser/ui/webui/chrome_url_data_manager_backend.h" 2453e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#include "chrome/browser/ui/webui/chrome_web_ui_data_source.h" 255c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "chrome/common/url_constants.h" 26c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)#include "content/public/browser/browser_thread.h" 275c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/child_process_data.h" 2853e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)#include "content/public/browser/devtools_agent_host_registry.h" 295c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/devtools_client_host.h" 30d5428f32f5d1719f774f62e19147104ca245a3abTorne (Richard Coles)#include "content/public/browser/devtools_manager.h" 315c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/favicon_status.h" 325c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/navigation_entry.h" 335c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_service.h" 345c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_source.h" 355c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/notification_types.h" 3609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/render_process_host.h" 3709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/render_view_host.h" 385c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/render_widget_host.h" 395c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/web_contents.h" 405c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/web_ui.h" 4109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/web_ui_message_handler.h" 425c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/browser/worker_service.h" 4309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#include "content/public/browser/worker_service_observer.h" 445c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "content/public/common/process_type.h" 455c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "grit/browser_resources.h" 465c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "grit/generated_resources.h" 475c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "net/base/escape.h" 485c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h" 49a854de003a23bf3c7f95ec0f8154ada64092ff5cTorne (Richard Coles) 500019e4eead4d990e4304c54a9028aca9122fb256Ben Murdochusing content::BrowserThread; 51c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles)using content::ChildProcessData; 525c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)using content::DevToolsAgentHost; 535c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)using content::DevToolsAgentHostRegistry; 54using content::DevToolsClientHost; 55using content::DevToolsManager; 56using content::RenderProcessHost; 57using content::RenderViewHost; 58using content::RenderViewHostDelegate; 59using content::RenderWidgetHost; 60using content::WebContents; 61using content::WebUIMessageHandler; 62using content::WorkerService; 63using content::WorkerServiceObserver; 64 65static const char kDataFile[] = "targets-data.json"; 66 67static const char kExtensionTargetType[] = "extension"; 68static const char kPageTargetType[] = "page"; 69static const char kWorkerTargetType[] = "worker"; 70 71static const char kInspectCommand[] = "inspect"; 72static const char kTerminateCommand[] = "terminate"; 73 74static const char kTargetTypeField[] = "type"; 75static const char kAttachedField[] = "attached"; 76static const char kProcessIdField[] = "processId"; 77static const char kRouteIdField[] = "routeId"; 78static const char kUrlField[] = "url"; 79static const char kNameField[] = "name"; 80static const char kFaviconUrlField[] = "favicon_url"; 81static const char kPidField[] = "pid"; 82 83namespace { 84 85DictionaryValue* BuildTargetDescriptor( 86 const std::string& target_type, 87 bool attached, 88 const GURL& url, 89 const std::string& name, 90 const GURL& favicon_url, 91 int process_id, 92 int route_id, 93 base::ProcessHandle handle = base::kNullProcessHandle) { 94 DictionaryValue* target_data = new DictionaryValue(); 95 target_data->SetString(kTargetTypeField, target_type); 96 target_data->SetBoolean(kAttachedField, attached); 97 target_data->SetInteger(kProcessIdField, process_id); 98 target_data->SetInteger(kRouteIdField, route_id); 99 target_data->SetString(kUrlField, url.spec()); 100 target_data->SetString(kNameField, net::EscapeForHTML(name)); 101 target_data->SetInteger(kPidField, base::GetProcId(handle)); 102 target_data->SetString(kFaviconUrlField, favicon_url.spec()); 103 104 return target_data; 105} 106 107bool HasClientHost(RenderViewHost* rvh) { 108 if (!DevToolsAgentHostRegistry::HasDevToolsAgentHost(rvh)) 109 return false; 110 111 DevToolsAgentHost* agent = 112 DevToolsAgentHostRegistry::GetDevToolsAgentHost(rvh); 113 return !!DevToolsManager::GetInstance()->GetDevToolsClientHostFor(agent); 114} 115 116DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) { 117 WebContents* web_contents = WebContents::FromRenderViewHost(rvh); 118 std::string title; 119 std::string target_type = is_tab ? kPageTargetType : ""; 120 GURL url; 121 GURL favicon_url; 122 if (web_contents) { 123 url = web_contents->GetURL(); 124 title = UTF16ToUTF8(web_contents->GetTitle()); 125 content::NavigationController& controller = web_contents->GetController(); 126 content::NavigationEntry* entry = controller.GetActiveEntry(); 127 if (entry != NULL && entry->GetURL().is_valid()) 128 favicon_url = entry->GetFavicon().url; 129 130 Profile* profile = Profile::FromBrowserContext( 131 web_contents->GetBrowserContext()); 132 if (profile) { 133 ExtensionService* extension_service = profile->GetExtensionService(); 134 const extensions::Extension* extension = extension_service-> 135 extensions()->GetByID(url.host()); 136 if (extension) { 137 target_type = kExtensionTargetType; 138 title = extension->name(); 139 } 140 } 141 } 142 143 return BuildTargetDescriptor(target_type, 144 HasClientHost(rvh), 145 url, 146 title, 147 favicon_url, 148 rvh->GetProcess()->GetID(), 149 rvh->GetRoutingID()); 150} 151 152class InspectDataSource : public ChromeWebUIDataSource { 153 public: 154 InspectDataSource(); 155 156 virtual void StartDataRequest(const std::string& path, 157 bool is_incognito, 158 int request_id); 159 private: 160 virtual ~InspectDataSource() {} 161 void SendDescriptors(int request_id, ListValue* rvh_list); 162 DISALLOW_COPY_AND_ASSIGN(InspectDataSource); 163}; 164 165InspectDataSource::InspectDataSource() 166 : ChromeWebUIDataSource(chrome::kChromeUIInspectHost, 167 MessageLoop::current()) { 168 add_resource_path("inspect.css", IDR_INSPECT_CSS); 169 add_resource_path("inspect.js", IDR_INSPECT_JS); 170 set_default_resource(IDR_INSPECT_HTML); 171} 172 173void InspectDataSource::StartDataRequest(const std::string& path, 174 bool is_incognito, 175 int request_id) { 176 if (path != kDataFile) { 177 ChromeWebUIDataSource::StartDataRequest(path, is_incognito, request_id); 178 return; 179 } 180 181 std::set<RenderViewHost*> tab_rvhs; 182 for (TabContentsIterator it; !it.done(); ++it) 183 tab_rvhs.insert(it->web_contents()->GetRenderViewHost()); 184 185 scoped_ptr<ListValue> rvh_list(new ListValue()); 186 187 for (RenderProcessHost::iterator it(RenderProcessHost::AllHostsIterator()); 188 !it.IsAtEnd(); it.Advance()) { 189 RenderProcessHost* render_process_host = it.GetCurrentValue(); 190 DCHECK(render_process_host); 191 192 // Ignore processes that don't have a connection, such as crashed tabs. 193 if (!render_process_host->HasConnection()) 194 continue; 195 196 RenderProcessHost::RenderWidgetHostsIterator rwit( 197 render_process_host->GetRenderWidgetHostsIterator()); 198 for (; !rwit.IsAtEnd(); rwit.Advance()) { 199 const RenderWidgetHost* widget = rwit.GetCurrentValue(); 200 DCHECK(widget); 201 if (!widget || !widget->IsRenderView()) 202 continue; 203 204 RenderViewHost* rvh = 205 RenderViewHost::From(const_cast<RenderWidgetHost*>(widget)); 206 207 bool is_tab = tab_rvhs.find(rvh) != tab_rvhs.end(); 208 rvh_list->Append(BuildTargetDescriptor(rvh, is_tab)); 209 } 210 } 211 212 BrowserThread::PostTask( 213 BrowserThread::IO, 214 FROM_HERE, 215 base::Bind(&InspectDataSource::SendDescriptors, 216 this, 217 request_id, 218 base::Owned(rvh_list.release()))); 219} 220 221void InspectDataSource::SendDescriptors(int request_id, 222 ListValue* rvh_list) { 223 std::vector<WorkerService::WorkerInfo> worker_info = 224 WorkerService::GetInstance()->GetWorkers(); 225 for (size_t i = 0; i < worker_info.size(); ++i) { 226 rvh_list->Append(BuildTargetDescriptor( 227 kWorkerTargetType, 228 false, 229 worker_info[i].url, 230 UTF16ToUTF8(worker_info[i].name), 231 GURL(), 232 worker_info[i].process_id, 233 worker_info[i].route_id, 234 worker_info[i].handle)); 235 } 236 237 std::string json_string; 238 base::JSONWriter::Write(rvh_list, &json_string); 239 240 SendResponse(request_id, base::RefCountedString::TakeString(&json_string)); 241} 242 243class InspectMessageHandler : public WebUIMessageHandler { 244 public: 245 InspectMessageHandler() {} 246 virtual ~InspectMessageHandler() {} 247 248 private: 249 // WebUIMessageHandler implementation. 250 virtual void RegisterMessages() OVERRIDE; 251 252 // Callback for "openDevTools" message. 253 void HandleInspectCommand(const ListValue* args); 254 void HandleTerminateCommand(const ListValue* args); 255 256 DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler); 257}; 258 259void InspectMessageHandler::RegisterMessages() { 260 web_ui()->RegisterMessageCallback(kInspectCommand, 261 base::Bind(&InspectMessageHandler::HandleInspectCommand, 262 base::Unretained(this))); 263 web_ui()->RegisterMessageCallback(kTerminateCommand, 264 base::Bind(&InspectMessageHandler::HandleTerminateCommand, 265 base::Unretained(this))); 266} 267 268void InspectMessageHandler::HandleInspectCommand(const ListValue* args) { 269 std::string process_id_str; 270 std::string route_id_str; 271 int process_id; 272 int route_id; 273 CHECK(args->GetSize() == 2); 274 CHECK(args->GetString(0, &process_id_str)); 275 CHECK(args->GetString(1, &route_id_str)); 276 CHECK(base::StringToInt(process_id_str, 277 &process_id)); 278 CHECK(base::StringToInt(route_id_str, &route_id)); 279 280 Profile* profile = Profile::FromWebUI(web_ui()); 281 if (!profile) 282 return; 283 284 RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); 285 if (rvh) { 286 DevToolsWindow::OpenDevToolsWindow(rvh); 287 return; 288 } 289 290 DevToolsAgentHost* agent_host = 291 DevToolsAgentHostRegistry::GetDevToolsAgentHostForWorker(process_id, 292 route_id); 293 if (agent_host) 294 DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host); 295} 296 297static void TerminateWorker(int process_id, int route_id) { 298 WorkerService::GetInstance()->TerminateWorker(process_id, route_id); 299} 300 301void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) { 302 std::string process_id_str; 303 std::string route_id_str; 304 int process_id; 305 int route_id; 306 CHECK(args->GetSize() == 2); 307 CHECK(args->GetString(0, &process_id_str)); 308 CHECK(args->GetString(1, &route_id_str)); 309 CHECK(base::StringToInt(process_id_str, 310 &process_id)); 311 CHECK(base::StringToInt(route_id_str, &route_id)); 312 313 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 314 base::Bind(&TerminateWorker, process_id, route_id)); 315} 316 317} // namespace 318 319class InspectUI::WorkerCreationDestructionListener 320 : public WorkerServiceObserver, 321 public base::RefCountedThreadSafe<WorkerCreationDestructionListener> { 322 public: 323 explicit WorkerCreationDestructionListener(InspectUI* workers_ui) 324 : discovery_ui_(workers_ui) { 325 BrowserThread::PostTask( 326 BrowserThread::IO, FROM_HERE, 327 base::Bind(&WorkerCreationDestructionListener::RegisterObserver, 328 this)); 329 } 330 331 void InspectUIDestroyed() { 332 discovery_ui_ = NULL; 333 BrowserThread::PostTask( 334 BrowserThread::IO, FROM_HERE, 335 base::Bind(&WorkerCreationDestructionListener::UnregisterObserver, 336 this)); 337 } 338 339 private: 340 friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>; 341 virtual ~WorkerCreationDestructionListener() {} 342 343 virtual void WorkerCreated( 344 const GURL& url, 345 const string16& name, 346 int process_id, 347 int route_id) OVERRIDE { 348 BrowserThread::PostTask( 349 BrowserThread::UI, FROM_HERE, 350 base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged, 351 this)); 352 } 353 354 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { 355 BrowserThread::PostTask( 356 BrowserThread::UI, FROM_HERE, 357 base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged, 358 this)); 359 } 360 361 void NotifyItemsChanged() { 362 if (discovery_ui_) 363 discovery_ui_->RefreshUI(); 364 } 365 366 void RegisterObserver() { 367 WorkerService::GetInstance()->AddObserver(this); 368 } 369 370 void UnregisterObserver() { 371 WorkerService::GetInstance()->RemoveObserver(this); 372 } 373 374 InspectUI* discovery_ui_; 375}; 376 377InspectUI::InspectUI(content::WebUI* web_ui) 378 : WebUIController(web_ui), 379 observer_(new WorkerCreationDestructionListener(this)) { 380 web_ui->AddMessageHandler(new InspectMessageHandler()); 381 382 InspectDataSource* html_source = new InspectDataSource(); 383 384 Profile* profile = Profile::FromWebUI(web_ui); 385 ChromeURLDataManager::AddDataSource(profile, html_source); 386 387 registrar_.Add(this, 388 content::NOTIFICATION_WEB_CONTENTS_CONNECTED, 389 content::NotificationService::AllSources()); 390 registrar_.Add(this, 391 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 392 content::NotificationService::AllSources()); 393 registrar_.Add(this, 394 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 395 content::NotificationService::AllSources()); 396} 397 398InspectUI::~InspectUI() { 399 StopListeningNotifications(); 400} 401 402void InspectUI::RefreshUI() { 403 web_ui()->CallJavascriptFunction("populateLists"); 404} 405 406void InspectUI::Observe(int type, 407 const content::NotificationSource& source, 408 const content::NotificationDetails& details) { 409 if (source != content::Source<WebContents>(web_ui()->GetWebContents())) 410 RefreshUI(); 411 else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED) 412 StopListeningNotifications(); 413} 414 415void InspectUI::StopListeningNotifications() 416{ 417 if (!observer_) 418 return; 419 observer_->InspectUIDestroyed(); 420 observer_ = NULL; 421 registrar_.RemoveAll(); 422} 423