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