inspect_ui.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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 kAppTargetType[] = "app"; 67static const char kExtensionTargetType[] = "extension"; 68static const char kPageTargetType[] = "page"; 69static const char kWorkerTargetType[] = "worker"; 70static const char kAdbTargetType[] = "adb_page"; 71 72static const char kInitUICommand[] = "init-ui"; 73static const char kInspectCommand[] = "inspect"; 74static const char kTerminateCommand[] = "terminate"; 75 76static const char kTargetTypeField[] = "type"; 77static const char kAttachedField[] = "attached"; 78static const char kProcessIdField[] = "processId"; 79static const char kRouteIdField[] = "routeId"; 80static const char kUrlField[] = "url"; 81static const char kNameField[] = "name"; 82static const char kFaviconUrlField[] = "faviconUrl"; 83static const char kPidField[] = "pid"; 84static const char kAdbSerialField[] = "adbSerial"; 85static const char kAdbModelField[] = "adbModel"; 86static const char kAdbBrowserNameField[] = "adbBrowserName"; 87static const char kAdbPageIdField[] = "adbPageId"; 88static const char kAdbBrowsersField[] = "browsers"; 89static const char kAdbPagesField[] = "pages"; 90 91DictionaryValue* BuildTargetDescriptor( 92 const std::string& target_type, 93 bool attached, 94 const GURL& url, 95 const std::string& name, 96 const GURL& favicon_url, 97 int process_id, 98 int route_id, 99 base::ProcessHandle handle = base::kNullProcessHandle) { 100 DictionaryValue* target_data = new DictionaryValue(); 101 target_data->SetString(kTargetTypeField, target_type); 102 target_data->SetBoolean(kAttachedField, attached); 103 target_data->SetInteger(kProcessIdField, process_id); 104 target_data->SetInteger(kRouteIdField, route_id); 105 target_data->SetString(kUrlField, url.spec()); 106 target_data->SetString(kNameField, net::EscapeForHTML(name)); 107 target_data->SetInteger(kPidField, base::GetProcId(handle)); 108 target_data->SetString(kFaviconUrlField, favicon_url.spec()); 109 110 return target_data; 111} 112 113bool HasClientHost(RenderViewHost* rvh) { 114 if (!DevToolsAgentHost::HasFor(rvh)) 115 return false; 116 117 scoped_refptr<DevToolsAgentHost> agent( 118 DevToolsAgentHost::GetOrCreateFor(rvh)); 119 return agent->IsAttached(); 120} 121 122DictionaryValue* BuildTargetDescriptor(RenderViewHost* rvh, bool is_tab) { 123 WebContents* web_contents = WebContents::FromRenderViewHost(rvh); 124 std::string title; 125 std::string target_type = is_tab ? kPageTargetType : ""; 126 GURL url; 127 GURL favicon_url; 128 if (web_contents) { 129 url = web_contents->GetURL(); 130 title = UTF16ToUTF8(web_contents->GetTitle()); 131 content::NavigationController& controller = web_contents->GetController(); 132 content::NavigationEntry* entry = controller.GetActiveEntry(); 133 if (entry != NULL && entry->GetURL().is_valid()) 134 favicon_url = entry->GetFavicon().url; 135 136 Profile* profile = Profile::FromBrowserContext( 137 web_contents->GetBrowserContext()); 138 if (profile) { 139 ExtensionService* extension_service = profile->GetExtensionService(); 140 const extensions::Extension* extension = extension_service-> 141 extensions()->GetByID(url.host()); 142 if (extension) { 143 if (extension->is_hosted_app() 144 || extension->is_legacy_packaged_app() 145 || extension->is_platform_app()) 146 target_type = kAppTargetType; 147 else 148 target_type = kExtensionTargetType; 149 title = extension->name(); 150 } 151 } 152 } 153 154 return BuildTargetDescriptor(target_type, 155 HasClientHost(rvh), 156 url, 157 title, 158 favicon_url, 159 rvh->GetProcess()->GetID(), 160 rvh->GetRoutingID()); 161} 162 163class InspectMessageHandler : public WebUIMessageHandler { 164 public: 165 explicit InspectMessageHandler(InspectUI* inspect_ui) 166 : inspect_ui_(inspect_ui) {} 167 virtual ~InspectMessageHandler() {} 168 169 private: 170 // WebUIMessageHandler implementation. 171 virtual void RegisterMessages() OVERRIDE; 172 173 void HandleInitUICommand(const ListValue* args); 174 void HandleInspectCommand(const ListValue* args); 175 void HandleTerminateCommand(const ListValue* args); 176 177 bool GetProcessAndRouteId(const ListValue* args, 178 int* process_id, 179 int* route_id); 180 181 InspectUI* inspect_ui_; 182 183 DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler); 184}; 185 186void InspectMessageHandler::RegisterMessages() { 187 web_ui()->RegisterMessageCallback(kInitUICommand, 188 base::Bind(&InspectMessageHandler::HandleInitUICommand, 189 base::Unretained(this))); 190 web_ui()->RegisterMessageCallback(kInspectCommand, 191 base::Bind(&InspectMessageHandler::HandleInspectCommand, 192 base::Unretained(this))); 193 web_ui()->RegisterMessageCallback(kTerminateCommand, 194 base::Bind(&InspectMessageHandler::HandleTerminateCommand, 195 base::Unretained(this))); 196} 197 198void InspectMessageHandler::HandleInitUICommand(const ListValue*) { 199 inspect_ui_->InitUI(); 200} 201 202void InspectMessageHandler::HandleInspectCommand(const ListValue* args) { 203 Profile* profile = Profile::FromWebUI(web_ui()); 204 if (!profile) 205 return; 206 207 int process_id; 208 int route_id; 209 if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0 210 || route_id == 0) { 211 // Check for ADB page id 212 const DictionaryValue* data; 213 std::string page_id; 214 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 215 data->GetString(kAdbPageIdField, &page_id)) { 216 inspect_ui_->InspectRemotePage(page_id); 217 } 218 return; 219 } 220 221 RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); 222 if (rvh) { 223 DevToolsWindow::OpenDevToolsWindow(rvh); 224 return; 225 } 226 227 scoped_refptr<DevToolsAgentHost> agent_host( 228 DevToolsAgentHost::GetForWorker(process_id, route_id)); 229 if (!agent_host.get()) 230 return; 231 232 DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get()); 233} 234 235static void TerminateWorker(int process_id, int route_id) { 236 WorkerService::GetInstance()->TerminateWorker(process_id, route_id); 237} 238 239void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) { 240 int process_id; 241 int route_id; 242 if (!GetProcessAndRouteId(args, &process_id, &route_id)) 243 return; 244 245 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 246 base::Bind(&TerminateWorker, process_id, route_id)); 247} 248 249bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args, 250 int* process_id, 251 int* route_id) { 252 const DictionaryValue* data; 253 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 254 data->GetInteger(kProcessIdField, process_id) && 255 data->GetInteger(kRouteIdField, route_id)) { 256 return true; 257 } 258 return false; 259} 260 261} // namespace 262 263class InspectUI::WorkerCreationDestructionListener 264 : public WorkerServiceObserver, 265 public base::RefCountedThreadSafe<WorkerCreationDestructionListener> { 266 public: 267 WorkerCreationDestructionListener() 268 : discovery_ui_(NULL) {} 269 270 void Init(InspectUI* workers_ui) { 271 DCHECK(workers_ui); 272 DCHECK(!discovery_ui_); 273 discovery_ui_ = workers_ui; 274 BrowserThread::PostTask( 275 BrowserThread::IO, FROM_HERE, 276 base::Bind(&WorkerCreationDestructionListener::RegisterObserver, 277 this)); 278 } 279 280 void InspectUIDestroyed() { 281 DCHECK(discovery_ui_); 282 discovery_ui_ = NULL; 283 BrowserThread::PostTask( 284 BrowserThread::IO, FROM_HERE, 285 base::Bind(&WorkerCreationDestructionListener::UnregisterObserver, 286 this)); 287 } 288 289 void InitUI() { 290 BrowserThread::PostTask( 291 BrowserThread::IO, FROM_HERE, 292 base::Bind(&WorkerCreationDestructionListener::CollectWorkersData, 293 this)); 294 } 295 296 private: 297 friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>; 298 virtual ~WorkerCreationDestructionListener() {} 299 300 virtual void WorkerCreated( 301 const GURL& url, 302 const string16& name, 303 int process_id, 304 int route_id) OVERRIDE { 305 CollectWorkersData(); 306 } 307 308 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { 309 CollectWorkersData(); 310 } 311 312 void CollectWorkersData() { 313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 314 scoped_ptr<ListValue> target_list(new ListValue()); 315 std::vector<WorkerService::WorkerInfo> worker_info = 316 WorkerService::GetInstance()->GetWorkers(); 317 for (size_t i = 0; i < worker_info.size(); ++i) { 318 target_list->Append(BuildTargetDescriptor( 319 kWorkerTargetType, 320 false, 321 worker_info[i].url, 322 UTF16ToUTF8(worker_info[i].name), 323 GURL(), 324 worker_info[i].process_id, 325 worker_info[i].route_id, 326 worker_info[i].handle)); 327 } 328 329 BrowserThread::PostTask( 330 BrowserThread::UI, FROM_HERE, 331 base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList, 332 this, base::Owned(target_list.release()))); 333 } 334 335 void RegisterObserver() { 336 WorkerService::GetInstance()->AddObserver(this); 337 } 338 339 void UnregisterObserver() { 340 WorkerService::GetInstance()->RemoveObserver(this); 341 } 342 343 void PopulateWorkersList(ListValue* target_list) { 344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 if (discovery_ui_) { 346 discovery_ui_->web_ui()->CallJavascriptFunction( 347 "populateWorkersList", *target_list); 348 } 349 } 350 351 InspectUI* discovery_ui_; 352}; 353 354InspectUI::InspectUI(content::WebUI* web_ui) 355 : WebUIController(web_ui) { 356 web_ui->AddMessageHandler(new InspectMessageHandler(this)); 357 Profile* profile = Profile::FromWebUI(web_ui); 358 content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource()); 359} 360 361InspectUI::~InspectUI() { 362 StopListeningNotifications(); 363} 364 365void InspectUI::InitUI() { 366 StartListeningNotifications(); 367 PopulateLists(); 368 observer_->InitUI(); 369} 370 371void InspectUI::InspectRemotePage(const std::string& id) { 372 RemotePages::iterator it = remote_pages_.find(id); 373 if (it != remote_pages_.end()) { 374 Profile* profile = Profile::FromWebUI(web_ui()); 375 it->second->Inspect(profile); 376 } 377} 378 379void InspectUI::PopulateLists() { 380 std::set<RenderViewHost*> tab_rvhs; 381 for (TabContentsIterator it; !it.done(); it.Next()) 382 tab_rvhs.insert(it->GetRenderViewHost()); 383 384 scoped_ptr<ListValue> target_list(new ListValue()); 385 386 std::vector<RenderViewHost*> rvh_vector = 387 DevToolsAgentHost::GetValidRenderViewHosts(); 388 389 for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin()); 390 it != rvh_vector.end(); it++) { 391 bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end(); 392 target_list->Append(BuildTargetDescriptor(*it, is_tab)); 393 } 394 web_ui()->CallJavascriptFunction("populateLists", *target_list.get()); 395} 396 397void InspectUI::Observe(int type, 398 const content::NotificationSource& source, 399 const content::NotificationDetails& details) { 400 if (source != content::Source<WebContents>(web_ui()->GetWebContents())) 401 PopulateLists(); 402 else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED) 403 StopListeningNotifications(); 404} 405 406void InspectUI::StartListeningNotifications() { 407 if (observer_) 408 return; 409 410 observer_ = new WorkerCreationDestructionListener(); 411 observer_->Init(this); 412 413 Profile* profile = Profile::FromWebUI(web_ui()); 414 DevToolsAdbBridge* adb_bridge = 415 DevToolsAdbBridge::Factory::GetForProfile(profile); 416 if (adb_bridge) 417 adb_bridge->AddListener(this); 418 419 registrar_.Add(this, 420 content::NOTIFICATION_WEB_CONTENTS_CONNECTED, 421 content::NotificationService::AllSources()); 422 registrar_.Add(this, 423 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 424 content::NotificationService::AllSources()); 425 registrar_.Add(this, 426 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 427 content::NotificationService::AllSources()); 428} 429 430void InspectUI::StopListeningNotifications() 431{ 432 if (!observer_.get()) 433 return; 434 Profile* profile = Profile::FromWebUI(web_ui()); 435 DevToolsAdbBridge* adb_bridge = 436 DevToolsAdbBridge::Factory::GetForProfile(profile); 437 if (adb_bridge) 438 adb_bridge->RemoveListener(this); 439 observer_->InspectUIDestroyed(); 440 observer_ = NULL; 441 registrar_.RemoveAll(); 442} 443 444content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() { 445 content::WebUIDataSource* source = 446 content::WebUIDataSource::Create(chrome::kChromeUIInspectHost); 447 source->AddResourcePath("inspect.css", IDR_INSPECT_CSS); 448 source->AddResourcePath("inspect.js", IDR_INSPECT_JS); 449 source->SetDefaultResource(IDR_INSPECT_HTML); 450 return source; 451} 452 453void InspectUI::RemoteDevicesChanged( 454 DevToolsAdbBridge::RemoteDevices* devices) { 455 remote_pages_.clear(); 456 ListValue device_list; 457 for (DevToolsAdbBridge::RemoteDevices::iterator dit = devices->begin(); 458 dit != devices->end(); ++dit) { 459 DevToolsAdbBridge::RemoteDevice& device = *(dit->get()); 460 DictionaryValue* device_data = new DictionaryValue(); 461 device_data->SetString(kAdbModelField, device.model()); 462 device_data->SetString(kAdbSerialField, device.serial()); 463 ListValue* browser_list = new ListValue(); 464 device_data->Set(kAdbBrowsersField, browser_list); 465 466 DevToolsAdbBridge::RemoteBrowsers& browsers = device.browsers(); 467 for (DevToolsAdbBridge::RemoteBrowsers::iterator bit = 468 browsers.begin(); bit != browsers.end(); ++bit) { 469 DevToolsAdbBridge::RemoteBrowser& browser = *(bit->get()); 470 DictionaryValue* browser_data = new DictionaryValue(); 471 browser_data->SetString(kAdbBrowserNameField, browser.name()); 472 ListValue* page_list = new ListValue(); 473 browser_data->Set(kAdbPagesField, page_list); 474 475 DevToolsAdbBridge::RemotePages& pages = browser.pages(); 476 for (DevToolsAdbBridge::RemotePages::iterator it = 477 pages.begin(); it != pages.end(); ++it) { 478 DevToolsAdbBridge::RemotePage* page = it->get(); 479 DictionaryValue* page_data = BuildTargetDescriptor(kAdbTargetType, 480 false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 481 0, 0); 482 page_data->SetString(kAdbPageIdField, page->global_id()); 483 page_list->Append(page_data); 484 remote_pages_[page->global_id()] = page; 485 } 486 browser_list->Append(browser_data); 487 } 488 device_list.Append(device_data); 489 } 490 web_ui()->CallJavascriptFunction("populateDeviceLists", device_list); 491} 492