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/devtools/devtools_window.h" 6 7 #include <algorithm> 8 9 #include "base/json/json_reader.h" 10 #include "base/metrics/histogram.h" 11 #include "base/prefs/scoped_user_pref_update.h" 12 #include "base/time/time.h" 13 #include "base/values.h" 14 #include "chrome/browser/chrome_page_zoom.h" 15 #include "chrome/browser/file_select_helper.h" 16 #include "chrome/browser/infobars/infobar_service.h" 17 #include "chrome/browser/prefs/pref_service_syncable.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/browser/sessions/session_tab_helper.h" 20 #include "chrome/browser/ui/browser.h" 21 #include "chrome/browser/ui/browser_dialogs.h" 22 #include "chrome/browser/ui/browser_iterator.h" 23 #include "chrome/browser/ui/browser_list.h" 24 #include "chrome/browser/ui/browser_window.h" 25 #include "chrome/browser/ui/host_desktop.h" 26 #include "chrome/browser/ui/prefs/prefs_tab_helper.h" 27 #include "chrome/browser/ui/tabs/tab_strip_model.h" 28 #include "chrome/browser/ui/webui/devtools_ui.h" 29 #include "chrome/common/chrome_switches.h" 30 #include "chrome/common/pref_names.h" 31 #include "chrome/common/render_messages.h" 32 #include "chrome/common/url_constants.h" 33 #include "components/pref_registry/pref_registry_syncable.h" 34 #include "content/public/browser/browser_thread.h" 35 #include "content/public/browser/devtools_agent_host.h" 36 #include "content/public/browser/devtools_client_host.h" 37 #include "content/public/browser/devtools_manager.h" 38 #include "content/public/browser/native_web_keyboard_event.h" 39 #include "content/public/browser/navigation_controller.h" 40 #include "content/public/browser/navigation_entry.h" 41 #include "content/public/browser/notification_source.h" 42 #include "content/public/browser/render_frame_host.h" 43 #include "content/public/browser/render_process_host.h" 44 #include "content/public/browser/render_view_host.h" 45 #include "content/public/browser/render_widget_host_view.h" 46 #include "content/public/browser/user_metrics.h" 47 #include "content/public/browser/web_contents.h" 48 #include "content/public/browser/web_contents_observer.h" 49 #include "content/public/common/content_client.h" 50 #include "content/public/common/page_transition_types.h" 51 #include "content/public/common/url_constants.h" 52 #include "content/public/test/test_utils.h" 53 #include "third_party/WebKit/public/web/WebInputEvent.h" 54 #include "ui/events/keycodes/keyboard_codes.h" 55 56 using base::DictionaryValue; 57 using blink::WebInputEvent; 58 using content::BrowserThread; 59 using content::DevToolsAgentHost; 60 using content::WebContents; 61 62 namespace { 63 64 typedef std::vector<DevToolsWindow*> DevToolsWindows; 65 base::LazyInstance<DevToolsWindows>::Leaky g_instances = 66 LAZY_INSTANCE_INITIALIZER; 67 68 static const char kKeyUpEventName[] = "keyup"; 69 static const char kKeyDownEventName[] = "keydown"; 70 71 } // namespace 72 73 // DevToolsEventForwarder ----------------------------------------------------- 74 75 class DevToolsEventForwarder { 76 public: 77 explicit DevToolsEventForwarder(DevToolsWindow* window) 78 : devtools_window_(window) {} 79 80 // Registers whitelisted shortcuts with the forwarder. 81 // Only registered keys will be forwarded to the DevTools frontend. 82 void SetWhitelistedShortcuts(const std::string& message); 83 84 // Forwards a keyboard event to the DevTools frontend if it is whitelisted. 85 // Returns |true| if the event has been forwarded, |false| otherwise. 86 bool ForwardEvent(const content::NativeWebKeyboardEvent& event); 87 88 private: 89 static int VirtualKeyCodeWithoutLocation(int key_code); 90 static bool KeyWhitelistingAllowed(int key_code, int modifiers); 91 static int CombineKeyCodeAndModifiers(int key_code, int modifiers); 92 93 DevToolsWindow* devtools_window_; 94 std::set<int> whitelisted_keys_; 95 96 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder); 97 }; 98 99 void DevToolsEventForwarder::SetWhitelistedShortcuts( 100 const std::string& message) { 101 scoped_ptr<base::Value> parsed_message(base::JSONReader::Read(message)); 102 base::ListValue* shortcut_list; 103 if (!parsed_message->GetAsList(&shortcut_list)) 104 return; 105 base::ListValue::iterator it = shortcut_list->begin(); 106 for (; it != shortcut_list->end(); ++it) { 107 base::DictionaryValue* dictionary; 108 if (!(*it)->GetAsDictionary(&dictionary)) 109 continue; 110 int key_code = 0; 111 dictionary->GetInteger("keyCode", &key_code); 112 if (key_code == 0) 113 continue; 114 int modifiers = 0; 115 dictionary->GetInteger("modifiers", &modifiers); 116 if (!KeyWhitelistingAllowed(key_code, modifiers)) { 117 LOG(WARNING) << "Key whitelisting forbidden: " 118 << "(" << key_code << "," << modifiers << ")"; 119 continue; 120 } 121 whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers)); 122 } 123 } 124 125 bool DevToolsEventForwarder::ForwardEvent( 126 const content::NativeWebKeyboardEvent& event) { 127 std::string event_type; 128 switch (event.type) { 129 case WebInputEvent::KeyDown: 130 case WebInputEvent::RawKeyDown: 131 event_type = kKeyDownEventName; 132 break; 133 case WebInputEvent::KeyUp: 134 event_type = kKeyUpEventName; 135 break; 136 default: 137 return false; 138 } 139 140 int key_code = VirtualKeyCodeWithoutLocation(event.windowsKeyCode); 141 int key = CombineKeyCodeAndModifiers(key_code, event.modifiers); 142 if (whitelisted_keys_.find(key) == whitelisted_keys_.end()) 143 return false; 144 145 base::DictionaryValue event_data; 146 event_data.SetString("type", event_type); 147 event_data.SetString("keyIdentifier", event.keyIdentifier); 148 event_data.SetInteger("keyCode", key_code); 149 event_data.SetInteger("modifiers", event.modifiers); 150 devtools_window_->bindings_->CallClientFunction( 151 "InspectorFrontendAPI.keyEventUnhandled", &event_data, NULL, NULL); 152 return true; 153 } 154 155 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code, 156 int modifiers) { 157 return key_code | (modifiers << 16); 158 } 159 160 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code, 161 int modifiers) { 162 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) || 163 modifiers != 0; 164 } 165 166 // Mapping copied from Blink's KeyboardEvent.cpp. 167 int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code) 168 { 169 switch (key_code) { 170 case ui::VKEY_LCONTROL: 171 case ui::VKEY_RCONTROL: 172 return ui::VKEY_CONTROL; 173 case ui::VKEY_LSHIFT: 174 case ui::VKEY_RSHIFT: 175 return ui::VKEY_SHIFT; 176 case ui::VKEY_LMENU: 177 case ui::VKEY_RMENU: 178 return ui::VKEY_MENU; 179 default: 180 return key_code; 181 } 182 } 183 184 // DevToolsWindow::InspectedWebContentsObserver ------------------------------- 185 186 class DevToolsWindow::InspectedWebContentsObserver 187 : public content::WebContentsObserver { 188 public: 189 explicit InspectedWebContentsObserver(WebContents* web_contents); 190 virtual ~InspectedWebContentsObserver(); 191 192 WebContents* web_contents() { 193 return WebContentsObserver::web_contents(); 194 } 195 196 private: 197 DISALLOW_COPY_AND_ASSIGN(InspectedWebContentsObserver); 198 }; 199 200 DevToolsWindow::InspectedWebContentsObserver::InspectedWebContentsObserver( 201 WebContents* web_contents) 202 : WebContentsObserver(web_contents) { 203 } 204 205 DevToolsWindow::InspectedWebContentsObserver::~InspectedWebContentsObserver() { 206 } 207 208 // DevToolsToolboxDelegate ---------------------------------------------------- 209 210 class DevToolsToolboxDelegate 211 : public content::WebContentsObserver, 212 public content::WebContentsDelegate { 213 public: 214 explicit DevToolsToolboxDelegate(WebContents* web_contents) 215 : WebContentsObserver(web_contents) { } 216 virtual ~DevToolsToolboxDelegate() { } 217 218 virtual content::WebContents* OpenURLFromTab( 219 content::WebContents* source, 220 const content::OpenURLParams& params) OVERRIDE { 221 DCHECK(source == web_contents()); 222 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) 223 return NULL; 224 content::NavigationController::LoadURLParams load_url_params(params.url); 225 source->GetController().LoadURLWithParams(load_url_params); 226 return source; 227 } 228 229 virtual void WebContentsDestroyed() OVERRIDE { 230 delete this; 231 } 232 233 private: 234 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate); 235 }; 236 237 // DevToolsWindow ------------------------------------------------------------- 238 239 const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; 240 241 DevToolsWindow::~DevToolsWindow() { 242 UpdateBrowserWindow(); 243 UpdateBrowserToolbar(); 244 245 if (toolbox_web_contents_) 246 delete toolbox_web_contents_; 247 248 DevToolsWindows* instances = g_instances.Pointer(); 249 DevToolsWindows::iterator it( 250 std::find(instances->begin(), instances->end(), this)); 251 DCHECK(it != instances->end()); 252 instances->erase(it); 253 } 254 255 // static 256 std::string DevToolsWindow::GetDevToolsWindowPlacementPrefKey() { 257 return std::string(prefs::kBrowserWindowPlacement) + "_" + 258 std::string(kDevToolsApp); 259 } 260 261 // static 262 void DevToolsWindow::RegisterProfilePrefs( 263 user_prefs::PrefRegistrySyncable* registry) { 264 registry->RegisterDictionaryPref( 265 prefs::kDevToolsEditedFiles, 266 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 267 registry->RegisterDictionaryPref( 268 prefs::kDevToolsFileSystemPaths, 269 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 270 registry->RegisterStringPref( 271 prefs::kDevToolsAdbKey, std::string(), 272 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 273 274 registry->RegisterDictionaryPref( 275 GetDevToolsWindowPlacementPrefKey().c_str(), 276 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 277 278 registry->RegisterBooleanPref( 279 prefs::kDevToolsDiscoverUsbDevicesEnabled, 280 true, 281 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 282 registry->RegisterBooleanPref( 283 prefs::kDevToolsPortForwardingEnabled, 284 false, 285 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 286 registry->RegisterBooleanPref( 287 prefs::kDevToolsPortForwardingDefaultSet, 288 false, 289 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 290 registry->RegisterDictionaryPref( 291 prefs::kDevToolsPortForwardingConfig, 292 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 293 } 294 295 // static 296 content::WebContents* DevToolsWindow::GetInTabWebContents( 297 WebContents* inspected_web_contents, 298 DevToolsContentsResizingStrategy* out_strategy) { 299 DevToolsWindow* window = GetInstanceForInspectedWebContents( 300 inspected_web_contents); 301 if (!window) 302 return NULL; 303 304 // Not yet loaded window is treated as docked, but we should not present it 305 // until we decided on docking. 306 bool is_docked_set = window->load_state_ == kLoadCompleted || 307 window->load_state_ == kIsDockedSet; 308 if (!is_docked_set) 309 return NULL; 310 311 // Undocked window should have toolbox web contents. 312 if (!window->is_docked_ && !window->toolbox_web_contents_) 313 return NULL; 314 315 if (out_strategy) 316 out_strategy->CopyFrom(window->contents_resizing_strategy_); 317 318 return window->is_docked_ ? window->main_web_contents_ : 319 window->toolbox_web_contents_; 320 } 321 322 // static 323 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedRenderViewHost( 324 content::RenderViewHost* inspected_rvh) { 325 if (!inspected_rvh || !DevToolsAgentHost::HasFor(inspected_rvh)) 326 return NULL; 327 328 scoped_refptr<DevToolsAgentHost> agent(DevToolsAgentHost::GetOrCreateFor( 329 inspected_rvh)); 330 return FindDevToolsWindow(agent.get()); 331 } 332 333 // static 334 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents( 335 WebContents* inspected_web_contents) { 336 if (!inspected_web_contents) 337 return NULL; 338 return GetInstanceForInspectedRenderViewHost( 339 inspected_web_contents->GetRenderViewHost()); 340 } 341 342 // static 343 bool DevToolsWindow::IsDevToolsWindow(content::RenderViewHost* window_rvh) { 344 return AsDevToolsWindow(window_rvh) != NULL; 345 } 346 347 // static 348 DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker( 349 Profile* profile, 350 DevToolsAgentHost* worker_agent) { 351 DevToolsWindow* window = FindDevToolsWindow(worker_agent); 352 if (!window) { 353 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile); 354 // Will disconnect the current client host if there is one. 355 content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( 356 worker_agent, window->bindings_->frontend_host()); 357 } 358 window->ScheduleShow(DevToolsToggleAction::Show()); 359 return window; 360 } 361 362 // static 363 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker( 364 Profile* profile) { 365 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker")); 366 return Create(profile, GURL(), NULL, true, false, false); 367 } 368 369 // static 370 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow( 371 content::RenderViewHost* inspected_rvh) { 372 return ToggleDevToolsWindow( 373 inspected_rvh, true, DevToolsToggleAction::Show()); 374 } 375 376 // static 377 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow( 378 content::RenderViewHost* inspected_rvh, 379 const DevToolsToggleAction& action) { 380 return ToggleDevToolsWindow( 381 inspected_rvh, true, action); 382 } 383 384 // static 385 DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForTest( 386 content::RenderViewHost* inspected_rvh, 387 bool is_docked) { 388 DevToolsWindow* window = OpenDevToolsWindow(inspected_rvh); 389 window->SetIsDockedAndShowImmediatelyForTest(is_docked); 390 return window; 391 } 392 393 // static 394 DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForTest( 395 Browser* browser, 396 bool is_docked) { 397 return OpenDevToolsWindowForTest( 398 browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(), 399 is_docked); 400 } 401 402 // static 403 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( 404 Browser* browser, 405 const DevToolsToggleAction& action) { 406 if (action.type() == DevToolsToggleAction::kToggle && 407 browser->is_devtools()) { 408 browser->tab_strip_model()->CloseAllTabs(); 409 return NULL; 410 } 411 412 return ToggleDevToolsWindow( 413 browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(), 414 action.type() == DevToolsToggleAction::kInspect, action); 415 } 416 417 // static 418 void DevToolsWindow::OpenExternalFrontend( 419 Profile* profile, 420 const std::string& frontend_url, 421 content::DevToolsAgentHost* agent_host) { 422 DevToolsWindow* window = FindDevToolsWindow(agent_host); 423 if (!window) { 424 window = Create(profile, DevToolsUI::GetProxyURL(frontend_url), NULL, 425 false, true, false); 426 content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( 427 agent_host, window->bindings_->frontend_host()); 428 } 429 window->ScheduleShow(DevToolsToggleAction::Show()); 430 } 431 432 // static 433 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( 434 content::RenderViewHost* inspected_rvh, 435 bool force_open, 436 const DevToolsToggleAction& action) { 437 scoped_refptr<DevToolsAgentHost> agent( 438 DevToolsAgentHost::GetOrCreateFor(inspected_rvh)); 439 content::DevToolsManager* manager = content::DevToolsManager::GetInstance(); 440 DevToolsWindow* window = FindDevToolsWindow(agent.get()); 441 bool do_open = force_open; 442 if (!window) { 443 Profile* profile = Profile::FromBrowserContext( 444 inspected_rvh->GetProcess()->GetBrowserContext()); 445 content::RecordAction( 446 base::UserMetricsAction("DevTools_InspectRenderer")); 447 window = Create(profile, GURL(), inspected_rvh, false, false, true); 448 manager->RegisterDevToolsClientHostFor(agent.get(), 449 window->bindings_->frontend_host()); 450 do_open = true; 451 } 452 453 // Update toolbar to reflect DevTools changes. 454 window->UpdateBrowserToolbar(); 455 456 // If window is docked and visible, we hide it on toggle. If window is 457 // undocked, we show (activate) it. 458 if (!window->is_docked_ || do_open) 459 window->ScheduleShow(action); 460 else 461 window->CloseWindow(); 462 463 return window; 464 } 465 466 // static 467 void DevToolsWindow::InspectElement(content::RenderViewHost* inspected_rvh, 468 int x, 469 int y) { 470 scoped_refptr<DevToolsAgentHost> agent( 471 DevToolsAgentHost::GetOrCreateFor(inspected_rvh)); 472 agent->InspectElement(x, y); 473 bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL; 474 base::TimeTicks start_time = base::TimeTicks::Now(); 475 // TODO(loislo): we should initiate DevTools window opening from within 476 // renderer. Otherwise, we still can hit a race condition here. 477 DevToolsWindow* window = OpenDevToolsWindow(inspected_rvh); 478 if (should_measure_time) 479 window->inspect_element_start_time_ = start_time; 480 } 481 482 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) { 483 if (load_state_ == kLoadCompleted) { 484 Show(action); 485 return; 486 } 487 488 // Action will be done only after load completed. 489 action_on_load_ = action; 490 491 if (!can_dock_) { 492 // No harm to show always-undocked window right away. 493 is_docked_ = false; 494 Show(DevToolsToggleAction::Show()); 495 } 496 } 497 498 void DevToolsWindow::Show(const DevToolsToggleAction& action) { 499 if (action.type() == DevToolsToggleAction::kNoOp) 500 return; 501 502 if (is_docked_) { 503 DCHECK(can_dock_); 504 Browser* inspected_browser = NULL; 505 int inspected_tab_index = -1; 506 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), 507 &inspected_browser, 508 &inspected_tab_index); 509 DCHECK(inspected_browser); 510 DCHECK(inspected_tab_index != -1); 511 512 // Tell inspected browser to update splitter and switch to inspected panel. 513 BrowserWindow* inspected_window = inspected_browser->window(); 514 main_web_contents_->SetDelegate(this); 515 516 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model(); 517 tab_strip_model->ActivateTabAt(inspected_tab_index, true); 518 519 inspected_window->UpdateDevTools(); 520 main_web_contents_->SetInitialFocus(); 521 inspected_window->Show(); 522 // On Aura, focusing once is not enough. Do it again. 523 // Note that focusing only here but not before isn't enough either. We just 524 // need to focus twice. 525 main_web_contents_->SetInitialFocus(); 526 527 PrefsTabHelper::CreateForWebContents(main_web_contents_); 528 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs(); 529 530 DoAction(action); 531 return; 532 } 533 534 // Avoid consecutive window switching if the devtools window has been opened 535 // and the Inspect Element shortcut is pressed in the inspected tab. 536 bool should_show_window = 537 !browser_ || (action.type() != DevToolsToggleAction::kInspect); 538 539 if (!browser_) 540 CreateDevToolsBrowser(); 541 542 if (should_show_window) { 543 browser_->window()->Show(); 544 main_web_contents_->SetInitialFocus(); 545 } 546 if (toolbox_web_contents_) 547 UpdateBrowserWindow(); 548 549 DoAction(action); 550 } 551 552 // static 553 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents, 554 bool proceed, bool* proceed_to_fire_unload) { 555 DevToolsWindow* window = AsDevToolsWindow( 556 frontend_contents->GetRenderViewHost()); 557 if (!window) 558 return false; 559 if (!window->intercepted_page_beforeunload_) 560 return false; 561 window->BeforeUnloadFired(frontend_contents, proceed, 562 proceed_to_fire_unload); 563 return true; 564 } 565 566 // static 567 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) { 568 DevToolsWindow* window = 569 DevToolsWindow::GetInstanceForInspectedRenderViewHost( 570 contents->GetRenderViewHost()); 571 if (!window || window->intercepted_page_beforeunload_) 572 return false; 573 574 // Not yet loaded frontend will not handle beforeunload. 575 if (window->load_state_ != kLoadCompleted) 576 return false; 577 578 window->intercepted_page_beforeunload_ = true; 579 // Handle case of devtools inspecting another devtools instance by passing 580 // the call up to the inspecting devtools instance. 581 if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) { 582 window->main_web_contents_->DispatchBeforeUnload(false); 583 } 584 return true; 585 } 586 587 // static 588 bool DevToolsWindow::NeedsToInterceptBeforeUnload( 589 WebContents* contents) { 590 DevToolsWindow* window = 591 DevToolsWindow::GetInstanceForInspectedRenderViewHost( 592 contents->GetRenderViewHost()); 593 return window && !window->intercepted_page_beforeunload_; 594 } 595 596 // static 597 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser( 598 Browser* browser) { 599 DCHECK(browser->is_devtools()); 600 // When FastUnloadController is used, devtools frontend will be detached 601 // from the browser window at this point which means we've already fired 602 // beforeunload. 603 if (browser->tab_strip_model()->empty()) 604 return true; 605 WebContents* contents = 606 browser->tab_strip_model()->GetWebContentsAt(0); 607 DevToolsWindow* window = AsDevToolsWindow(contents->GetRenderViewHost()); 608 if (!window) 609 return false; 610 return window->intercepted_page_beforeunload_; 611 } 612 613 // static 614 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) { 615 DevToolsWindow *window = 616 DevToolsWindow::GetInstanceForInspectedRenderViewHost( 617 contents->GetRenderViewHost()); 618 if (!window) 619 return; 620 window->intercepted_page_beforeunload_ = false; 621 // Propagate to devtools opened on devtools if any. 622 DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_); 623 } 624 625 DevToolsWindow::DevToolsWindow(Profile* profile, 626 const GURL& url, 627 content::RenderViewHost* inspected_rvh, 628 bool can_dock) 629 : profile_(profile), 630 main_web_contents_( 631 WebContents::Create(WebContents::CreateParams(profile))), 632 toolbox_web_contents_(NULL), 633 bindings_(NULL), 634 browser_(NULL), 635 is_docked_(true), 636 can_dock_(can_dock), 637 // This initialization allows external front-end to work without changes. 638 // We don't wait for docking call, but instead immediately show undocked. 639 // Passing "dockSide=undocked" parameter ensures proper UI. 640 load_state_(can_dock ? kNotLoaded : kIsDockedSet), 641 action_on_load_(DevToolsToggleAction::NoOp()), 642 ignore_set_is_docked_(false), 643 intercepted_page_beforeunload_(false) { 644 // Set up delegate, so we get fully-functional window immediately. 645 // It will not appear in UI though until |load_state_ == kLoadCompleted|. 646 main_web_contents_->SetDelegate(this); 647 bindings_ = new DevToolsUIBindings( 648 main_web_contents_, 649 DevToolsUIBindings::ApplyThemeToURL(profile, url)); 650 // Bindings take ownership over devtools as its delegate. 651 bindings_->SetDelegate(this); 652 653 g_instances.Get().push_back(this); 654 655 // There is no inspected_rvh in case of shared workers. 656 if (inspected_rvh) 657 inspected_contents_observer_.reset(new InspectedWebContentsObserver( 658 content::WebContents::FromRenderViewHost(inspected_rvh))); 659 event_forwarder_.reset(new DevToolsEventForwarder(this)); 660 } 661 662 // static 663 DevToolsWindow* DevToolsWindow::Create( 664 Profile* profile, 665 const GURL& frontend_url, 666 content::RenderViewHost* inspected_rvh, 667 bool shared_worker_frontend, 668 bool external_frontend, 669 bool can_dock) { 670 if (inspected_rvh) { 671 // Check for a place to dock. 672 Browser* browser = NULL; 673 int tab; 674 WebContents* inspected_web_contents = 675 content::WebContents::FromRenderViewHost(inspected_rvh); 676 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents, 677 &browser, &tab) || 678 inspected_rvh->GetMainFrame()->IsCrossProcessSubframe() || 679 browser->is_type_popup()) { 680 can_dock = false; 681 } 682 } 683 684 // Create WebContents with devtools. 685 GURL url(GetDevToolsURL(profile, frontend_url, 686 shared_worker_frontend, 687 external_frontend, 688 can_dock)); 689 return new DevToolsWindow(profile, url, inspected_rvh, can_dock); 690 } 691 692 // static 693 GURL DevToolsWindow::GetDevToolsURL(Profile* profile, 694 const GURL& base_url, 695 bool shared_worker_frontend, 696 bool external_frontend, 697 bool can_dock) { 698 // Compatibility errors are encoded with data urls, pass them 699 // through with no decoration. 700 if (base_url.SchemeIs("data")) 701 return base_url; 702 703 std::string frontend_url( 704 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec()); 705 std::string url_string( 706 frontend_url + 707 ((frontend_url.find("?") == std::string::npos) ? "?" : "&")); 708 if (shared_worker_frontend) 709 url_string += "&isSharedWorker=true"; 710 if (external_frontend) 711 url_string += "&remoteFrontend=true"; 712 if (can_dock) 713 url_string += "&can_dock=true"; 714 return GURL(url_string); 715 } 716 717 // static 718 DevToolsWindow* DevToolsWindow::FindDevToolsWindow( 719 DevToolsAgentHost* agent_host) { 720 DevToolsWindows* instances = g_instances.Pointer(); 721 content::DevToolsManager* manager = content::DevToolsManager::GetInstance(); 722 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); 723 ++it) { 724 if (manager->GetDevToolsAgentHostFor((*it)->bindings_->frontend_host()) == 725 agent_host) 726 return *it; 727 } 728 return NULL; 729 } 730 731 // static 732 DevToolsWindow* DevToolsWindow::AsDevToolsWindow( 733 content::RenderViewHost* window_rvh) { 734 if (g_instances == NULL) 735 return NULL; 736 DevToolsWindows* instances = g_instances.Pointer(); 737 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end(); 738 ++it) { 739 if ((*it)->main_web_contents_->GetRenderViewHost() == window_rvh) 740 return *it; 741 } 742 return NULL; 743 } 744 745 WebContents* DevToolsWindow::OpenURLFromTab( 746 WebContents* source, 747 const content::OpenURLParams& params) { 748 DCHECK(source == main_web_contents_); 749 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) { 750 WebContents* inspected_web_contents = GetInspectedWebContents(); 751 return inspected_web_contents ? 752 inspected_web_contents->OpenURL(params) : NULL; 753 } 754 755 content::DevToolsManager* manager = content::DevToolsManager::GetInstance(); 756 scoped_refptr<DevToolsAgentHost> agent_host( 757 manager->GetDevToolsAgentHostFor(bindings_->frontend_host())); 758 if (!agent_host.get()) 759 return NULL; 760 manager->ClientHostClosing(bindings_->frontend_host()); 761 manager->RegisterDevToolsClientHostFor(agent_host.get(), 762 bindings_->frontend_host()); 763 764 content::NavigationController::LoadURLParams load_url_params(params.url); 765 main_web_contents_->GetController().LoadURLWithParams(load_url_params); 766 return main_web_contents_; 767 } 768 769 void DevToolsWindow::ActivateContents(WebContents* contents) { 770 if (is_docked_) { 771 WebContents* inspected_tab = GetInspectedWebContents(); 772 inspected_tab->GetDelegate()->ActivateContents(inspected_tab); 773 } else { 774 browser_->window()->Activate(); 775 } 776 } 777 778 void DevToolsWindow::AddNewContents(WebContents* source, 779 WebContents* new_contents, 780 WindowOpenDisposition disposition, 781 const gfx::Rect& initial_pos, 782 bool user_gesture, 783 bool* was_blocked) { 784 if (new_contents == toolbox_web_contents_) { 785 toolbox_web_contents_->SetDelegate( 786 new DevToolsToolboxDelegate(toolbox_web_contents_)); 787 gfx::Size size = main_web_contents_->GetViewBounds().size(); 788 if (toolbox_web_contents_->GetRenderWidgetHostView()) 789 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size); 790 UpdateBrowserWindow(); 791 return; 792 } 793 794 WebContents* inspected_web_contents = GetInspectedWebContents(); 795 if (inspected_web_contents) { 796 inspected_web_contents->GetDelegate()->AddNewContents( 797 source, new_contents, disposition, initial_pos, user_gesture, 798 was_blocked); 799 } 800 } 801 802 void DevToolsWindow::WebContentsCreated(WebContents* source_contents, 803 int opener_render_frame_id, 804 const base::string16& frame_name, 805 const GURL& target_url, 806 WebContents* new_contents) { 807 if (target_url.SchemeIs(content::kChromeDevToolsScheme) && 808 target_url.query().find("toolbox=true") != std::string::npos) { 809 CHECK(can_dock_); 810 toolbox_web_contents_ = new_contents; 811 } 812 } 813 814 void DevToolsWindow::CloseContents(WebContents* source) { 815 CHECK(is_docked_); 816 // Do this first so that when GetDockedInstanceForInspectedTab is called 817 // from UpdateDevTools it won't return this instance 818 // see crbug.com/372504 819 content::DevToolsManager::GetInstance()->ClientHostClosing( 820 bindings_->frontend_host()); 821 // This will prevent any activity after frontend is loaded. 822 action_on_load_ = DevToolsToggleAction::NoOp(); 823 ignore_set_is_docked_ = true; 824 UpdateBrowserWindow(); 825 // In case of docked main_web_contents_, we own it so delete here. 826 // Embedding DevTools window will be deleted as a result of 827 // DevToolsUIBindings destruction. 828 delete main_web_contents_; 829 } 830 831 void DevToolsWindow::ContentsZoomChange(bool zoom_in) { 832 DCHECK(is_docked_); 833 chrome_page_zoom::Zoom(main_web_contents_, 834 zoom_in ? content::PAGE_ZOOM_IN : content::PAGE_ZOOM_OUT); 835 } 836 837 void DevToolsWindow::BeforeUnloadFired(WebContents* tab, 838 bool proceed, 839 bool* proceed_to_fire_unload) { 840 if (!intercepted_page_beforeunload_) { 841 // Docked devtools window closed directly. 842 if (proceed) { 843 content::DevToolsManager::GetInstance()->ClientHostClosing( 844 bindings_->frontend_host()); 845 } 846 *proceed_to_fire_unload = proceed; 847 } else { 848 // Inspected page is attempting to close. 849 WebContents* inspected_web_contents = GetInspectedWebContents(); 850 if (proceed) { 851 inspected_web_contents->DispatchBeforeUnload(false); 852 } else { 853 bool should_proceed; 854 inspected_web_contents->GetDelegate()->BeforeUnloadFired( 855 inspected_web_contents, false, &should_proceed); 856 DCHECK(!should_proceed); 857 } 858 *proceed_to_fire_unload = false; 859 } 860 } 861 862 bool DevToolsWindow::PreHandleKeyboardEvent( 863 WebContents* source, 864 const content::NativeWebKeyboardEvent& event, 865 bool* is_keyboard_shortcut) { 866 if (is_docked_) { 867 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 868 if (inspected_window) { 869 return inspected_window->PreHandleKeyboardEvent(event, 870 is_keyboard_shortcut); 871 } 872 } 873 return false; 874 } 875 876 void DevToolsWindow::HandleKeyboardEvent( 877 WebContents* source, 878 const content::NativeWebKeyboardEvent& event) { 879 if (is_docked_) { 880 if (event.windowsKeyCode == 0x08) { 881 // Do not navigate back in history on Windows (http://crbug.com/74156). 882 return; 883 } 884 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 885 if (inspected_window) 886 inspected_window->HandleKeyboardEvent(event); 887 } 888 } 889 890 content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() { 891 WebContents* inspected_web_contents = GetInspectedWebContents(); 892 return (inspected_web_contents && inspected_web_contents->GetDelegate()) ? 893 inspected_web_contents->GetDelegate()->GetJavaScriptDialogManager() : 894 content::WebContentsDelegate::GetJavaScriptDialogManager(); 895 } 896 897 content::ColorChooser* DevToolsWindow::OpenColorChooser( 898 WebContents* web_contents, 899 SkColor initial_color, 900 const std::vector<content::ColorSuggestion>& suggestions) { 901 return chrome::ShowColorChooser(web_contents, initial_color); 902 } 903 904 void DevToolsWindow::RunFileChooser(WebContents* web_contents, 905 const content::FileChooserParams& params) { 906 FileSelectHelper::RunFileChooser(web_contents, params); 907 } 908 909 void DevToolsWindow::WebContentsFocused(WebContents* contents) { 910 Browser* inspected_browser = NULL; 911 int inspected_tab_index = -1; 912 if (is_docked_ && FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), 913 &inspected_browser, 914 &inspected_tab_index)) 915 inspected_browser->window()->WebContentsFocused(contents); 916 } 917 918 bool DevToolsWindow::PreHandleGestureEvent( 919 WebContents* source, 920 const blink::WebGestureEvent& event) { 921 // Disable pinch zooming. 922 return event.type == blink::WebGestureEvent::GesturePinchBegin || 923 event.type == blink::WebGestureEvent::GesturePinchUpdate || 924 event.type == blink::WebGestureEvent::GesturePinchEnd; 925 } 926 927 void DevToolsWindow::ActivateWindow() { 928 if (is_docked_ && GetInspectedBrowserWindow()) 929 main_web_contents_->Focus(); 930 else if (!is_docked_ && !browser_->window()->IsActive()) 931 browser_->window()->Activate(); 932 } 933 934 void DevToolsWindow::CloseWindow() { 935 DCHECK(is_docked_); 936 // This will prevent any activity after frontend is loaded. 937 action_on_load_ = DevToolsToggleAction::NoOp(); 938 ignore_set_is_docked_ = true; 939 main_web_contents_->DispatchBeforeUnload(false); 940 } 941 942 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) { 943 DevToolsContentsResizingStrategy strategy(rect); 944 if (contents_resizing_strategy_.Equals(strategy)) 945 return; 946 947 contents_resizing_strategy_.CopyFrom(strategy); 948 UpdateBrowserWindow(); 949 } 950 951 void DevToolsWindow::SetContentsResizingStrategy( 952 const gfx::Insets& insets, const gfx::Size& min_size) { 953 DevToolsContentsResizingStrategy strategy(insets, min_size); 954 if (contents_resizing_strategy_.Equals(strategy)) 955 return; 956 957 contents_resizing_strategy_.CopyFrom(strategy); 958 UpdateBrowserWindow(); 959 } 960 961 void DevToolsWindow::InspectElementCompleted() { 962 if (!inspect_element_start_time_.is_null()) { 963 UMA_HISTOGRAM_TIMES("DevTools.InspectElement", 964 base::TimeTicks::Now() - inspect_element_start_time_); 965 inspect_element_start_time_ = base::TimeTicks(); 966 } 967 } 968 969 void DevToolsWindow::MoveWindow(int x, int y) { 970 if (!is_docked_) { 971 gfx::Rect bounds = browser_->window()->GetBounds(); 972 bounds.Offset(x, y); 973 browser_->window()->SetBounds(bounds); 974 } 975 } 976 977 void DevToolsWindow::SetIsDockedAndShowImmediatelyForTest(bool is_docked) { 978 DCHECK(!is_docked || can_dock_); 979 if (load_state_ == kLoadCompleted) { 980 SetIsDocked(is_docked); 981 } else { 982 is_docked_ = is_docked; 983 // Load is completed when both kIsDockedSet and kOnLoadFired happened. 984 // Note that kIsDockedSet may be already set when can_dock_ is false. 985 load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet; 986 // Note that action_on_load_ will be performed after the load is actually 987 // completed. For now, just show the window. 988 Show(DevToolsToggleAction::Show()); 989 if (load_state_ == kLoadCompleted) 990 LoadCompleted(); 991 } 992 ignore_set_is_docked_ = true; 993 } 994 995 void DevToolsWindow::SetIsDocked(bool dock_requested) { 996 if (ignore_set_is_docked_) 997 return; 998 999 DCHECK(can_dock_ || !dock_requested); 1000 if (!can_dock_) 1001 dock_requested = false; 1002 1003 bool was_docked = is_docked_; 1004 is_docked_ = dock_requested; 1005 1006 if (load_state_ != kLoadCompleted) { 1007 // This is a first time call we waited for to initialize. 1008 load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet; 1009 if (load_state_ == kLoadCompleted) 1010 LoadCompleted(); 1011 return; 1012 } 1013 1014 if (dock_requested == was_docked) 1015 return; 1016 1017 if (dock_requested && !was_docked) { 1018 // Detach window from the external devtools browser. It will lead to 1019 // the browser object's close and delete. Remove observer first. 1020 TabStripModel* tab_strip_model = browser_->tab_strip_model(); 1021 tab_strip_model->DetachWebContentsAt( 1022 tab_strip_model->GetIndexOfWebContents(main_web_contents_)); 1023 browser_ = NULL; 1024 } else if (!dock_requested && was_docked) { 1025 UpdateBrowserWindow(); 1026 } 1027 1028 Show(DevToolsToggleAction::Show()); 1029 } 1030 1031 void DevToolsWindow::OpenInNewTab(const std::string& url) { 1032 content::OpenURLParams params( 1033 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB, 1034 content::PAGE_TRANSITION_LINK, false); 1035 WebContents* inspected_web_contents = GetInspectedWebContents(); 1036 if (inspected_web_contents) { 1037 inspected_web_contents->OpenURL(params); 1038 } else { 1039 chrome::HostDesktopType host_desktop_type; 1040 if (browser_) { 1041 host_desktop_type = browser_->host_desktop_type(); 1042 } else { 1043 // There should always be a browser when there are no inspected web 1044 // contents. 1045 NOTREACHED(); 1046 host_desktop_type = chrome::GetActiveDesktop(); 1047 } 1048 1049 const BrowserList* browser_list = 1050 BrowserList::GetInstance(host_desktop_type); 1051 for (BrowserList::const_iterator it = browser_list->begin(); 1052 it != browser_list->end(); ++it) { 1053 if ((*it)->type() == Browser::TYPE_TABBED) { 1054 (*it)->OpenURL(params); 1055 break; 1056 } 1057 } 1058 } 1059 } 1060 1061 void DevToolsWindow::SetWhitelistedShortcuts( 1062 const std::string& message) { 1063 event_forwarder_->SetWhitelistedShortcuts(message); 1064 } 1065 1066 void DevToolsWindow::InspectedContentsClosing() { 1067 intercepted_page_beforeunload_ = false; 1068 // This will prevent any activity after frontend is loaded. 1069 action_on_load_ = DevToolsToggleAction::NoOp(); 1070 ignore_set_is_docked_ = true; 1071 main_web_contents_->GetRenderViewHost()->ClosePage(); 1072 } 1073 1074 InfoBarService* DevToolsWindow::GetInfoBarService() { 1075 return is_docked_ ? 1076 InfoBarService::FromWebContents(GetInspectedWebContents()) : 1077 InfoBarService::FromWebContents(main_web_contents_); 1078 } 1079 1080 void DevToolsWindow::RenderProcessGone() { 1081 // Docked DevToolsWindow owns its main_web_contents_ and must delete it. 1082 // Undocked main_web_contents_ are owned and handled by browser. 1083 // see crbug.com/369932 1084 if (is_docked_) 1085 CloseContents(main_web_contents_); 1086 } 1087 1088 void DevToolsWindow::OnLoadCompleted() { 1089 // First seed inspected tab id for extension APIs. 1090 WebContents* inspected_web_contents = GetInspectedWebContents(); 1091 if (inspected_web_contents) { 1092 SessionTabHelper* session_tab_helper = 1093 SessionTabHelper::FromWebContents(inspected_web_contents); 1094 if (session_tab_helper) { 1095 base::FundamentalValue tabId(session_tab_helper->session_id().id()); 1096 bindings_->CallClientFunction("WebInspector.setInspectedTabId", 1097 &tabId, NULL, NULL); 1098 } 1099 } 1100 1101 // We could be in kLoadCompleted state already if frontend reloads itself. 1102 if (load_state_ != kLoadCompleted) { 1103 // Load is completed when both kIsDockedSet and kOnLoadFired happened. 1104 // Here we set kOnLoadFired. 1105 load_state_ = load_state_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired; 1106 } 1107 if (load_state_ == kLoadCompleted) 1108 LoadCompleted(); 1109 } 1110 1111 void DevToolsWindow::CreateDevToolsBrowser() { 1112 std::string wp_key = GetDevToolsWindowPlacementPrefKey(); 1113 PrefService* prefs = profile_->GetPrefs(); 1114 const base::DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str()); 1115 if (!wp_pref || wp_pref->empty()) { 1116 DictionaryPrefUpdate update(prefs, wp_key.c_str()); 1117 base::DictionaryValue* defaults = update.Get(); 1118 defaults->SetInteger("left", 100); 1119 defaults->SetInteger("top", 100); 1120 defaults->SetInteger("right", 740); 1121 defaults->SetInteger("bottom", 740); 1122 defaults->SetBoolean("maximized", false); 1123 defaults->SetBoolean("always_on_top", false); 1124 } 1125 1126 browser_ = new Browser(Browser::CreateParams::CreateForDevTools( 1127 profile_, 1128 chrome::GetHostDesktopTypeForNativeView( 1129 main_web_contents_->GetNativeView()))); 1130 browser_->tab_strip_model()->AddWebContents( 1131 main_web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL, 1132 TabStripModel::ADD_ACTIVE); 1133 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs(); 1134 } 1135 1136 // static 1137 bool DevToolsWindow::FindInspectedBrowserAndTabIndex( 1138 WebContents* inspected_web_contents, Browser** browser, int* tab) { 1139 if (!inspected_web_contents) 1140 return false; 1141 1142 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 1143 int tab_index = it->tab_strip_model()->GetIndexOfWebContents( 1144 inspected_web_contents); 1145 if (tab_index != TabStripModel::kNoTab) { 1146 *browser = *it; 1147 *tab = tab_index; 1148 return true; 1149 } 1150 } 1151 return false; 1152 } 1153 1154 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { 1155 Browser* browser = NULL; 1156 int tab; 1157 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(), 1158 &browser, &tab) ? 1159 browser->window() : NULL; 1160 } 1161 1162 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) { 1163 switch (action.type()) { 1164 case DevToolsToggleAction::kShowConsole: 1165 bindings_->CallClientFunction( 1166 "InspectorFrontendAPI.showConsole", NULL, NULL, NULL); 1167 break; 1168 1169 case DevToolsToggleAction::kInspect: 1170 bindings_->CallClientFunction( 1171 "InspectorFrontendAPI.enterInspectElementMode", NULL, NULL, NULL); 1172 break; 1173 1174 case DevToolsToggleAction::kShow: 1175 case DevToolsToggleAction::kToggle: 1176 // Do nothing. 1177 break; 1178 1179 case DevToolsToggleAction::kReveal: { 1180 const DevToolsToggleAction::RevealParams* params = 1181 action.params(); 1182 CHECK(params); 1183 base::StringValue url_value(params->url); 1184 base::FundamentalValue line_value(static_cast<int>(params->line_number)); 1185 base::FundamentalValue column_value( 1186 static_cast<int>(params->column_number)); 1187 bindings_->CallClientFunction("InspectorFrontendAPI.revealSourceLine", 1188 &url_value, &line_value, &column_value); 1189 break; 1190 } 1191 default: 1192 NOTREACHED(); 1193 break; 1194 } 1195 } 1196 1197 void DevToolsWindow::UpdateBrowserToolbar() { 1198 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 1199 if (inspected_window) 1200 inspected_window->UpdateToolbar(NULL); 1201 } 1202 1203 void DevToolsWindow::UpdateBrowserWindow() { 1204 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 1205 if (inspected_window) 1206 inspected_window->UpdateDevTools(); 1207 } 1208 1209 WebContents* DevToolsWindow::GetInspectedWebContents() { 1210 return inspected_contents_observer_ ? 1211 inspected_contents_observer_->web_contents() : NULL; 1212 } 1213 1214 void DevToolsWindow::LoadCompleted() { 1215 Show(action_on_load_); 1216 action_on_load_ = DevToolsToggleAction::NoOp(); 1217 if (!load_completed_callback_.is_null()) { 1218 load_completed_callback_.Run(); 1219 load_completed_callback_ = base::Closure(); 1220 } 1221 } 1222 1223 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) { 1224 if (load_state_ == kLoadCompleted) { 1225 if (!closure.is_null()) 1226 closure.Run(); 1227 return; 1228 } 1229 load_completed_callback_ = closure; 1230 } 1231 1232 bool DevToolsWindow::ForwardKeyboardEvent( 1233 const content::NativeWebKeyboardEvent& event) { 1234 return event_forwarder_->ForwardEvent(event); 1235 } 1236