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 "base/prefs/pref_service.h" 8#include "base/stl_util.h" 9#include "chrome/browser/devtools/devtools_target_impl.h" 10#include "chrome/browser/devtools/devtools_targets_ui.h" 11#include "chrome/browser/devtools/devtools_ui_bindings.h" 12#include "chrome/browser/profiles/profile.h" 13#include "chrome/browser/ui/browser_navigator.h" 14#include "chrome/browser/ui/singleton_tabs.h" 15#include "chrome/browser/ui/webui/theme_source.h" 16#include "chrome/common/pref_names.h" 17#include "chrome/common/url_constants.h" 18#include "content/public/browser/devtools_agent_host.h" 19#include "content/public/browser/navigation_entry.h" 20#include "content/public/browser/notification_service.h" 21#include "content/public/browser/notification_source.h" 22#include "content/public/browser/notification_types.h" 23#include "content/public/browser/user_metrics.h" 24#include "content/public/browser/web_contents.h" 25#include "content/public/browser/web_contents_delegate.h" 26#include "content/public/browser/web_contents_observer.h" 27#include "content/public/browser/web_ui.h" 28#include "content/public/browser/web_ui_data_source.h" 29#include "content/public/browser/web_ui_message_handler.h" 30#include "grit/browser_resources.h" 31 32using content::WebContents; 33using content::WebUIMessageHandler; 34 35namespace { 36 37const char kInitUICommand[] = "init-ui"; 38const char kInspectCommand[] = "inspect"; 39const char kActivateCommand[] = "activate"; 40const char kCloseCommand[] = "close"; 41const char kReloadCommand[] = "reload"; 42const char kOpenCommand[] = "open"; 43const char kInspectBrowser[] = "inspect-browser"; 44const char kLocalHost[] = "localhost"; 45 46const char kDiscoverUsbDevicesEnabledCommand[] = 47 "set-discover-usb-devices-enabled"; 48const char kPortForwardingEnabledCommand[] = 49 "set-port-forwarding-enabled"; 50const char kPortForwardingConfigCommand[] = "set-port-forwarding-config"; 51 52const char kPortForwardingDefaultPort[] = "8080"; 53const char kPortForwardingDefaultLocation[] = "localhost:8080"; 54 55// InspectMessageHandler -------------------------------------------- 56 57class InspectMessageHandler : public WebUIMessageHandler { 58 public: 59 explicit InspectMessageHandler(InspectUI* inspect_ui) 60 : inspect_ui_(inspect_ui) {} 61 virtual ~InspectMessageHandler() {} 62 63 private: 64 // WebUIMessageHandler implementation. 65 virtual void RegisterMessages() OVERRIDE; 66 67 void HandleInitUICommand(const base::ListValue* args); 68 void HandleInspectCommand(const base::ListValue* args); 69 void HandleActivateCommand(const base::ListValue* args); 70 void HandleCloseCommand(const base::ListValue* args); 71 void HandleReloadCommand(const base::ListValue* args); 72 void HandleOpenCommand(const base::ListValue* args); 73 void HandleInspectBrowserCommand(const base::ListValue* args); 74 void HandleBooleanPrefChanged(const char* pref_name, 75 const base::ListValue* args); 76 void HandlePortForwardingConfigCommand(const base::ListValue* args); 77 78 InspectUI* inspect_ui_; 79 80 DISALLOW_COPY_AND_ASSIGN(InspectMessageHandler); 81}; 82 83void InspectMessageHandler::RegisterMessages() { 84 web_ui()->RegisterMessageCallback(kInitUICommand, 85 base::Bind(&InspectMessageHandler::HandleInitUICommand, 86 base::Unretained(this))); 87 web_ui()->RegisterMessageCallback(kInspectCommand, 88 base::Bind(&InspectMessageHandler::HandleInspectCommand, 89 base::Unretained(this))); 90 web_ui()->RegisterMessageCallback(kActivateCommand, 91 base::Bind(&InspectMessageHandler::HandleActivateCommand, 92 base::Unretained(this))); 93 web_ui()->RegisterMessageCallback(kCloseCommand, 94 base::Bind(&InspectMessageHandler::HandleCloseCommand, 95 base::Unretained(this))); 96 web_ui()->RegisterMessageCallback(kDiscoverUsbDevicesEnabledCommand, 97 base::Bind(&InspectMessageHandler::HandleBooleanPrefChanged, 98 base::Unretained(this), 99 &prefs::kDevToolsDiscoverUsbDevicesEnabled[0])); 100 web_ui()->RegisterMessageCallback(kPortForwardingEnabledCommand, 101 base::Bind(&InspectMessageHandler::HandleBooleanPrefChanged, 102 base::Unretained(this), 103 &prefs::kDevToolsPortForwardingEnabled[0])); 104 web_ui()->RegisterMessageCallback(kPortForwardingConfigCommand, 105 base::Bind(&InspectMessageHandler::HandlePortForwardingConfigCommand, 106 base::Unretained(this))); 107 web_ui()->RegisterMessageCallback(kReloadCommand, 108 base::Bind(&InspectMessageHandler::HandleReloadCommand, 109 base::Unretained(this))); 110 web_ui()->RegisterMessageCallback(kOpenCommand, 111 base::Bind(&InspectMessageHandler::HandleOpenCommand, 112 base::Unretained(this))); 113 web_ui()->RegisterMessageCallback(kInspectBrowser, 114 base::Bind(&InspectMessageHandler::HandleInspectBrowserCommand, 115 base::Unretained(this))); 116} 117 118void InspectMessageHandler::HandleInitUICommand(const base::ListValue*) { 119 inspect_ui_->InitUI(); 120} 121 122static bool ParseStringArgs(const base::ListValue* args, 123 std::string* arg0, 124 std::string* arg1, 125 std::string* arg2 = 0) { 126 int arg_size = args->GetSize(); 127 return (!arg0 || (arg_size > 0 && args->GetString(0, arg0))) && 128 (!arg1 || (arg_size > 1 && args->GetString(1, arg1))) && 129 (!arg2 || (arg_size > 2 && args->GetString(2, arg2))); 130} 131 132void InspectMessageHandler::HandleInspectCommand(const base::ListValue* args) { 133 std::string source; 134 std::string id; 135 if (ParseStringArgs(args, &source, &id)) 136 inspect_ui_->Inspect(source, id); 137} 138 139void InspectMessageHandler::HandleActivateCommand(const base::ListValue* args) { 140 std::string source; 141 std::string id; 142 if (ParseStringArgs(args, &source, &id)) 143 inspect_ui_->Activate(source, id); 144} 145 146void InspectMessageHandler::HandleCloseCommand(const base::ListValue* args) { 147 std::string source; 148 std::string id; 149 if (ParseStringArgs(args, &source, &id)) 150 inspect_ui_->Close(source, id); 151} 152 153void InspectMessageHandler::HandleReloadCommand(const base::ListValue* args) { 154 std::string source; 155 std::string id; 156 if (ParseStringArgs(args, &source, &id)) 157 inspect_ui_->Reload(source, id); 158} 159 160void InspectMessageHandler::HandleOpenCommand(const base::ListValue* args) { 161 std::string source_id; 162 std::string browser_id; 163 std::string url; 164 if (ParseStringArgs(args, &source_id, &browser_id, &url)) 165 inspect_ui_->Open(source_id, browser_id, url); 166} 167 168void InspectMessageHandler::HandleInspectBrowserCommand( 169 const base::ListValue* args) { 170 std::string source_id; 171 std::string browser_id; 172 std::string front_end; 173 if (ParseStringArgs(args, &source_id, &browser_id, &front_end)) { 174 inspect_ui_->InspectBrowserWithCustomFrontend( 175 source_id, browser_id, GURL(front_end)); 176 } 177} 178 179void InspectMessageHandler::HandleBooleanPrefChanged( 180 const char* pref_name, 181 const base::ListValue* args) { 182 Profile* profile = Profile::FromWebUI(web_ui()); 183 if (!profile) 184 return; 185 186 bool enabled; 187 if (args->GetSize() == 1 && args->GetBoolean(0, &enabled)) 188 profile->GetPrefs()->SetBoolean(pref_name, enabled); 189} 190 191void InspectMessageHandler::HandlePortForwardingConfigCommand( 192 const base::ListValue* args) { 193 Profile* profile = Profile::FromWebUI(web_ui()); 194 if (!profile) 195 return; 196 197 const base::DictionaryValue* dict_src; 198 if (args->GetSize() == 1 && args->GetDictionary(0, &dict_src)) 199 profile->GetPrefs()->Set(prefs::kDevToolsPortForwardingConfig, *dict_src); 200} 201 202// DevToolsUIBindingsEnabler ---------------------------------------- 203 204class DevToolsUIBindingsEnabler 205 : public content::WebContentsObserver { 206 public: 207 DevToolsUIBindingsEnabler(WebContents* web_contents, 208 const GURL& url); 209 virtual ~DevToolsUIBindingsEnabler() {} 210 211 DevToolsUIBindings* GetBindings(); 212 213 private: 214 // contents::WebContentsObserver overrides. 215 virtual void WebContentsDestroyed() OVERRIDE; 216 virtual void AboutToNavigateRenderView( 217 content::RenderViewHost* render_view_host) OVERRIDE; 218 219 DevToolsUIBindings bindings_; 220 GURL url_; 221 DISALLOW_COPY_AND_ASSIGN(DevToolsUIBindingsEnabler); 222}; 223 224DevToolsUIBindingsEnabler::DevToolsUIBindingsEnabler( 225 WebContents* web_contents, 226 const GURL& url) 227 : WebContentsObserver(web_contents), 228 bindings_(web_contents), 229 url_(url) { 230} 231 232DevToolsUIBindings* DevToolsUIBindingsEnabler::GetBindings() { 233 return &bindings_; 234} 235 236void DevToolsUIBindingsEnabler::WebContentsDestroyed() { 237 delete this; 238} 239 240void DevToolsUIBindingsEnabler::AboutToNavigateRenderView( 241 content::RenderViewHost* render_view_host) { 242 content::NavigationEntry* entry = 243 web_contents()->GetController().GetActiveEntry(); 244 if (url_ != entry->GetURL()) 245 delete this; 246} 247 248} // namespace 249 250// InspectUI -------------------------------------------------------- 251 252InspectUI::InspectUI(content::WebUI* web_ui) 253 : WebUIController(web_ui) { 254 web_ui->AddMessageHandler(new InspectMessageHandler(this)); 255 Profile* profile = Profile::FromWebUI(web_ui); 256 content::WebUIDataSource::Add(profile, CreateInspectUIHTMLSource()); 257 258 // Set up the chrome://theme/ source. 259 ThemeSource* theme = new ThemeSource(profile); 260 content::URLDataSource::Add(profile, theme); 261} 262 263InspectUI::~InspectUI() { 264 StopListeningNotifications(); 265} 266 267void InspectUI::InitUI() { 268 SetPortForwardingDefaults(); 269 StartListeningNotifications(); 270 UpdateDiscoverUsbDevicesEnabled(); 271 UpdatePortForwardingEnabled(); 272 UpdatePortForwardingConfig(); 273} 274 275void InspectUI::Inspect(const std::string& source_id, 276 const std::string& target_id) { 277 DevToolsTargetImpl* target = FindTarget(source_id, target_id); 278 if (target) { 279 const std::string target_type = target->GetType(); 280 target->Inspect(Profile::FromWebUI(web_ui())); 281 ForceUpdateIfNeeded(source_id, target_type); 282 } 283} 284 285void InspectUI::Activate(const std::string& source_id, 286 const std::string& target_id) { 287 DevToolsTargetImpl* target = FindTarget(source_id, target_id); 288 if (target) { 289 const std::string target_type = target->GetType(); 290 target->Activate(); 291 ForceUpdateIfNeeded(source_id, target_type); 292 } 293} 294 295void InspectUI::Close(const std::string& source_id, 296 const std::string& target_id) { 297 DevToolsTargetImpl* target = FindTarget(source_id, target_id); 298 if (target) { 299 const std::string target_type = target->GetType(); 300 target->Close(); 301 ForceUpdateIfNeeded(source_id, target_type); 302 } 303} 304 305void InspectUI::Reload(const std::string& source_id, 306 const std::string& target_id) { 307 DevToolsTargetImpl* target = FindTarget(source_id, target_id); 308 if (target) { 309 const std::string target_type = target->GetType(); 310 target->Reload(); 311 ForceUpdateIfNeeded(source_id, target_type); 312 } 313} 314 315static void NoOp(DevToolsTargetImpl*) {} 316 317void InspectUI::Open(const std::string& source_id, 318 const std::string& browser_id, 319 const std::string& url) { 320 DevToolsTargetsUIHandler* handler = FindTargetHandler(source_id); 321 if (handler) 322 handler->Open(browser_id, url, base::Bind(&NoOp)); 323} 324 325void InspectUI::InspectBrowserWithCustomFrontend( 326 const std::string& source_id, 327 const std::string& browser_id, 328 const GURL& frontend_url) { 329 if (!frontend_url.SchemeIs(content::kChromeUIScheme) && 330 !frontend_url.SchemeIs(content::kChromeDevToolsScheme) && 331 frontend_url.host() != kLocalHost) { 332 return; 333 } 334 335 DevToolsTargetsUIHandler* handler = FindTargetHandler(source_id); 336 if (!handler) 337 return; 338 339 // Fetch agent host from remote browser. 340 scoped_refptr<content::DevToolsAgentHost> agent_host = 341 handler->GetBrowserAgentHost(browser_id); 342 if (agent_host->IsAttached()) 343 return; 344 345 // Create web contents for the front-end. 346 WebContents* inspect_ui = web_ui()->GetWebContents(); 347 WebContents* front_end = inspect_ui->GetDelegate()->OpenURLFromTab( 348 inspect_ui, 349 content::OpenURLParams(frontend_url, 350 content::Referrer(), 351 NEW_FOREGROUND_TAB, 352 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, 353 false)); 354 355 // Install devtools bindings. 356 DevToolsUIBindingsEnabler* bindings_enabler = 357 new DevToolsUIBindingsEnabler(front_end, frontend_url); 358 bindings_enabler->GetBindings()->AttachTo(agent_host); 359} 360 361void InspectUI::InspectDevices(Browser* browser) { 362 content::RecordAction(base::UserMetricsAction("InspectDevices")); 363 chrome::NavigateParams params(chrome::GetSingletonTabNavigateParams( 364 browser, GURL(chrome::kChromeUIInspectURL))); 365 params.path_behavior = chrome::NavigateParams::IGNORE_AND_NAVIGATE; 366 ShowSingletonTabOverwritingNTP(browser, params); 367} 368 369void InspectUI::Observe(int type, 370 const content::NotificationSource& source, 371 const content::NotificationDetails& details) { 372 if (source == content::Source<WebContents>(web_ui()->GetWebContents())) 373 StopListeningNotifications(); 374} 375 376void InspectUI::StartListeningNotifications() { 377 if (!target_handlers_.empty()) // Possible when reloading the page. 378 StopListeningNotifications(); 379 380 Profile* profile = Profile::FromWebUI(web_ui()); 381 382 DevToolsTargetsUIHandler::Callback callback = 383 base::Bind(&InspectUI::PopulateTargets, base::Unretained(this)); 384 385 AddTargetUIHandler( 386 DevToolsTargetsUIHandler::CreateForLocal(callback)); 387 if (profile->IsOffTheRecord()) { 388 ShowIncognitoWarning(); 389 } else { 390 AddTargetUIHandler( 391 DevToolsTargetsUIHandler::CreateForAdb(callback, profile)); 392 } 393 394 port_status_serializer_.reset( 395 new PortForwardingStatusSerializer( 396 base::Bind(&InspectUI::PopulatePortStatus, base::Unretained(this)), 397 profile)); 398 399 notification_registrar_.Add(this, 400 content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED, 401 content::NotificationService::AllSources()); 402 403 pref_change_registrar_.Init(profile->GetPrefs()); 404 pref_change_registrar_.Add(prefs::kDevToolsDiscoverUsbDevicesEnabled, 405 base::Bind(&InspectUI::UpdateDiscoverUsbDevicesEnabled, 406 base::Unretained(this))); 407 pref_change_registrar_.Add(prefs::kDevToolsPortForwardingEnabled, 408 base::Bind(&InspectUI::UpdatePortForwardingEnabled, 409 base::Unretained(this))); 410 pref_change_registrar_.Add(prefs::kDevToolsPortForwardingConfig, 411 base::Bind(&InspectUI::UpdatePortForwardingConfig, 412 base::Unretained(this))); 413} 414 415void InspectUI::StopListeningNotifications() { 416 if (target_handlers_.empty()) 417 return; 418 419 STLDeleteValues(&target_handlers_); 420 421 port_status_serializer_.reset(); 422 423 notification_registrar_.RemoveAll(); 424 pref_change_registrar_.RemoveAll(); 425} 426 427content::WebUIDataSource* InspectUI::CreateInspectUIHTMLSource() { 428 content::WebUIDataSource* source = 429 content::WebUIDataSource::Create(chrome::kChromeUIInspectHost); 430 source->AddResourcePath("inspect.css", IDR_INSPECT_CSS); 431 source->AddResourcePath("inspect.js", IDR_INSPECT_JS); 432 source->SetDefaultResource(IDR_INSPECT_HTML); 433 return source; 434} 435 436void InspectUI::UpdateDiscoverUsbDevicesEnabled() { 437 web_ui()->CallJavascriptFunction( 438 "updateDiscoverUsbDevicesEnabled", 439 *GetPrefValue(prefs::kDevToolsDiscoverUsbDevicesEnabled)); 440} 441 442void InspectUI::UpdatePortForwardingEnabled() { 443 web_ui()->CallJavascriptFunction( 444 "updatePortForwardingEnabled", 445 *GetPrefValue(prefs::kDevToolsPortForwardingEnabled)); 446} 447 448void InspectUI::UpdatePortForwardingConfig() { 449 web_ui()->CallJavascriptFunction( 450 "updatePortForwardingConfig", 451 *GetPrefValue(prefs::kDevToolsPortForwardingConfig)); 452} 453 454void InspectUI::SetPortForwardingDefaults() { 455 Profile* profile = Profile::FromWebUI(web_ui()); 456 PrefService* prefs = profile->GetPrefs(); 457 458 bool default_set; 459 if (!GetPrefValue(prefs::kDevToolsPortForwardingDefaultSet)-> 460 GetAsBoolean(&default_set) || default_set) { 461 return; 462 } 463 464 // This is the first chrome://inspect invocation on a fresh profile or after 465 // upgrade from a version that did not have kDevToolsPortForwardingDefaultSet. 466 prefs->SetBoolean(prefs::kDevToolsPortForwardingDefaultSet, true); 467 468 bool enabled; 469 const base::DictionaryValue* config; 470 if (!GetPrefValue(prefs::kDevToolsPortForwardingEnabled)-> 471 GetAsBoolean(&enabled) || 472 !GetPrefValue(prefs::kDevToolsPortForwardingConfig)-> 473 GetAsDictionary(&config)) { 474 return; 475 } 476 477 // Do nothing if user already took explicit action. 478 if (enabled || config->size() != 0) 479 return; 480 481 base::DictionaryValue default_config; 482 default_config.SetString( 483 kPortForwardingDefaultPort, kPortForwardingDefaultLocation); 484 prefs->Set(prefs::kDevToolsPortForwardingConfig, default_config); 485} 486 487const base::Value* InspectUI::GetPrefValue(const char* name) { 488 Profile* profile = Profile::FromWebUI(web_ui()); 489 return profile->GetPrefs()->FindPreference(name)->GetValue(); 490} 491 492void InspectUI::AddTargetUIHandler( 493 scoped_ptr<DevToolsTargetsUIHandler> handler) { 494 DevToolsTargetsUIHandler* handler_ptr = handler.release(); 495 target_handlers_[handler_ptr->source_id()] = handler_ptr; 496} 497 498DevToolsTargetsUIHandler* InspectUI::FindTargetHandler( 499 const std::string& source_id) { 500 TargetHandlerMap::iterator it = target_handlers_.find(source_id); 501 return it != target_handlers_.end() ? it->second : NULL; 502} 503 504DevToolsTargetImpl* InspectUI::FindTarget( 505 const std::string& source_id, const std::string& target_id) { 506 TargetHandlerMap::iterator it = target_handlers_.find(source_id); 507 return it != target_handlers_.end() ? 508 it->second->GetTarget(target_id) : NULL; 509} 510 511void InspectUI::PopulateTargets(const std::string& source, 512 const base::ListValue& targets) { 513 web_ui()->CallJavascriptFunction("populateTargets", 514 base::StringValue(source), 515 targets); 516} 517 518void InspectUI::ForceUpdateIfNeeded(const std::string& source_id, 519 const std::string& target_type) { 520 // TODO(dgozman): remove this after moving discovery to protocol. 521 // See crbug.com/398049. 522 if (target_type != DevToolsTargetImpl::kTargetTypeServiceWorker) 523 return; 524 DevToolsTargetsUIHandler* handler = FindTargetHandler(source_id); 525 if (handler) 526 handler->ForceUpdate(); 527} 528 529void InspectUI::PopulatePortStatus(const base::Value& status) { 530 web_ui()->CallJavascriptFunction("populatePortStatus", status); 531} 532 533void InspectUI::ShowIncognitoWarning() { 534 web_ui()->CallJavascriptFunction("showIncognitoWarning"); 535} 536