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