devtools_window.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/command_line.h" 10#include "base/json/json_writer.h" 11#include "base/lazy_instance.h" 12#include "base/stringprintf.h" 13#include "base/strings/string_number_conversions.h" 14#include "base/utf_string_conversions.h" 15#include "base/values.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/extensions/api/debugger/debugger_api.h" 18#include "chrome/browser/extensions/extension_service.h" 19#include "chrome/browser/extensions/extension_system.h" 20#include "chrome/browser/file_select_helper.h" 21#include "chrome/browser/infobars/confirm_infobar_delegate.h" 22#include "chrome/browser/prefs/pref_service_syncable.h" 23#include "chrome/browser/prefs/scoped_user_pref_update.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/sessions/session_tab_helper.h" 26#include "chrome/browser/themes/theme_properties.h" 27#include "chrome/browser/themes/theme_service.h" 28#include "chrome/browser/themes/theme_service_factory.h" 29#include "chrome/browser/ui/browser.h" 30#include "chrome/browser/ui/browser_iterator.h" 31#include "chrome/browser/ui/browser_list.h" 32#include "chrome/browser/ui/browser_window.h" 33#include "chrome/browser/ui/host_desktop.h" 34#include "chrome/browser/ui/prefs/prefs_tab_helper.h" 35#include "chrome/browser/ui/tabs/tab_strip_model.h" 36#include "chrome/browser/ui/webui/devtools_ui.h" 37#include "chrome/common/chrome_notification_types.h" 38#include "chrome/common/chrome_switches.h" 39#include "chrome/common/extensions/manifest_url_handler.h" 40#include "chrome/common/pref_names.h" 41#include "chrome/common/render_messages.h" 42#include "chrome/common/url_constants.h" 43#include "components/user_prefs/pref_registry_syncable.h" 44#include "content/public/browser/child_process_security_policy.h" 45#include "content/public/browser/devtools_agent_host.h" 46#include "content/public/browser/devtools_client_host.h" 47#include "content/public/browser/devtools_manager.h" 48#include "content/public/browser/favicon_status.h" 49#include "content/public/browser/load_notification_details.h" 50#include "content/public/browser/navigation_controller.h" 51#include "content/public/browser/navigation_entry.h" 52#include "content/public/browser/notification_source.h" 53#include "content/public/browser/render_process_host.h" 54#include "content/public/browser/render_view_host.h" 55#include "content/public/browser/web_contents.h" 56#include "content/public/browser/web_contents_observer.h" 57#include "content/public/browser/web_contents_view.h" 58#include "content/public/common/bindings_policy.h" 59#include "content/public/common/content_client.h" 60#include "content/public/common/page_transition_types.h" 61#include "content/public/common/url_constants.h" 62#include "grit/generated_resources.h" 63#include "ui/base/l10n/l10n_util.h" 64 65typedef std::vector<DevToolsWindow*> DevToolsWindowList; 66namespace { 67base::LazyInstance<DevToolsWindowList>::Leaky 68 g_instances = LAZY_INSTANCE_INITIALIZER; 69} // namespace 70 71using base::Bind; 72using base::Callback; 73using content::DevToolsAgentHost; 74using content::DevToolsClientHost; 75using content::DevToolsManager; 76using content::FileChooserParams; 77using content::NativeWebKeyboardEvent; 78using content::NavigationController; 79using content::NavigationEntry; 80using content::OpenURLParams; 81using content::RenderViewHost; 82using content::WebContents; 83 84const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; 85 86const char kOldPrefBottom[] = "bottom"; 87const char kOldPrefRight[] = "right"; 88 89const char kPrefBottom[] = "dock_bottom"; 90const char kPrefRight[] = "dock_right"; 91const char kPrefUndocked[] = "undocked"; 92 93const char kDockSideBottom[] = "bottom"; 94const char kDockSideRight[] = "right"; 95const char kDockSideUndocked[] = "undocked"; 96const char kDockSideMinimized[] = "minimized"; 97 98// Minimal height of devtools pane or content pane when devtools are docked 99// to the browser window. 100const int kMinDevToolsHeight = 50; 101const int kMinDevToolsWidth = 150; 102const int kMinContentsSize = 50; 103const int kMinimizedDevToolsHeight = 24; 104 105class DevToolsWindow::InspectedWebContentsObserver 106 : public content::WebContentsObserver { 107 public: 108 explicit InspectedWebContentsObserver(content::WebContents* web_contents) 109 : WebContentsObserver(web_contents) { 110 } 111 112 content::WebContents* Get() { return web_contents(); } 113}; 114 115class DevToolsWindow::FrontendWebContentsObserver 116 : public content::WebContentsObserver { 117 public: 118 explicit FrontendWebContentsObserver(content::WebContents* web_contents) 119 : WebContentsObserver(web_contents) { 120 } 121 private: 122 // Overriden from contents::WebContentsObserver. 123 virtual void AboutToNavigateRenderView( 124 RenderViewHost* render_view_host) OVERRIDE { 125 content::DevToolsClientHost::SetupDevToolsFrontendClient(render_view_host); 126 } 127}; 128 129typedef Callback<void(bool)> ConfirmInfoBarCallback; 130 131class DevToolsConfirmInfoBarDelegate : public ConfirmInfoBarDelegate { 132 public: 133 DevToolsConfirmInfoBarDelegate( 134 InfoBarService* infobar_service, 135 const ConfirmInfoBarCallback& callback, 136 string16 message) 137 : ConfirmInfoBarDelegate(infobar_service), 138 callback_(callback), 139 message_(message) { 140 } 141 142 virtual string16 GetMessageText() const OVERRIDE { return message_; } 143 144 virtual bool Accept() OVERRIDE { 145 callback_.Run(true); 146 callback_.Reset(); 147 return true; 148 } 149 150 virtual bool Cancel() OVERRIDE { 151 callback_.Run(false); 152 callback_.Reset(); 153 return true; 154 } 155 156 virtual string16 GetButtonLabel(InfoBarButton button) const OVERRIDE { 157 return l10n_util::GetStringUTF16((button == BUTTON_OK) 158 ? IDS_DEV_TOOLS_CONFIRM_ALLOW_BUTTON 159 : IDS_DEV_TOOLS_CONFIRM_DENY_BUTTON); 160 } 161 162 private: 163 virtual ~DevToolsConfirmInfoBarDelegate() { 164 if (!callback_.is_null()) { 165 callback_.Run(false); 166 } 167 } 168 169 ConfirmInfoBarCallback callback_; 170 string16 message_; 171}; 172 173// static 174std::string DevToolsWindow::GetDevToolsWindowPlacementPrefKey() { 175 std::string wp_key; 176 wp_key.append(prefs::kBrowserWindowPlacement); 177 wp_key.append("_"); 178 wp_key.append(kDevToolsApp); 179 return wp_key; 180} 181 182// static 183void DevToolsWindow::RegisterUserPrefs( 184 user_prefs::PrefRegistrySyncable* registry) { 185 registry->RegisterBooleanPref( 186 prefs::kDevToolsOpenDocked, 187 true, 188 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 189 registry->RegisterStringPref( 190 prefs::kDevToolsDockSide, 191 kDockSideBottom, 192 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 193 registry->RegisterDictionaryPref( 194 prefs::kDevToolsEditedFiles, 195 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 196 registry->RegisterDictionaryPref( 197 prefs::kDevToolsFileSystemPaths, 198 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 199 200 registry->RegisterDictionaryPref( 201 GetDevToolsWindowPlacementPrefKey().c_str(), 202 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 203} 204 205// static 206DevToolsWindow* DevToolsWindow::GetDockedInstanceForInspectedTab( 207 WebContents* inspected_web_contents) { 208 if (!inspected_web_contents) 209 return NULL; 210 211 if (!DevToolsAgentHost::HasFor(inspected_web_contents->GetRenderViewHost())) 212 return NULL; 213 scoped_refptr<DevToolsAgentHost> agent(DevToolsAgentHost::GetOrCreateFor( 214 inspected_web_contents->GetRenderViewHost())); 215 DevToolsWindow* window = FindDevToolsWindow(agent); 216 return window && window->IsDocked() ? window : NULL; 217} 218 219// static 220bool DevToolsWindow::IsDevToolsWindow(RenderViewHost* window_rvh) { 221 return AsDevToolsWindow(window_rvh) != NULL; 222} 223 224// static 225DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker( 226 Profile* profile, 227 DevToolsAgentHost* worker_agent) { 228 DevToolsWindow* window = FindDevToolsWindow(worker_agent); 229 if (!window) { 230 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile); 231 // Will disconnect the current client host if there is one. 232 DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( 233 worker_agent, 234 window->frontend_host_.get()); 235 } 236 window->Show(DEVTOOLS_TOGGLE_ACTION_SHOW); 237 return window; 238} 239 240// static 241DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker( 242 Profile* profile) { 243 return Create(profile, GURL(), NULL, DEVTOOLS_DOCK_SIDE_UNDOCKED, true); 244} 245 246// static 247DevToolsWindow* DevToolsWindow::OpenDevToolsWindow( 248 RenderViewHost* inspected_rvh) { 249 return ToggleDevToolsWindow(inspected_rvh, true, 250 DEVTOOLS_TOGGLE_ACTION_SHOW); 251} 252 253// static 254DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( 255 Browser* browser, 256 DevToolsToggleAction action) { 257 if (action == DEVTOOLS_TOGGLE_ACTION_TOGGLE && browser->is_devtools()) { 258 browser->tab_strip_model()->CloseAllTabs(); 259 return NULL; 260 } 261 RenderViewHost* inspected_rvh = 262 browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(); 263 264 return ToggleDevToolsWindow(inspected_rvh, 265 action == DEVTOOLS_TOGGLE_ACTION_INSPECT, 266 action); 267} 268 269// static 270void DevToolsWindow::InspectElement(RenderViewHost* inspected_rvh, 271 int x, 272 int y) { 273 scoped_refptr<DevToolsAgentHost> agent( 274 DevToolsAgentHost::GetOrCreateFor(inspected_rvh)); 275 agent->InspectElement(x, y); 276 // TODO(loislo): we should initiate DevTools window opening from within 277 // renderer. Otherwise, we still can hit a race condition here. 278 OpenDevToolsWindow(inspected_rvh); 279} 280 281// static 282void DevToolsWindow::OpenExternalFrontend( 283 Profile* profile, 284 const std::string& frontend_url, 285 content::DevToolsAgentHost* agent_host) { 286 DevToolsWindow* window = FindDevToolsWindow(agent_host); 287 if (!window) { 288 window = Create(profile, DevToolsUI::GetProxyURL(frontend_url), NULL, 289 DEVTOOLS_DOCK_SIDE_UNDOCKED, false); 290 DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor( 291 agent_host, window->frontend_host_.get()); 292 } 293 window->Show(DEVTOOLS_TOGGLE_ACTION_SHOW); 294} 295 296// static 297DevToolsWindow* DevToolsWindow::Create( 298 Profile* profile, 299 const GURL& frontend_url, 300 RenderViewHost* inspected_rvh, 301 DevToolsDockSide dock_side, 302 bool shared_worker_frontend) { 303 // Create WebContents with devtools. 304 GURL url = GetDevToolsURL(profile, frontend_url, dock_side, 305 shared_worker_frontend); 306 return new DevToolsWindow(profile, url, inspected_rvh, dock_side); 307} 308 309DevToolsWindow::DevToolsWindow(Profile* profile, 310 const GURL& url, 311 RenderViewHost* inspected_rvh, 312 DevToolsDockSide dock_side) 313 : profile_(profile), 314 browser_(NULL), 315 dock_side_(dock_side), 316 is_loaded_(false), 317 action_on_load_(DEVTOOLS_TOGGLE_ACTION_SHOW), 318 weak_factory_(this), 319 width_(-1), 320 height_(-1), 321 dock_side_before_minimized_(dock_side) { 322 web_contents_ = WebContents::Create(WebContents::CreateParams(profile)); 323 frontend_contents_observer_.reset( 324 new FrontendWebContentsObserver(web_contents_)); 325 326 web_contents_->GetController().LoadURL(url, content::Referrer(), 327 content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); 328 329 RenderViewHost* render_view_host = web_contents_->GetRenderViewHost(); 330 if (url.host() == chrome::kChromeUIDevToolsBundledHost) { 331 // Only allow file scheme in embedded front-end by default. 332 int process_id = render_view_host->GetProcess()->GetID(); 333 content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme( 334 process_id, chrome::kFileScheme); 335 } 336 337 frontend_host_.reset( 338 DevToolsClientHost::CreateDevToolsFrontendHost(web_contents_, this)); 339 file_helper_.reset(new DevToolsFileHelper(web_contents_, profile)); 340 341 g_instances.Get().push_back(this); 342 // Wipe out page icon so that the default application icon is used. 343 NavigationEntry* entry = web_contents_->GetController().GetActiveEntry(); 344 entry->GetFavicon().image = gfx::Image(); 345 entry->GetFavicon().valid = true; 346 347 // Register on-load actions. 348 registrar_.Add( 349 this, 350 content::NOTIFICATION_LOAD_STOP, 351 content::Source<NavigationController>(&web_contents_->GetController())); 352 registrar_.Add( 353 this, 354 chrome::NOTIFICATION_TAB_CLOSING, 355 content::Source<NavigationController>(&web_contents_->GetController())); 356 registrar_.Add( 357 this, 358 chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 359 content::Source<ThemeService>( 360 ThemeServiceFactory::GetForProfile(profile_))); 361 // There is no inspected_rvh in case of shared workers. 362 if (inspected_rvh) 363 inspected_contents_observer_.reset(new InspectedWebContentsObserver( 364 WebContents::FromRenderViewHost(inspected_rvh))); 365} 366 367DevToolsWindow::~DevToolsWindow() { 368 DevToolsWindowList& instances = g_instances.Get(); 369 DevToolsWindowList::iterator it = std::find(instances.begin(), 370 instances.end(), 371 this); 372 DCHECK(it != instances.end()); 373 instances.erase(it); 374} 375 376content::WebContents* DevToolsWindow::GetInspectedWebContents() { 377 if (!inspected_contents_observer_) 378 return NULL; 379 return inspected_contents_observer_->Get(); 380} 381 382void DevToolsWindow::InspectedContentsClosing() { 383 Hide(); 384} 385 386void DevToolsWindow::Hide() { 387 if (IsDocked()) { 388 // Update dev tools to reflect removed dev tools window. 389 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 390 if (inspected_window) 391 inspected_window->UpdateDevTools(); 392 // In case of docked web_contents_, we own it so delete here. 393 delete web_contents_; 394 395 delete this; 396 } else { 397 // First, initiate self-destruct to free all the registrars. 398 // Then close all tabs. Browser will take care of deleting web_contents_ 399 // for us. 400 Browser* browser = browser_; 401 delete this; 402 browser->tab_strip_model()->CloseAllTabs(); 403 } 404} 405 406void DevToolsWindow::Show(DevToolsToggleAction action) { 407 if (IsDocked()) { 408 Browser* inspected_browser; 409 int inspected_tab_index; 410 // Tell inspected browser to update splitter and switch to inspected panel. 411 if (!IsInspectedBrowserPopup() && 412 FindInspectedBrowserAndTabIndex(&inspected_browser, 413 &inspected_tab_index)) { 414 BrowserWindow* inspected_window = inspected_browser->window(); 415 web_contents_->SetDelegate(this); 416 inspected_window->UpdateDevTools(); 417 web_contents_->GetView()->SetInitialFocus(); 418 inspected_window->Show(); 419 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model(); 420 tab_strip_model->ActivateTabAt(inspected_tab_index, true); 421 PrefsTabHelper::CreateForWebContents(web_contents_); 422 GetRenderViewHost()->SyncRendererPrefs(); 423 ScheduleAction(action); 424 return; 425 } else { 426 // Sometimes we don't know where to dock. Stay undocked. 427 dock_side_ = DEVTOOLS_DOCK_SIDE_UNDOCKED; 428 } 429 } 430 431 // Avoid consecutive window switching if the devtools window has been opened 432 // and the Inspect Element shortcut is pressed in the inspected tab. 433 bool should_show_window = 434 !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT; 435 436 if (!browser_) 437 CreateDevToolsBrowser(); 438 439 if (should_show_window) { 440 browser_->window()->Show(); 441 web_contents_->GetView()->SetInitialFocus(); 442 } 443 444 ScheduleAction(action); 445} 446 447DevToolsClientHost* DevToolsWindow::GetDevToolsClientHostForTest() { 448 return frontend_host_.get(); 449} 450 451int DevToolsWindow::GetWidth(int container_width) { 452 if (width_ == -1) { 453 width_ = profile_->GetPrefs()-> 454 GetInteger(prefs::kDevToolsVSplitLocation); 455 } 456 457 // By default, size devtools as 1/3 of the browser window. 458 if (width_ == -1) 459 width_ = container_width / 3; 460 461 // Respect the minimum devtools width preset. 462 width_ = std::max(kMinDevToolsWidth, width_); 463 464 // But it should never compromise the content window size unless the entire 465 // window is tiny. 466 width_ = std::min(container_width - kMinContentsSize, width_); 467 return width_; 468} 469 470int DevToolsWindow::GetHeight(int container_height) { 471 if (height_ == -1) { 472 height_ = profile_->GetPrefs()-> 473 GetInteger(prefs::kDevToolsHSplitLocation); 474 } 475 476 // By default, size devtools as 1/3 of the browser window. 477 if (height_ == -1) 478 height_ = container_height / 3; 479 480 // Respect the minimum devtools width preset. 481 height_ = std::max(kMinDevToolsHeight, height_); 482 483 // But it should never compromise the content window size. 484 height_ = std::min(container_height - kMinContentsSize, height_); 485 return height_; 486} 487 488int DevToolsWindow::GetMinimumWidth() { 489 return kMinDevToolsWidth; 490} 491 492int DevToolsWindow::GetMinimumHeight() { 493 return kMinDevToolsHeight; 494} 495 496void DevToolsWindow::SetWidth(int width) { 497 width_ = width; 498 profile_->GetPrefs()->SetInteger(prefs::kDevToolsVSplitLocation, width); 499} 500 501void DevToolsWindow::SetHeight(int height) { 502 height_ = height; 503 profile_->GetPrefs()->SetInteger(prefs::kDevToolsHSplitLocation, height); 504} 505 506int DevToolsWindow::GetMinimizedHeight() { 507 return kMinimizedDevToolsHeight; 508} 509 510RenderViewHost* DevToolsWindow::GetRenderViewHost() { 511 return web_contents_->GetRenderViewHost(); 512} 513 514void DevToolsWindow::CreateDevToolsBrowser() { 515 std::string wp_key = GetDevToolsWindowPlacementPrefKey(); 516 PrefService* prefs = profile_->GetPrefs(); 517 const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str()); 518 if (!wp_pref || wp_pref->empty()) { 519 DictionaryPrefUpdate update(prefs, wp_key.c_str()); 520 DictionaryValue* defaults = update.Get(); 521 defaults->SetInteger("left", 100); 522 defaults->SetInteger("top", 100); 523 defaults->SetInteger("right", 740); 524 defaults->SetInteger("bottom", 740); 525 defaults->SetBoolean("maximized", false); 526 defaults->SetBoolean("always_on_top", false); 527 } 528 529 chrome::HostDesktopType host_desktop_type = 530 chrome::GetHostDesktopTypeForNativeView( 531 web_contents_->GetView()->GetNativeView()); 532 533 browser_ = new Browser(Browser::CreateParams::CreateForDevTools( 534 profile_, host_desktop_type)); 535 browser_->tab_strip_model()->AddWebContents( 536 web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL, 537 TabStripModel::ADD_ACTIVE); 538 GetRenderViewHost()->SyncRendererPrefs(); 539} 540 541bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser, 542 int* tab) { 543 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 544 if (!inspected_web_contents) 545 return false; 546 547 for (chrome::BrowserIterator it; !it.done(); it.Next()) { 548 int tab_index = it->tab_strip_model()->GetIndexOfWebContents( 549 inspected_web_contents); 550 if (tab_index != TabStripModel::kNoTab) { 551 *browser = *it; 552 *tab = tab_index; 553 return true; 554 } 555 } 556 return false; 557} 558 559BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { 560 Browser* browser = NULL; 561 int tab; 562 return FindInspectedBrowserAndTabIndex(&browser, &tab) ? 563 browser->window() : NULL; 564} 565 566bool DevToolsWindow::IsInspectedBrowserPopup() { 567 Browser* browser = NULL; 568 int tab; 569 if (!FindInspectedBrowserAndTabIndex(&browser, &tab)) 570 return false; 571 572 return browser->is_type_popup(); 573} 574 575void DevToolsWindow::UpdateFrontendDockSide() { 576 base::StringValue dock_side(SideToString(dock_side_)); 577 CallClientFunction("InspectorFrontendAPI.setDockSide", &dock_side); 578 base::FundamentalValue docked(IsDocked()); 579 CallClientFunction("InspectorFrontendAPI.setAttachedWindow", &docked); 580} 581 582 583void DevToolsWindow::AddDevToolsExtensionsToClient() { 584 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 585 if (inspected_web_contents) { 586 SessionTabHelper* session_tab_helper = 587 SessionTabHelper::FromWebContents(inspected_web_contents); 588 if (session_tab_helper) { 589 base::FundamentalValue tabId(session_tab_helper->session_id().id()); 590 CallClientFunction("WebInspector.setInspectedTabId", &tabId); 591 } 592 } 593 ListValue results; 594 Profile* profile = 595 Profile::FromBrowserContext(web_contents_->GetBrowserContext()); 596 const ExtensionService* extension_service = extensions::ExtensionSystem::Get( 597 profile->GetOriginalProfile())->extension_service(); 598 if (!extension_service) 599 return; 600 601 const ExtensionSet* extensions = extension_service->extensions(); 602 603 for (ExtensionSet::const_iterator extension = extensions->begin(); 604 extension != extensions->end(); ++extension) { 605 if (extensions::ManifestURL::GetDevToolsPage(*extension).is_empty()) 606 continue; 607 DictionaryValue* extension_info = new DictionaryValue(); 608 extension_info->Set("startPage", new StringValue( 609 extensions::ManifestURL::GetDevToolsPage(*extension).spec())); 610 extension_info->Set("name", new StringValue((*extension)->name())); 611 bool allow_experimental = (*extension)->HasAPIPermission( 612 extensions::APIPermission::kExperimental); 613 extension_info->Set("exposeExperimentalAPIs", 614 new base::FundamentalValue(allow_experimental)); 615 results.Append(extension_info); 616 } 617 CallClientFunction("WebInspector.addExtensions", &results); 618} 619 620WebContents* DevToolsWindow::OpenURLFromTab(WebContents* source, 621 const OpenURLParams& params) { 622 if (!params.url.SchemeIs(chrome::kChromeDevToolsScheme)) { 623 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 624 if (inspected_web_contents) 625 return inspected_web_contents->OpenURL(params); 626 return NULL; 627 } 628 629 DevToolsManager* manager = DevToolsManager::GetInstance(); 630 scoped_refptr<DevToolsAgentHost> agent_host( 631 manager->GetDevToolsAgentHostFor(frontend_host_.get())); 632 if (!agent_host) 633 return NULL; 634 manager->ClientHostClosing(frontend_host_.get()); 635 manager->RegisterDevToolsClientHostFor(agent_host, frontend_host_.get()); 636 637 chrome::NavigateParams nav_params(profile_, params.url, params.transition); 638 FillNavigateParamsFromOpenURLParams(&nav_params, params); 639 nav_params.source_contents = source; 640 nav_params.tabstrip_add_types = TabStripModel::ADD_NONE; 641 nav_params.window_action = chrome::NavigateParams::SHOW_WINDOW; 642 nav_params.user_gesture = true; 643 chrome::Navigate(&nav_params); 644 return nav_params.target_contents; 645} 646 647void DevToolsWindow::CallClientFunction(const std::string& function_name, 648 const Value* arg1, 649 const Value* arg2) { 650 std::string params; 651 if (arg1) { 652 std::string json; 653 base::JSONWriter::Write(arg1, &json); 654 params.append(json); 655 if (arg2) { 656 base::JSONWriter::Write(arg2, &json); 657 params.append(", " + json); 658 } 659 } 660 string16 javascript = ASCIIToUTF16(function_name + "(" + params + ");"); 661 web_contents_->GetRenderViewHost()-> 662 ExecuteJavascriptInWebFrame(string16(), javascript); 663} 664 665void DevToolsWindow::Observe(int type, 666 const content::NotificationSource& source, 667 const content::NotificationDetails& details) { 668 if (type == content::NOTIFICATION_LOAD_STOP && !is_loaded_) { 669 is_loaded_ = true; 670 UpdateTheme(); 671 DoAction(); 672 AddDevToolsExtensionsToClient(); 673 } else if (type == chrome::NOTIFICATION_TAB_CLOSING) { 674 if (content::Source<NavigationController>(source).ptr() == 675 &web_contents_->GetController()) { 676 // This happens when browser closes all of its tabs as a result 677 // of window.Close event. 678 // Notify manager that this DevToolsClientHost no longer exists and 679 // initiate self-destuct here. 680 DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_.get()); 681 UpdateBrowserToolbar(); 682 delete this; 683 } 684 } else if (type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED) { 685 UpdateTheme(); 686 } 687} 688 689void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) { 690 action_on_load_ = action; 691 if (is_loaded_) 692 DoAction(); 693} 694 695void DevToolsWindow::DoAction() { 696 UpdateFrontendDockSide(); 697 switch (action_on_load_) { 698 case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE: 699 CallClientFunction("InspectorFrontendAPI.showConsole", NULL); 700 break; 701 case DEVTOOLS_TOGGLE_ACTION_INSPECT: 702 CallClientFunction("InspectorFrontendAPI.enterInspectElementMode", NULL); 703 case DEVTOOLS_TOGGLE_ACTION_SHOW: 704 case DEVTOOLS_TOGGLE_ACTION_TOGGLE: 705 // Do nothing. 706 break; 707 default: 708 NOTREACHED(); 709 } 710 action_on_load_ = DEVTOOLS_TOGGLE_ACTION_SHOW; 711} 712 713std::string SkColorToRGBAString(SkColor color) { 714 // We convert the alpha using DoubleToString because StringPrintf will use 715 // locale specific formatters (e.g., use , instead of . in German). 716 return base::StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color), 717 SkColorGetG(color), SkColorGetB(color), 718 base::DoubleToString(SkColorGetA(color) / 255.0).c_str()); 719} 720 721// static 722GURL DevToolsWindow::GetDevToolsURL(Profile* profile, 723 const GURL& base_url, 724 DevToolsDockSide dock_side, 725 bool shared_worker_frontend) { 726 ThemeService* tp = ThemeServiceFactory::GetForProfile(profile); 727 CHECK(tp); 728 729 SkColor color_toolbar = 730 tp->GetColor(ThemeProperties::COLOR_TOOLBAR); 731 SkColor color_tab_text = 732 tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT); 733 734 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); 735 bool experiments_enabled = 736 command_line.HasSwitch(switches::kEnableDevToolsExperiments); 737 738 std::string frontend_url = base_url.is_empty() ? 739 chrome::kChromeUIDevToolsURL : base_url.spec(); 740 std::string params_separator = 741 frontend_url.find("?") == std::string::npos ? "?" : "&"; 742 std::string url_string = base::StringPrintf("%s%s" 743 "dockSide=%s&toolbarColor=%s&textColor=%s%s%s", 744 frontend_url.c_str(), 745 params_separator.c_str(), 746 SideToString(dock_side).c_str(), 747 SkColorToRGBAString(color_toolbar).c_str(), 748 SkColorToRGBAString(color_tab_text).c_str(), 749 shared_worker_frontend ? "&isSharedWorker=true" : "", 750 experiments_enabled ? "&experiments=true" : ""); 751 return GURL(url_string); 752} 753 754void DevToolsWindow::UpdateTheme() { 755 ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_); 756 CHECK(tp); 757 758 SkColor color_toolbar = 759 tp->GetColor(ThemeProperties::COLOR_TOOLBAR); 760 SkColor color_tab_text = 761 tp->GetColor(ThemeProperties::COLOR_BOOKMARK_TEXT); 762 std::string command = base::StringPrintf( 763 "InspectorFrontendAPI.setToolbarColors(\"%s\", \"%s\")", 764 SkColorToRGBAString(color_toolbar).c_str(), 765 SkColorToRGBAString(color_tab_text).c_str()); 766 web_contents_->GetRenderViewHost()-> 767 ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(command)); 768} 769 770void DevToolsWindow::AddNewContents(WebContents* source, 771 WebContents* new_contents, 772 WindowOpenDisposition disposition, 773 const gfx::Rect& initial_pos, 774 bool user_gesture, 775 bool* was_blocked) { 776 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 777 if (inspected_web_contents) { 778 inspected_web_contents->GetDelegate()->AddNewContents( 779 source, new_contents, disposition, initial_pos, user_gesture, 780 was_blocked); 781 } 782} 783 784bool DevToolsWindow::PreHandleKeyboardEvent( 785 WebContents* source, 786 const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { 787 if (IsDocked()) { 788 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 789 if (inspected_window) 790 return inspected_window->PreHandleKeyboardEvent( 791 event, is_keyboard_shortcut); 792 } 793 return false; 794} 795 796void DevToolsWindow::HandleKeyboardEvent(WebContents* source, 797 const NativeWebKeyboardEvent& event) { 798 if (IsDocked()) { 799 if (event.windowsKeyCode == 0x08) { 800 // Do not navigate back in history on Windows (http://crbug.com/74156). 801 return; 802 } 803 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 804 if (inspected_window) 805 inspected_window->HandleKeyboardEvent(event); 806 } 807} 808 809// static 810DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow( 811 RenderViewHost* inspected_rvh, 812 bool force_open, 813 DevToolsToggleAction action) { 814 scoped_refptr<DevToolsAgentHost> agent( 815 DevToolsAgentHost::GetOrCreateFor(inspected_rvh)); 816 DevToolsManager* manager = DevToolsManager::GetInstance(); 817 DevToolsWindow* window = FindDevToolsWindow(agent); 818 bool do_open = force_open; 819 if (!window) { 820 Profile* profile = Profile::FromBrowserContext( 821 inspected_rvh->GetProcess()->GetBrowserContext()); 822 DevToolsDockSide dock_side = GetDockSideFromPrefs(profile); 823 window = Create(profile, GURL(), inspected_rvh, dock_side, false); 824 manager->RegisterDevToolsClientHostFor(agent, window->frontend_host_.get()); 825 do_open = true; 826 } 827 828 // Update toolbar to reflect DevTools changes. 829 window->UpdateBrowserToolbar(); 830 831 // If window is docked and visible, we hide it on toggle. If window is 832 // undocked, we show (activate) it. If window is minimized, we maximize it. 833 if (window->dock_side_ == DEVTOOLS_DOCK_SIDE_MINIMIZED) 834 window->Restore(); 835 else if (!window->IsDocked() || do_open) 836 window->Show(action); 837 else 838 window->CloseWindow(); 839 840 return window; 841} 842 843// static 844DevToolsWindow* DevToolsWindow::FindDevToolsWindow( 845 DevToolsAgentHost* agent_host) { 846 DevToolsManager* manager = DevToolsManager::GetInstance(); 847 DevToolsWindowList& instances = g_instances.Get(); 848 for (DevToolsWindowList::iterator it = instances.begin(); 849 it != instances.end(); ++it) { 850 if (manager->GetDevToolsAgentHostFor((*it)->frontend_host_.get()) == 851 agent_host) 852 return *it; 853 } 854 return NULL; 855} 856 857// static 858DevToolsWindow* DevToolsWindow::AsDevToolsWindow(RenderViewHost* window_rvh) { 859 if (g_instances == NULL) 860 return NULL; 861 DevToolsWindowList& instances = g_instances.Get(); 862 for (DevToolsWindowList::iterator it = instances.begin(); 863 it != instances.end(); ++it) { 864 if ((*it)->web_contents_->GetRenderViewHost() == window_rvh) 865 return *it; 866 } 867 return NULL; 868} 869 870void DevToolsWindow::ActivateWindow() { 871 if (!IsDocked()) { 872 if (!browser_->window()->IsActive()) { 873 browser_->window()->Activate(); 874 } 875 } else { 876 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 877 if (inspected_window) 878 web_contents_->GetView()->Focus(); 879 } 880} 881 882void DevToolsWindow::ChangeAttachedWindowHeight(unsigned height) { 883 if (dock_side_ != DEVTOOLS_DOCK_SIDE_BOTTOM) 884 return; 885 886 SetHeight(height); 887 // Update inspected window to adjust heights. 888 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 889 if (inspected_window) 890 inspected_window->UpdateDevTools(); 891} 892 893void DevToolsWindow::CloseWindow() { 894 DCHECK(IsDocked()); 895 DevToolsManager::GetInstance()->ClientHostClosing(frontend_host_.get()); 896 Hide(); 897} 898 899void DevToolsWindow::MoveWindow(int x, int y) { 900 if (!IsDocked()) { 901 gfx::Rect bounds = browser_->window()->GetBounds(); 902 bounds.Offset(x, y); 903 browser_->window()->SetBounds(bounds); 904 } 905} 906 907void DevToolsWindow::SetDockSide(const std::string& side) { 908 DevToolsDockSide requested_side = SideFromString(side); 909 bool dock_requested = requested_side != DEVTOOLS_DOCK_SIDE_UNDOCKED; 910 bool is_docked = IsDocked(); 911 912 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 913 if (dock_requested && (!inspected_web_contents || 914 !GetInspectedBrowserWindow() || IsInspectedBrowserPopup())) { 915 // Cannot dock, avoid window flashing due to close-reopen cycle. 916 return; 917 } 918 919 if (dock_side_ != DEVTOOLS_DOCK_SIDE_MINIMIZED && 920 requested_side == DEVTOOLS_DOCK_SIDE_MINIMIZED) { 921 dock_side_before_minimized_ = dock_side_; 922 } 923 924 dock_side_ = requested_side; 925 if (dock_requested) { 926 if (!is_docked) { 927 // Detach window from the external devtools browser. It will lead to 928 // the browser object's close and delete. Remove observer first. 929 TabStripModel* tab_strip_model = browser_->tab_strip_model(); 930 tab_strip_model->DetachWebContentsAt( 931 tab_strip_model->GetIndexOfWebContents(web_contents_)); 932 browser_ = NULL; 933 } 934 } else if (is_docked) { 935 // Update inspected window to hide split and reset it. 936 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 937 if (inspected_window) 938 inspected_window->UpdateDevTools(); 939 } 940 941 if (dock_side_ != DEVTOOLS_DOCK_SIDE_MINIMIZED) { 942 std::string pref_value = kPrefBottom; 943 switch (dock_side_) { 944 case DEVTOOLS_DOCK_SIDE_UNDOCKED: 945 pref_value = kPrefUndocked; 946 break; 947 case DEVTOOLS_DOCK_SIDE_RIGHT: 948 pref_value = kPrefRight; 949 break; 950 case DEVTOOLS_DOCK_SIDE_BOTTOM: 951 pref_value = kPrefBottom; 952 break; 953 case DEVTOOLS_DOCK_SIDE_MINIMIZED: 954 // We don't persist minimized state. 955 break; 956 } 957 profile_->GetPrefs()->SetString(prefs::kDevToolsDockSide, pref_value); 958 } 959 960 Show(DEVTOOLS_TOGGLE_ACTION_SHOW); 961} 962 963void DevToolsWindow::Restore() { 964 if (dock_side_ == DEVTOOLS_DOCK_SIDE_MINIMIZED) 965 SetDockSide(SideToString(dock_side_before_minimized_)); 966} 967 968void DevToolsWindow::OpenInNewTab(const std::string& url) { 969 OpenURLParams params(GURL(url), 970 content::Referrer(), 971 NEW_FOREGROUND_TAB, 972 content::PAGE_TRANSITION_LINK, 973 false /* is_renderer_initiated */); 974 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 975 if (inspected_web_contents) { 976 inspected_web_contents->OpenURL(params); 977 } else { 978 chrome::HostDesktopType host_desktop_type; 979 if (browser_) { 980 host_desktop_type = browser_->host_desktop_type(); 981 } else { 982 // There should always be a browser when there are no inspected web 983 // contents. 984 NOTREACHED(); 985 host_desktop_type = chrome::GetActiveDesktop(); 986 } 987 988 const BrowserList* browser_list = 989 BrowserList::GetInstance(host_desktop_type); 990 for (BrowserList::const_iterator it = browser_list->begin(); 991 it != browser_list->end(); ++it) { 992 if ((*it)->type() == Browser::TYPE_TABBED) { 993 (*it)->OpenURL(params); 994 break; 995 } 996 } 997 } 998} 999 1000void DevToolsWindow::SaveToFile(const std::string& url, 1001 const std::string& content, 1002 bool save_as) { 1003 file_helper_->Save(url, content, save_as, Bind(&DevToolsWindow::FileSavedAs, 1004 weak_factory_.GetWeakPtr(), 1005 url)); 1006} 1007 1008void DevToolsWindow::AppendToFile(const std::string& url, 1009 const std::string& content) { 1010 file_helper_->Append(url, content, Bind(&DevToolsWindow::AppendedTo, 1011 weak_factory_.GetWeakPtr(), 1012 url)); 1013} 1014 1015namespace { 1016 1017DictionaryValue* CreateFileSystemValue( 1018 DevToolsFileHelper::FileSystem file_system) { 1019 DictionaryValue* file_system_value = new DictionaryValue(); 1020 file_system_value->SetString("fileSystemName", file_system.file_system_name); 1021 file_system_value->SetString("rootURL", file_system.root_url); 1022 file_system_value->SetString("fileSystemPath", file_system.file_system_path); 1023 return file_system_value; 1024} 1025 1026} // namespace 1027 1028void DevToolsWindow::RequestFileSystems() { 1029 CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme)); 1030 file_helper_->RequestFileSystems( 1031 Bind(&DevToolsWindow::FileSystemsLoaded, weak_factory_.GetWeakPtr())); 1032} 1033 1034void DevToolsWindow::AddFileSystem() { 1035 CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme)); 1036 file_helper_->AddFileSystem( 1037 Bind(&DevToolsWindow::FileSystemAdded, weak_factory_.GetWeakPtr()), 1038 Bind(&DevToolsWindow::ShowDevToolsConfirmInfoBar, 1039 weak_factory_.GetWeakPtr())); 1040} 1041 1042void DevToolsWindow::RemoveFileSystem(const std::string& file_system_path) { 1043 CHECK(web_contents_->GetURL().SchemeIs(chrome::kChromeDevToolsScheme)); 1044 file_helper_->RemoveFileSystem(file_system_path); 1045 StringValue file_system_path_value(file_system_path); 1046 CallClientFunction("InspectorFrontendAPI.fileSystemRemoved", 1047 &file_system_path_value); 1048} 1049 1050void DevToolsWindow::FileSavedAs(const std::string& url) { 1051 StringValue url_value(url); 1052 CallClientFunction("InspectorFrontendAPI.savedURL", &url_value); 1053} 1054 1055void DevToolsWindow::AppendedTo(const std::string& url) { 1056 StringValue url_value(url); 1057 CallClientFunction("InspectorFrontendAPI.appendedToURL", &url_value); 1058} 1059 1060void DevToolsWindow::FileSystemsLoaded( 1061 const std::vector<DevToolsFileHelper::FileSystem>& file_systems) { 1062 ListValue file_systems_value; 1063 for (size_t i = 0; i < file_systems.size(); ++i) { 1064 file_systems_value.Append(CreateFileSystemValue(file_systems[i])); 1065 } 1066 CallClientFunction("InspectorFrontendAPI.fileSystemsLoaded", 1067 &file_systems_value); 1068} 1069 1070void DevToolsWindow::FileSystemAdded( 1071 const DevToolsFileHelper::FileSystem& file_system) { 1072 StringValue error_string_value(""); 1073 DictionaryValue* file_system_value = NULL; 1074 if (!file_system.file_system_path.empty()) 1075 file_system_value = CreateFileSystemValue(file_system); 1076 CallClientFunction("InspectorFrontendAPI.fileSystemAdded", 1077 &error_string_value, 1078 file_system_value); 1079 if (file_system_value) 1080 delete file_system_value; 1081} 1082 1083void DevToolsWindow::ShowDevToolsConfirmInfoBar( 1084 const string16& message, 1085 const ConfirmInfoBarCallback& callback) { 1086 InfoBarService* infobar_service = IsDocked() ? 1087 InfoBarService::FromWebContents(GetInspectedWebContents()) : 1088 InfoBarService::FromWebContents(web_contents_); 1089 1090 if (infobar_service) { 1091 infobar_service->AddInfoBar(scoped_ptr<InfoBarDelegate>( 1092 new DevToolsConfirmInfoBarDelegate( 1093 infobar_service, 1094 callback, 1095 message))); 1096 } else { 1097 callback.Run(false); 1098 } 1099} 1100 1101content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() { 1102 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 1103 if (inspected_web_contents && inspected_web_contents->GetDelegate()) { 1104 return inspected_web_contents->GetDelegate()-> 1105 GetJavaScriptDialogManager(); 1106 } 1107 return content::WebContentsDelegate::GetJavaScriptDialogManager(); 1108} 1109 1110void DevToolsWindow::RunFileChooser(WebContents* web_contents, 1111 const FileChooserParams& params) { 1112 FileSelectHelper::RunFileChooser(web_contents, params); 1113} 1114 1115void DevToolsWindow::WebContentsFocused(WebContents* contents) { 1116 Browser* inspected_browser = NULL; 1117 int inspected_tab_index = -1; 1118 1119 if (IsDocked() && FindInspectedBrowserAndTabIndex(&inspected_browser, 1120 &inspected_tab_index)) { 1121 inspected_browser->window()->WebContentsFocused(contents); 1122 } 1123} 1124 1125void DevToolsWindow::UpdateBrowserToolbar() { 1126 content::WebContents* inspected_web_contents = GetInspectedWebContents(); 1127 if (!inspected_web_contents) 1128 return; 1129 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 1130 if (inspected_window) 1131 inspected_window->UpdateToolbar(inspected_web_contents, false); 1132} 1133 1134bool DevToolsWindow::IsDocked() { 1135 return dock_side_ != DEVTOOLS_DOCK_SIDE_UNDOCKED; 1136} 1137 1138// static 1139DevToolsDockSide DevToolsWindow::GetDockSideFromPrefs(Profile* profile) { 1140 std::string dock_side = 1141 profile->GetPrefs()->GetString(prefs::kDevToolsDockSide); 1142 1143 // Migrate prefs 1144 if (dock_side == kOldPrefBottom || dock_side == kOldPrefRight) { 1145 bool docked = profile->GetPrefs()->GetBoolean(prefs::kDevToolsOpenDocked); 1146 if (dock_side == kOldPrefBottom) 1147 return docked ? DEVTOOLS_DOCK_SIDE_BOTTOM : DEVTOOLS_DOCK_SIDE_UNDOCKED; 1148 else 1149 return docked ? DEVTOOLS_DOCK_SIDE_RIGHT : DEVTOOLS_DOCK_SIDE_UNDOCKED; 1150 } 1151 1152 if (dock_side == kPrefUndocked) 1153 return DEVTOOLS_DOCK_SIDE_UNDOCKED; 1154 else if (dock_side == kPrefRight) 1155 return DEVTOOLS_DOCK_SIDE_RIGHT; 1156 // Default to docked to bottom 1157 return DEVTOOLS_DOCK_SIDE_BOTTOM; 1158} 1159 1160// static 1161std::string DevToolsWindow::SideToString(DevToolsDockSide dock_side) { 1162 std::string dock_side_string; 1163 switch (dock_side) { 1164 case DEVTOOLS_DOCK_SIDE_UNDOCKED: return kDockSideUndocked; 1165 case DEVTOOLS_DOCK_SIDE_RIGHT: return kDockSideRight; 1166 case DEVTOOLS_DOCK_SIDE_BOTTOM: return kDockSideBottom; 1167 case DEVTOOLS_DOCK_SIDE_MINIMIZED: return kDockSideMinimized; 1168 } 1169 return kDockSideUndocked; 1170} 1171 1172// static 1173DevToolsDockSide DevToolsWindow::SideFromString( 1174 const std::string& dock_side) { 1175 if (dock_side == kDockSideRight) 1176 return DEVTOOLS_DOCK_SIDE_RIGHT; 1177 if (dock_side == kDockSideBottom) 1178 return DEVTOOLS_DOCK_SIDE_BOTTOM; 1179 if (dock_side == kDockSideMinimized) 1180 return DEVTOOLS_DOCK_SIDE_MINIMIZED; 1181 return DEVTOOLS_DOCK_SIDE_UNDOCKED; 1182} 1183