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