inspect_ui.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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. 4 5#include "chrome/browser/ui/webui/inspect_ui.h" 6 7#include <set> 8 9#include "base/bind.h" 10#include "base/bind_helpers.h" 11#include "base/json/json_writer.h" 12#include "base/memory/ref_counted_memory.h" 13#include "base/strings/string_number_conversions.h" 14#include "base/strings/string_util.h" 15#include "base/strings/utf_string_conversions.h" 16#include "base/values.h" 17#include "chrome/browser/devtools/devtools_window.h" 18#include "chrome/browser/extensions/extension_service.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" 21#include "chrome/common/url_constants.h" 22#include "content/public/browser/browser_thread.h" 23#include "content/public/browser/child_process_data.h" 24#include "content/public/browser/devtools_agent_host.h" 25#include "content/public/browser/devtools_client_host.h" 26#include "content/public/browser/devtools_manager.h" 27#include "content/public/browser/favicon_status.h" 28#include "content/public/browser/navigation_entry.h" 29#include "content/public/browser/notification_service.h" 30#include "content/public/browser/notification_source.h" 31#include "content/public/browser/notification_types.h" 32#include "content/public/browser/render_process_host.h" 33#include "content/public/browser/render_view_host.h" 34#include "content/public/browser/web_contents.h" 35#include "content/public/browser/web_ui.h" 36#include "content/public/browser/web_ui_data_source.h" 37#include "content/public/browser/web_ui_message_handler.h" 38#include "content/public/browser/worker_service.h" 39#include "content/public/browser/worker_service_observer.h" 40#include "content/public/common/process_type.h" 41#include "grit/browser_resources.h" 42#include "grit/generated_resources.h" 43#include "net/base/escape.h" 44#include "net/base/net_errors.h" 45#include "ui/base/resource/resource_bundle.h" 46 47using content::BrowserThread; 48using content::ChildProcessData; 49using content::DevToolsAgentHost; 50using content::DevToolsClientHost; 51using content::DevToolsManager; 52using content::RenderProcessHost; 53using content::RenderViewHost; 54using content::RenderViewHostDelegate; 55using content::RenderWidgetHost; 56using content::WebContents; 57using content::WebUIMessageHandler; 58using content::WorkerService; 59using content::WorkerServiceObserver; 60 61namespace { 62 63static const char kDataFile[] = "targets-data.json"; 64static const char kAdbPages[] = "adb-pages"; 65 66static const char kExtensionTargetType[] = "extension"; 67static const char kPageTargetType[] = "page"; 68static const char kWorkerTargetType[] = "worker"; 69static const char kAdbTargetType[] = "adb_page"; 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[] = "faviconUrl"; 81static const char kPidField[] = "pid"; 82static const char kAdbSerialField[] = "adbSerial"; 83static const char kAdbModelField[] = "adbModel"; 84static const char kAdbPackageField[] = "adbPackage"; 85static const char kAdbSocketField[] = "adbSocket"; 86static const char kAdbDebugUrlField[] = "adbDebugUrl"; 87static const char kAdbFrontendUrlField[] = "adbFrontendUrl"; 88 89DictionaryValue* BuildTargetDescriptor( 90 const std::string& target_type, 91 bool attached, 92 const GURL& url, 93 const std::string& name, 94 const GURL& favicon_url, 95 int process_id, 96 int route_id, 97 base::ProcessHandle handle = base::kNullProcessHandle) { 98 DictionaryValue* target_data = new DictionaryValue(); 99 target_data->SetString(kTargetTypeField, target_type); 100 target_data->SetBoolean(kAttachedField, attached); 101 target_data->SetInteger(kProcessIdField, process_id); 102 target_data->SetInteger(kRouteIdField, route_id); 103 target_data->SetString(kUrlField, url.spec()); 104 target_data->SetString(kNameField, net::EscapeForHTML(name)); 105 target_data->SetInteger(kPidField, base::GetProcId(handle)); 106 target_data->SetString(kFaviconUrlField, favicon_url.spec()); 107 108 return target_data; 109} 110 111bool HasClientHost(RenderViewHost* rvh) { 112 if (!DevToolsAgentHost::HasFor(rvh)) 113 return false; 114 115 scoped_refptr<DevToolsAgentHost> agent( 116 DevToolsAgentHost::GetOrCreateFor(rvh)); 117 return agent->IsAttached(); 118} 119 120DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) { 121 WebContents* web_contents = WebContents::FromRenderViewHost(rvh); 122 std::string title; 123 std::string target_type = is_tab ? kPageTargetType : ""; 124 GURL url; 125 GURL favicon_url; 126 if (web_contents) { 127 url = web_contents->GetURL(); 128 title = UTF16ToUTF8(web_contents->GetTitle()); 129 content::NavigationController& controller = web_contents->GetController(); 130 content::NavigationEntry* entry = controller.GetActiveEntry(); 131 if (entry != NULL && entry->GetURL().is_valid()) 132 favicon_url = entry->GetFavicon().url; 133 134 Profile* profile = Profile::FromBrowserContext( 135 web_contents->GetBrowserContext()); 136 if (profile) { 137 ExtensionService* extension_service = profile->GetExtensionService(); 138 const extensions::Extension* extension = extension_service-> 139 extensions()->GetByID(url.host()); 140 if (extension) { 141 target_type = kExtensionTargetType; 142 title = extension->name(); 143 } 144 } 145 } 146 147 return BuildTargetDescriptor(target_type, 148 HasClientHost(rvh), 149 url, 150 title, 151 favicon_url, 152 rvh->GetProcess()->GetID(), 153 rvh->GetRoutingID()); 154} 155 156// Appends the inspectable workers to the list of RenderViews, and sends the 157// response back to the webui system. 158void SendDescriptors( 159 ListValue* rvh_list, 160 const content::WebUIDataSource::GotDataCallback& callback) { 161 std::vector<WorkerService::WorkerInfo> worker_info = 162 WorkerService::GetInstance()->GetWorkers(); 163 for (size_t i = 0; i < worker_info.size(); ++i) { 164 rvh_list->Append(BuildTargetDescriptor( 165 kWorkerTargetType, 166 false, 167 worker_info[i].url, 168 UTF16ToUTF8(worker_info[i].name), 169 GURL(), 170 worker_info[i].process_id, 171 worker_info[i].route_id, 172 worker_info[i].handle)); 173 } 174 175 std::string json_string; 176 base::JSONWriter::Write(rvh_list, &json_string); 177 callback.Run(base::RefCountedString::TakeString(&json_string)); 178} 179 180bool HandleDataRequestCallback( 181 const std::string& path, 182 const content::WebUIDataSource::GotDataCallback& callback) { 183 std::set<RenderViewHost*> tab_rvhs; 184 for (TabContentsIterator it; !it.done(); it.Next()) 185 tab_rvhs.insert(it->GetRenderViewHost()); 186 187 scoped_ptr<ListValue> rvh_list(new ListValue()); 188 189 std::vector<RenderViewHost*> rvh_vector = 190 DevToolsAgentHost::GetValidRenderViewHosts(); 191 192 for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin()); 193 it != rvh_vector.end(); it++) { 194 bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end(); 195 rvh_list->Append(BuildTargetDescriptor(*it, is_tab)); 196 } 197 198 BrowserThread::PostTask( 199 BrowserThread::IO, 200 FROM_HERE, 201 base::Bind(&SendDescriptors, base::Owned(rvh_list.release()), callback)); 202 return true; 203} 204 205class InspectMessageHandler : public WebUIMessageHandler { 206 public: 207 explicit InspectMessageHandler(DevToolsAdbBridge* adb_bridge) 208 : adb_bridge_(adb_bridge) {} 209 virtual ~InspectMessageHandler() {} 210 211 private: 212 // WebUIMessageHandler implementation. 213 virtual void RegisterMessages() OVERRIDE; 214 215 // Callback for "openDevTools" message. 216 void HandleInspectCommand(const ListValue* args); 217 void HandleTerminateCommand(const ListValue* args); 218 219 bool GetProcessAndRouteId(const ListValue* args, 220 int* process_id, 221 int* route_id); 222 223 DevToolsAdbBridge* adb_bridge_; 224 225 DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler); 226}; 227 228void InspectMessageHandler::RegisterMessages() { 229 web_ui()->RegisterMessageCallback(kInspectCommand, 230 base::Bind(&InspectMessageHandler::HandleInspectCommand, 231 base::Unretained(this))); 232 web_ui()->RegisterMessageCallback(kTerminateCommand, 233 base::Bind(&InspectMessageHandler::HandleTerminateCommand, 234 base::Unretained(this))); 235} 236 237void InspectMessageHandler::HandleInspectCommand(const ListValue* args) { 238 int process_id; 239 int route_id; 240 if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0 241 || route_id == 0) { 242 // Check for ADB serial 243 const DictionaryValue* data; 244 std::string serial; 245 std::string socket; 246 std::string debug_url; 247 std::string frontend_url; 248 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 249 data->GetString(kAdbSerialField, &serial) && 250 data->GetString(kAdbSocketField, &socket) && 251 data->GetString(kAdbDebugUrlField, &debug_url) && 252 data->GetString(kAdbFrontendUrlField, &frontend_url)) { 253 adb_bridge_->Attach(serial, socket, debug_url, frontend_url); 254 } 255 return; 256 } 257 258 RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); 259 if (rvh) { 260 DevToolsWindow::OpenDevToolsWindow(rvh); 261 return; 262 } 263 264 Profile* profile = Profile::FromWebUI(web_ui()); 265 if (!profile) 266 return; 267 scoped_refptr<DevToolsAgentHost> agent_host( 268 DevToolsAgentHost::GetForWorker(process_id, route_id)); 269 if (!agent_host.get()) 270 return; 271 272 DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get()); 273} 274 275static void TerminateWorker(int process_id, int route_id) { 276 WorkerService::GetInstance()->TerminateWorker(process_id, route_id); 277} 278 279void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) { 280 int process_id; 281 int route_id; 282 if (!GetProcessAndRouteId(args, &process_id, &route_id)) 283 return; 284 285 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 286 base::Bind(&TerminateWorker, process_id, route_id)); 287} 288 289bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args, 290 int* process_id, 291 int* route_id) { 292 const DictionaryValue* data; 293 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 294 data->GetInteger(kProcessIdField, process_id) && 295 data->GetInteger(kRouteIdField, route_id)) { 296 return true; 297 } 298 return false; 299} 300 301} // namespace 302 303class InspectUI::WorkerCreationDestructionListener 304 : public WorkerServiceObserver, 305 public base::RefCountedThreadSafe<WorkerCreationDestructionListener> { 306 public: 307 WorkerCreationDestructionListener() 308 : discovery_ui_(NULL) {} 309 310 void Init(InspectUI* workers_ui) { 311 DCHECK(workers_ui); 312 DCHECK(!discovery_ui_); 313 discovery_ui_ = workers_ui; 314 BrowserThread::PostTask( 315 BrowserThread::IO, FROM_HERE, 316 base::Bind(&WorkerCreationDestructionListener::RegisterObserver, 317 this)); 318 } 319 320 void InspectUIDestroyed() { 321 DCHECK(discovery_ui_); 322 discovery_ui_ = NULL; 323 BrowserThread::PostTask( 324 BrowserThread::IO, FROM_HERE, 325 base::Bind(&WorkerCreationDestructionListener::UnregisterObserver, 326 this)); 327 } 328 329 private: 330 friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>; 331 virtual ~WorkerCreationDestructionListener() {} 332 333 virtual void WorkerCreated( 334 const GURL& url, 335 const string16& name, 336 int process_id, 337 int route_id) OVERRIDE { 338 BrowserThread::PostTask( 339 BrowserThread::UI, FROM_HERE, 340 base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged, 341 this)); 342 } 343 344 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { 345 BrowserThread::PostTask( 346 BrowserThread::UI, FROM_HERE, 347 base::Bind(&WorkerCreationDestructionListener::NotifyItemsChanged, 348 this)); 349 } 350 351 void NotifyItemsChanged() { 352 if (discovery_ui_) 353 discovery_ui_->RefreshUI(); 354 } 355 356 void RegisterObserver() { 357 WorkerService::GetInstance()->AddObserver(this); 358 } 359 360 void UnregisterObserver() { 361 WorkerService::GetInstance()->RemoveObserver(this); 362 } 363 364 InspectUI* discovery_ui_; 365}; 366 367InspectUI::InspectUI(content::WebUI* web_ui) 368 : WebUIController(web_ui), 369 observer_(new WorkerCreationDestructionListener()), 370 weak_factory_(this) { 371 observer_->Init(this); 372 Profile* profile = Profile::FromWebUI(web_ui); 373 adb_bridge_ = DevToolsAdbBridge::Factory::GetForProfile(profile); 374 web_ui->AddMessageHandler(new InspectMessageHandler(adb_bridge_)); 375 content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource()); 376 377 registrar_.Add(this, 378 content::NOTIFICATION_WEB_CONTENTS_CONNECTED, 379 content::NotificationService::AllSources()); 380 registrar_.Add(this, 381 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 382 content::NotificationService::AllSources()); 383 registrar_.Add(this, 384 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 385 content::NotificationService::AllSources()); 386} 387 388InspectUI::~InspectUI() { 389 StopListeningNotifications(); 390} 391 392void InspectUI::RefreshUI() { 393 web_ui()->CallJavascriptFunction("populateLists"); 394} 395 396// static 397bool InspectUI::WeakHandleRequestCallback( 398 const base::WeakPtr<InspectUI>& inspect_ui, 399 const std::string& path, 400 const content::WebUIDataSource::GotDataCallback& callback) { 401 if (!inspect_ui.get()) 402 return false; 403 return inspect_ui->HandleRequestCallback(path, callback); 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_.get()) 418 return; 419 adb_bridge_ = NULL; 420 observer_->InspectUIDestroyed(); 421 observer_ = NULL; 422 registrar_.RemoveAll(); 423} 424 425content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() { 426 content::WebUIDataSource* source = 427 content::WebUIDataSource::Create(chrome::kChromeUIInspectHost); 428 source->AddResourcePath("inspect.css", IDR_INSPECT_CSS); 429 source->AddResourcePath("inspect.js", IDR_INSPECT_JS); 430 source->SetDefaultResource(IDR_INSPECT_HTML); 431 source->SetRequestFilter(base::Bind(&InspectUI::WeakHandleRequestCallback, 432 weak_factory_.GetWeakPtr())); 433 return source; 434} 435 436bool InspectUI::HandleRequestCallback( 437 const std::string& path, 438 const content::WebUIDataSource::GotDataCallback& callback) { 439 if (path == kDataFile) 440 return HandleDataRequestCallback(path, callback); 441 if (path.find(kAdbPages) == 0) 442 return HandleAdbPagesCallback(path, callback); 443 return false; 444} 445 446bool InspectUI::HandleAdbPagesCallback( 447 const std::string& path, 448 const content::WebUIDataSource::GotDataCallback& callback) { 449 adb_bridge_->Pages(base::Bind(&InspectUI::OnAdbPages, 450 weak_factory_.GetWeakPtr(), 451 callback)); 452 return true; 453} 454 455void InspectUI::OnAdbPages( 456 const content::WebUIDataSource::GotDataCallback& callback, 457 int result, 458 DevToolsAdbBridge::RemotePages* pages) { 459 if (result != net::OK) 460 return; 461 ListValue targets; 462 scoped_ptr<DevToolsAdbBridge::RemotePages> my_pages(pages); 463 for (DevToolsAdbBridge::RemotePages::iterator it = my_pages->begin(); 464 it != my_pages->end(); ++it) { 465 DevToolsAdbBridge::RemotePage* page = it->get(); 466 DictionaryValue* target_data = BuildTargetDescriptor(kAdbTargetType, 467 false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 0, 468 0); 469 target_data->SetString(kAdbSerialField, page->serial()); 470 target_data->SetString(kAdbModelField, page->model()); 471 target_data->SetString(kAdbPackageField, page->package()); 472 target_data->SetString(kAdbSocketField, page->socket()); 473 target_data->SetString(kAdbDebugUrlField, page->debug_url()); 474 target_data->SetString(kAdbFrontendUrlField, page->frontend_url()); 475 targets.Append(target_data); 476 } 477 478 std::string json_string; 479 base::JSONWriter::Write(&targets, &json_string); 480 callback.Run(base::RefCountedString::TakeString(&json_string)); 481} 482