inspect_ui.cc revision 558790d6acca3451cf3a6b497803a5f07d0bec58
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 kAdbPackageField[] = "adbPackage"; 87static const char kAdbPageIdField[] = "adbPageId"; 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 if (extension->is_hosted_app() 142 || extension->is_legacy_packaged_app() 143 || extension->is_platform_app()) 144 target_type = kAppTargetType; 145 else 146 target_type = kExtensionTargetType; 147 title = extension->name(); 148 } 149 } 150 } 151 152 return BuildTargetDescriptor(target_type, 153 HasClientHost(rvh), 154 url, 155 title, 156 favicon_url, 157 rvh->GetProcess()->GetID(), 158 rvh->GetRoutingID()); 159} 160 161class InspectMessageHandler : public WebUIMessageHandler { 162 public: 163 explicit InspectMessageHandler(InspectUI* inspect_ui) 164 : inspect_ui_(inspect_ui) {} 165 virtual ~InspectMessageHandler() {} 166 167 private: 168 // WebUIMessageHandler implementation. 169 virtual void RegisterMessages() OVERRIDE; 170 171 void HandleInitUICommand(const ListValue* args); 172 void HandleInspectCommand(const ListValue* args); 173 void HandleTerminateCommand(const ListValue* args); 174 175 bool GetProcessAndRouteId(const ListValue* args, 176 int* process_id, 177 int* route_id); 178 179 InspectUI* inspect_ui_; 180 181 DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler); 182}; 183 184void InspectMessageHandler::RegisterMessages() { 185 web_ui()->RegisterMessageCallback(kInitUICommand, 186 base::Bind(&InspectMessageHandler::HandleInitUICommand, 187 base::Unretained(this))); 188 web_ui()->RegisterMessageCallback(kInspectCommand, 189 base::Bind(&InspectMessageHandler::HandleInspectCommand, 190 base::Unretained(this))); 191 web_ui()->RegisterMessageCallback(kTerminateCommand, 192 base::Bind(&InspectMessageHandler::HandleTerminateCommand, 193 base::Unretained(this))); 194} 195 196void InspectMessageHandler::HandleInitUICommand(const ListValue*) { 197 inspect_ui_->InitUI(); 198} 199 200void InspectMessageHandler::HandleInspectCommand(const ListValue* args) { 201 Profile* profile = Profile::FromWebUI(web_ui()); 202 if (!profile) 203 return; 204 205 int process_id; 206 int route_id; 207 if (!GetProcessAndRouteId(args, &process_id, &route_id) || process_id == 0 208 || route_id == 0) { 209 // Check for ADB page id 210 const DictionaryValue* data; 211 std::string page_id; 212 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 213 data->GetString(kAdbPageIdField, &page_id)) { 214 scoped_refptr<DevToolsAdbBridge> adb_bridge = 215 DevToolsAdbBridge::Factory::GetForProfile(profile); 216 if (adb_bridge) 217 adb_bridge->Attach(page_id); 218 } 219 return; 220 } 221 222 RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); 223 if (rvh) { 224 DevToolsWindow::OpenDevToolsWindow(rvh); 225 return; 226 } 227 228 scoped_refptr<DevToolsAgentHost> agent_host( 229 DevToolsAgentHost::GetForWorker(process_id, route_id)); 230 if (!agent_host.get()) 231 return; 232 233 DevToolsWindow::OpenDevToolsWindowForWorker(profile, agent_host.get()); 234} 235 236static void TerminateWorker(int process_id, int route_id) { 237 WorkerService::GetInstance()->TerminateWorker(process_id, route_id); 238} 239 240void InspectMessageHandler::HandleTerminateCommand(const ListValue* args) { 241 int process_id; 242 int route_id; 243 if (!GetProcessAndRouteId(args, &process_id, &route_id)) 244 return; 245 246 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, 247 base::Bind(&TerminateWorker, process_id, route_id)); 248} 249 250bool InspectMessageHandler::GetProcessAndRouteId(const ListValue* args, 251 int* process_id, 252 int* route_id) { 253 const DictionaryValue* data; 254 if (args->GetSize() == 1 && args->GetDictionary(0, &data) && 255 data->GetInteger(kProcessIdField, process_id) && 256 data->GetInteger(kRouteIdField, route_id)) { 257 return true; 258 } 259 return false; 260} 261 262} // namespace 263 264class InspectUI::WorkerCreationDestructionListener 265 : public WorkerServiceObserver, 266 public base::RefCountedThreadSafe<WorkerCreationDestructionListener> { 267 public: 268 WorkerCreationDestructionListener() 269 : discovery_ui_(NULL) {} 270 271 void Init(InspectUI* workers_ui) { 272 DCHECK(workers_ui); 273 DCHECK(!discovery_ui_); 274 discovery_ui_ = workers_ui; 275 BrowserThread::PostTask( 276 BrowserThread::IO, FROM_HERE, 277 base::Bind(&WorkerCreationDestructionListener::RegisterObserver, 278 this)); 279 } 280 281 void InspectUIDestroyed() { 282 DCHECK(discovery_ui_); 283 discovery_ui_ = NULL; 284 BrowserThread::PostTask( 285 BrowserThread::IO, FROM_HERE, 286 base::Bind(&WorkerCreationDestructionListener::UnregisterObserver, 287 this)); 288 } 289 290 void InitUI() { 291 BrowserThread::PostTask( 292 BrowserThread::IO, FROM_HERE, 293 base::Bind(&WorkerCreationDestructionListener::CollectWorkersData, 294 this)); 295 } 296 297 private: 298 friend class base::RefCountedThreadSafe<WorkerCreationDestructionListener>; 299 virtual ~WorkerCreationDestructionListener() {} 300 301 virtual void WorkerCreated( 302 const GURL& url, 303 const string16& name, 304 int process_id, 305 int route_id) OVERRIDE { 306 CollectWorkersData(); 307 } 308 309 virtual void WorkerDestroyed(int process_id, int route_id) OVERRIDE { 310 CollectWorkersData(); 311 } 312 313 void CollectWorkersData() { 314 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 315 scoped_ptr<ListValue> target_list(new ListValue()); 316 std::vector<WorkerService::WorkerInfo> worker_info = 317 WorkerService::GetInstance()->GetWorkers(); 318 for (size_t i = 0; i < worker_info.size(); ++i) { 319 target_list->Append(BuildTargetDescriptor( 320 kWorkerTargetType, 321 false, 322 worker_info[i].url, 323 UTF16ToUTF8(worker_info[i].name), 324 GURL(), 325 worker_info[i].process_id, 326 worker_info[i].route_id, 327 worker_info[i].handle)); 328 } 329 330 BrowserThread::PostTask( 331 BrowserThread::UI, FROM_HERE, 332 base::Bind(&WorkerCreationDestructionListener::PopulateWorkersList, 333 this, base::Owned(target_list.release()))); 334 } 335 336 void RegisterObserver() { 337 WorkerService::GetInstance()->AddObserver(this); 338 } 339 340 void UnregisterObserver() { 341 WorkerService::GetInstance()->RemoveObserver(this); 342 } 343 344 void PopulateWorkersList(ListValue* target_list) { 345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 346 if (discovery_ui_) { 347 discovery_ui_->web_ui()->CallJavascriptFunction( 348 "populateWorkersList", *target_list); 349 } 350 } 351 352 InspectUI* discovery_ui_; 353}; 354 355InspectUI::InspectUI(content::WebUI* web_ui) 356 : WebUIController(web_ui) { 357 web_ui->AddMessageHandler(new InspectMessageHandler(this)); 358 Profile* profile = Profile::FromWebUI(web_ui); 359 content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource()); 360} 361 362InspectUI::~InspectUI() { 363 StopListeningNotifications(); 364} 365 366void InspectUI::InitUI() { 367 StartListeningNotifications(); 368 PopulateLists(); 369 observer_->InitUI(); 370} 371 372void InspectUI::PopulateLists() { 373 std::set<RenderViewHost*> tab_rvhs; 374 for (TabContentsIterator it; !it.done(); it.Next()) 375 tab_rvhs.insert(it->GetRenderViewHost()); 376 377 scoped_ptr<ListValue> target_list(new ListValue()); 378 379 std::vector<RenderViewHost*> rvh_vector = 380 DevToolsAgentHost::GetValidRenderViewHosts(); 381 382 for (std::vector<RenderViewHost*>::iterator it(rvh_vector.begin()); 383 it != rvh_vector.end(); it++) { 384 bool is_tab = tab_rvhs.find(*it) != tab_rvhs.end(); 385 target_list->Append(BuildTargetDescriptor(*it, is_tab)); 386 } 387 web_ui()->CallJavascriptFunction("populateLists", *target_list.get()); 388} 389 390void InspectUI::Observe(int type, 391 const content::NotificationSource& source, 392 const content::NotificationDetails& details) { 393 if (source != content::Source<WebContents>(web_ui()->GetWebContents())) 394 PopulateLists(); 395 else if (type == content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED) 396 StopListeningNotifications(); 397} 398 399void InspectUI::StartListeningNotifications() { 400 if (observer_) 401 return; 402 403 observer_ = new WorkerCreationDestructionListener(); 404 observer_->Init(this); 405 406 Profile* profile = Profile::FromWebUI(web_ui()); 407 DevToolsAdbBridge* adb_bridge = 408 DevToolsAdbBridge::Factory::GetForProfile(profile); 409 if (adb_bridge) 410 adb_bridge->AddListener(this); 411 412 registrar_.Add(this, 413 content::NOTIFICATION_WEB_CONTENTS_CONNECTED, 414 content::NotificationService::AllSources()); 415 registrar_.Add(this, 416 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 417 content::NotificationService::AllSources()); 418 registrar_.Add(this, 419 content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 420 content::NotificationService::AllSources()); 421} 422 423void InspectUI::StopListeningNotifications() 424{ 425 if (!observer_.get()) 426 return; 427 Profile* profile = Profile::FromWebUI(web_ui()); 428 DevToolsAdbBridge* adb_bridge = 429 DevToolsAdbBridge::Factory::GetForProfile(profile); 430 if (adb_bridge) 431 adb_bridge->RemoveListener(this); 432 observer_->InspectUIDestroyed(); 433 observer_ = NULL; 434 registrar_.RemoveAll(); 435} 436 437content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() { 438 content::WebUIDataSource* source = 439 content::WebUIDataSource::Create(chrome::kChromeUIInspectHost); 440 source->AddResourcePath("inspect.css", IDR_INSPECT_CSS); 441 source->AddResourcePath("inspect.js", IDR_INSPECT_JS); 442 source->SetDefaultResource(IDR_INSPECT_HTML); 443 return source; 444} 445 446void InspectUI::RemotePagesChanged(DevToolsAdbBridge::RemotePages* pages) { 447 ListValue targets; 448 for (DevToolsAdbBridge::RemotePages::iterator it = pages->begin(); 449 it != pages->end(); ++it) { 450 DevToolsAdbBridge::RemotePage* page = it->get(); 451 DictionaryValue* target_data = BuildTargetDescriptor(kAdbTargetType, 452 false, GURL(page->url()), page->title(), GURL(page->favicon_url()), 0, 453 0); 454 target_data->SetString(kAdbSerialField, page->serial()); 455 target_data->SetString(kAdbModelField, page->model()); 456 target_data->SetString(kAdbPackageField, page->package()); 457 target_data->SetString(kAdbPageIdField, page->id()); 458 targets.Append(target_data); 459 } 460 web_ui()->CallJavascriptFunction("populateDeviceLists", targets); 461} 462