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