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