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