devtools_window.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 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 "base/command_line.h" 6#include "base/json/json_writer.h" 7#include "base/string_number_conversions.h" 8#include "base/utf_string_conversions.h" 9#include "base/values.h" 10#include "chrome/browser/browser_list.h" 11#include "chrome/browser/browser_process.h" 12#include "chrome/browser/browser_window.h" 13#include "chrome/browser/debugger/devtools_manager.h" 14#include "chrome/browser/debugger/devtools_window.h" 15#include "chrome/browser/extensions/extensions_service.h" 16#include "chrome/browser/in_process_webkit/session_storage_namespace.h" 17#include "chrome/browser/load_notification_details.h" 18#include "chrome/browser/prefs/pref_service.h" 19#include "chrome/browser/profile.h" 20#include "chrome/browser/renderer_host/render_view_host.h" 21#include "chrome/browser/tab_contents/navigation_controller.h" 22#include "chrome/browser/tab_contents/navigation_entry.h" 23#include "chrome/browser/tab_contents/tab_contents.h" 24#include "chrome/browser/tab_contents/tab_contents_view.h" 25#include "chrome/browser/tabs/tab_strip_model.h" 26#include "chrome/browser/themes/browser_theme_provider.h" 27#include "chrome/browser/ui/browser.h" 28#include "chrome/common/bindings_policy.h" 29#include "chrome/common/chrome_switches.h" 30#include "chrome/common/pref_names.h" 31#include "chrome/common/render_messages.h" 32#include "chrome/common/url_constants.h" 33#include "grit/generated_resources.h" 34 35const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; 36 37// static 38TabContents* DevToolsWindow::GetDevToolsContents(TabContents* inspected_tab) { 39 if (!inspected_tab) { 40 return NULL; 41 } 42 43 if (!DevToolsManager::GetInstance()) 44 return NULL; // Happens only in tests. 45 46 DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> 47 GetDevToolsClientHostFor(inspected_tab->render_view_host()); 48 if (!client_host) { 49 return NULL; 50 } 51 52 DevToolsWindow* window = client_host->AsDevToolsWindow(); 53 if (!window || !window->is_docked()) { 54 return NULL; 55 } 56 return window->tab_contents(); 57} 58 59DevToolsWindow::DevToolsWindow(Profile* profile, 60 RenderViewHost* inspected_rvh, 61 bool docked) 62 : profile_(profile), 63 browser_(NULL), 64 docked_(docked), 65 is_loaded_(false), 66 action_on_load_(DEVTOOLS_TOGGLE_ACTION_NONE) { 67 // Create TabContents with devtools. 68 tab_contents_ = new TabContents(profile, NULL, MSG_ROUTING_NONE, NULL, NULL); 69 tab_contents_->render_view_host()->AllowBindings(BindingsPolicy::DOM_UI); 70 tab_contents_->controller().LoadURL( 71 GetDevToolsUrl(), GURL(), PageTransition::START_PAGE); 72 73 // Wipe out page icon so that the default application icon is used. 74 NavigationEntry* entry = tab_contents_->controller().GetActiveEntry(); 75 entry->favicon().set_bitmap(SkBitmap()); 76 entry->favicon().set_is_valid(true); 77 78 // Register on-load actions. 79 registrar_.Add(this, 80 NotificationType::LOAD_STOP, 81 Source<NavigationController>(&tab_contents_->controller())); 82 registrar_.Add(this, 83 NotificationType::TAB_CLOSING, 84 Source<NavigationController>(&tab_contents_->controller())); 85 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, 86 NotificationService::AllSources()); 87 inspected_tab_ = inspected_rvh->delegate()->GetAsTabContents(); 88} 89 90DevToolsWindow::~DevToolsWindow() { 91} 92 93DevToolsWindow* DevToolsWindow::AsDevToolsWindow() { 94 return this; 95} 96 97void DevToolsWindow::SendMessageToClient(const IPC::Message& message) { 98 RenderViewHost* target_host = tab_contents_->render_view_host(); 99 IPC::Message* m = new IPC::Message(message); 100 m->set_routing_id(target_host->routing_id()); 101 target_host->Send(m); 102} 103 104void DevToolsWindow::InspectedTabClosing() { 105 if (docked_) { 106 // Update dev tools to reflect removed dev tools window. 107 108 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 109 if (inspected_window) 110 inspected_window->UpdateDevTools(); 111 // In case of docked tab_contents we own it, so delete here. 112 delete tab_contents_; 113 114 delete this; 115 } else { 116 // First, initiate self-destruct to free all the registrars. 117 // Then close all tabs. Browser will take care of deleting tab_contents 118 // for us. 119 Browser* browser = browser_; 120 delete this; 121 browser->CloseAllTabs(); 122 } 123} 124 125void DevToolsWindow::Show(DevToolsToggleAction action) { 126 if (docked_) { 127 Browser* inspected_browser; 128 int inspected_tab_index; 129 // Tell inspected browser to update splitter and switch to inspected panel. 130 if (FindInspectedBrowserAndTabIndex(&inspected_browser, 131 &inspected_tab_index)) { 132 BrowserWindow* inspected_window = inspected_browser->window(); 133 tab_contents_->set_delegate(this); 134 inspected_window->UpdateDevTools(); 135 SetAttachedWindow(); 136 tab_contents_->view()->SetInitialFocus(); 137 inspected_window->Show(); 138 TabStripModel* tabstrip_model = inspected_browser->tabstrip_model(); 139 tabstrip_model->SelectTabContentsAt(inspected_tab_index, true); 140 ScheduleAction(action); 141 return; 142 } else { 143 // Sometimes we don't know where to dock. Stay undocked. 144 docked_ = false; 145 } 146 } 147 148 // Avoid consecutive window switching if the devtools window has been opened 149 // and the Inspect Element shortcut is pressed in the inspected tab. 150 bool should_show_window = 151 !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT; 152 153 if (!browser_) 154 CreateDevToolsBrowser(); 155 156 if (should_show_window) 157 browser_->window()->Show(); 158 SetAttachedWindow(); 159 if (should_show_window) 160 tab_contents_->view()->SetInitialFocus(); 161 162 ScheduleAction(action); 163} 164 165void DevToolsWindow::Activate() { 166 if (!docked_) { 167 if (!browser_->window()->IsActive()) { 168 browser_->window()->Activate(); 169 } 170 } else { 171 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 172 if (inspected_window) 173 tab_contents_->view()->Focus(); 174 } 175} 176 177void DevToolsWindow::SetDocked(bool docked) { 178 if (docked_ == docked) 179 return; 180 if (docked && !GetInspectedBrowserWindow()) { 181 // Cannot dock, avoid window flashing due to close-reopen cycle. 182 return; 183 } 184 docked_ = docked; 185 186 if (docked) { 187 // Detach window from the external devtools browser. It will lead to 188 // the browser object's close and delete. Remove observer first. 189 TabStripModel* tabstrip_model = browser_->tabstrip_model(); 190 tabstrip_model->DetachTabContentsAt( 191 tabstrip_model->GetIndexOfTabContents(tab_contents_)); 192 browser_ = NULL; 193 } else { 194 // Update inspected window to hide split and reset it. 195 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 196 if (inspected_window) { 197 inspected_window->UpdateDevTools(); 198 inspected_window = NULL; 199 } 200 } 201 Show(DEVTOOLS_TOGGLE_ACTION_NONE); 202} 203 204RenderViewHost* DevToolsWindow::GetRenderViewHost() { 205 return tab_contents_->render_view_host(); 206} 207 208void DevToolsWindow::CreateDevToolsBrowser() { 209 // TODO(pfeldman): Make browser's getter for this key static. 210 std::string wp_key; 211 wp_key.append(prefs::kBrowserWindowPlacement); 212 wp_key.append("_"); 213 wp_key.append(kDevToolsApp); 214 215 PrefService* prefs = g_browser_process->local_state(); 216 if (!prefs->FindPreference(wp_key.c_str())) { 217 prefs->RegisterDictionaryPref(wp_key.c_str()); 218 } 219 220 const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str()); 221 if (!wp_pref) { 222 DictionaryValue* defaults = prefs->GetMutableDictionary(wp_key.c_str()); 223 defaults->SetInteger("left", 100); 224 defaults->SetInteger("top", 100); 225 defaults->SetInteger("right", 740); 226 defaults->SetInteger("bottom", 740); 227 defaults->SetBoolean("maximized", false); 228 defaults->SetBoolean("always_on_top", false); 229 } 230 231 browser_ = Browser::CreateForDevTools(profile_); 232 browser_->tabstrip_model()->AddTabContents( 233 tab_contents_, -1, PageTransition::START_PAGE, 234 TabStripModel::ADD_SELECTED); 235} 236 237bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser, 238 int* tab) { 239 const NavigationController& controller = inspected_tab_->controller(); 240 for (BrowserList::const_iterator it = BrowserList::begin(); 241 it != BrowserList::end(); ++it) { 242 int tab_index = (*it)->GetIndexOfController(&controller); 243 if (tab_index != TabStripModel::kNoTab) { 244 *browser = *it; 245 *tab = tab_index; 246 return true; 247 } 248 } 249 return false; 250} 251 252BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { 253 Browser* browser = NULL; 254 int tab; 255 return FindInspectedBrowserAndTabIndex(&browser, &tab) ? 256 browser->window() : NULL; 257} 258 259void DevToolsWindow::SetAttachedWindow() { 260 tab_contents_->render_view_host()-> 261 ExecuteJavascriptInWebFrame( 262 L"", docked_ ? L"WebInspector.setAttachedWindow(true);" : 263 L"WebInspector.setAttachedWindow(false);"); 264} 265 266 267void DevToolsWindow::AddDevToolsExtensionsToClient() { 268 if (inspected_tab_) { 269 FundamentalValue tabId(inspected_tab_->controller().session_id().id()); 270 CallClientFunction(L"WebInspector.setInspectedTabId", tabId); 271 } 272 ListValue results; 273 const ExtensionsService* extension_service = tab_contents_->profile()-> 274 GetOriginalProfile()->GetExtensionsService(); 275 const ExtensionList* extensions = extension_service->extensions(); 276 277 for (ExtensionList::const_iterator extension = extensions->begin(); 278 extension != extensions->end(); ++extension) { 279 if ((*extension)->devtools_url().is_empty()) 280 continue; 281 DictionaryValue* extension_info = new DictionaryValue(); 282 extension_info->Set("startPage", 283 new StringValue((*extension)->devtools_url().spec())); 284 results.Append(extension_info); 285 } 286 CallClientFunction(L"WebInspector.addExtensions", results); 287} 288 289void DevToolsWindow::CallClientFunction(const std::wstring& function_name, 290 const Value& arg) { 291 std::string json; 292 base::JSONWriter::Write(&arg, false, &json); 293 std::wstring javascript = function_name + L"(" + UTF8ToWide(json) + L");"; 294 tab_contents_->render_view_host()-> 295 ExecuteJavascriptInWebFrame(L"", javascript); 296} 297 298void DevToolsWindow::Observe(NotificationType type, 299 const NotificationSource& source, 300 const NotificationDetails& details) { 301 if (type == NotificationType::LOAD_STOP && !is_loaded_) { 302 SetAttachedWindow(); 303 is_loaded_ = true; 304 UpdateTheme(); 305 DoAction(); 306 AddDevToolsExtensionsToClient(); 307 } else if (type == NotificationType::TAB_CLOSING) { 308 if (Source<NavigationController>(source).ptr() == 309 &tab_contents_->controller()) { 310 // This happens when browser closes all of its tabs as a result 311 // of window.Close event. 312 // Notify manager that this DevToolsClientHost no longer exists and 313 // initiate self-destuct here. 314 NotifyCloseListener(); 315 delete this; 316 } 317 } else if (type == NotificationType::BROWSER_THEME_CHANGED) { 318 UpdateTheme(); 319 } 320} 321 322void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) { 323 action_on_load_ = action; 324 if (is_loaded_) 325 DoAction(); 326} 327 328void DevToolsWindow::DoAction() { 329 // TODO: these messages should be pushed through the WebKit API instead. 330 switch (action_on_load_) { 331 case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE: 332 tab_contents_->render_view_host()-> 333 ExecuteJavascriptInWebFrame(L"", L"WebInspector.showConsole();"); 334 break; 335 case DEVTOOLS_TOGGLE_ACTION_INSPECT: 336 tab_contents_->render_view_host()-> 337 ExecuteJavascriptInWebFrame( 338 L"", L"WebInspector.toggleSearchingForNode();"); 339 case DEVTOOLS_TOGGLE_ACTION_NONE: 340 // Do nothing. 341 break; 342 default: 343 NOTREACHED(); 344 } 345 action_on_load_ = DEVTOOLS_TOGGLE_ACTION_NONE; 346} 347 348std::string SkColorToRGBAString(SkColor color) { 349 // We convert the alpha using DoubleToString because StringPrintf will use 350 // locale specific formatters (e.g., use , instead of . in German). 351 return StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color), 352 SkColorGetG(color), SkColorGetB(color), 353 base::DoubleToString(SkColorGetA(color) / 255.0).c_str()); 354} 355 356GURL DevToolsWindow::GetDevToolsUrl() { 357 BrowserThemeProvider* tp = profile_->GetThemeProvider(); 358 CHECK(tp); 359 360 SkColor color_toolbar = 361 tp->GetColor(BrowserThemeProvider::COLOR_TOOLBAR); 362 SkColor color_tab_text = 363 tp->GetColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT); 364 365 std::string url_string = StringPrintf( 366 "%sdevtools.html?docked=%s&toolbar_color=%s&text_color=%s", 367 chrome::kChromeUIDevToolsURL, 368 docked_ ? "true" : "false", 369 SkColorToRGBAString(color_toolbar).c_str(), 370 SkColorToRGBAString(color_tab_text).c_str()); 371 return GURL(url_string); 372} 373 374void DevToolsWindow::UpdateTheme() { 375 BrowserThemeProvider* tp = profile_->GetThemeProvider(); 376 CHECK(tp); 377 378 SkColor color_toolbar = 379 tp->GetColor(BrowserThemeProvider::COLOR_TOOLBAR); 380 SkColor color_tab_text = 381 tp->GetColor(BrowserThemeProvider::COLOR_BOOKMARK_TEXT); 382 std::string command = StringPrintf( 383 "WebInspector.setToolbarColors(\"%s\", \"%s\")", 384 SkColorToRGBAString(color_toolbar).c_str(), 385 SkColorToRGBAString(color_tab_text).c_str()); 386 tab_contents_->render_view_host()-> 387 ExecuteJavascriptInWebFrame(L"", UTF8ToWide(command)); 388} 389 390bool DevToolsWindow::PreHandleKeyboardEvent( 391 const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { 392 if (docked_) { 393 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 394 if (inspected_window) 395 return inspected_window->PreHandleKeyboardEvent( 396 event, is_keyboard_shortcut); 397 } 398 return false; 399} 400 401void DevToolsWindow::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) { 402 if (docked_) { 403 BrowserWindow* inspected_window = GetInspectedBrowserWindow(); 404 if (inspected_window) 405 inspected_window->HandleKeyboardEvent(event); 406 } 407} 408