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