// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "base/json/json_writer.h" #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/debugger/devtools_window.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/load_notification_details.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/themes/theme_service.h" #include "chrome/browser/themes/theme_service_factory.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/pref_names.h" #include "chrome/common/render_messages.h" #include "chrome/common/url_constants.h" #include "content/browser/in_process_webkit/session_storage_namespace.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/navigation_entry.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents_view.h" #include "content/common/bindings_policy.h" #include "content/common/notification_service.h" #include "grit/generated_resources.h" const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp"; // static TabContentsWrapper* DevToolsWindow::GetDevToolsContents( TabContents* inspected_tab) { if (!inspected_tab) { return NULL; } if (!DevToolsManager::GetInstance()) return NULL; // Happens only in tests. DevToolsClientHost* client_host = DevToolsManager::GetInstance()-> GetDevToolsClientHostFor(inspected_tab->render_view_host()); if (!client_host) { return NULL; } DevToolsWindow* window = client_host->AsDevToolsWindow(); if (!window || !window->is_docked()) { return NULL; } return window->tab_contents(); } DevToolsWindow::DevToolsWindow(Profile* profile, RenderViewHost* inspected_rvh, bool docked) : profile_(profile), browser_(NULL), docked_(docked), is_loaded_(false), action_on_load_(DEVTOOLS_TOGGLE_ACTION_NONE) { // Create TabContents with devtools. tab_contents_ = Browser::TabContentsFactory(profile, NULL, MSG_ROUTING_NONE, NULL, NULL); tab_contents_->tab_contents()-> render_view_host()->AllowBindings(BindingsPolicy::WEB_UI); tab_contents_->controller().LoadURL( GetDevToolsUrl(), GURL(), PageTransition::START_PAGE); // Wipe out page icon so that the default application icon is used. NavigationEntry* entry = tab_contents_->controller().GetActiveEntry(); entry->favicon().set_bitmap(SkBitmap()); entry->favicon().set_is_valid(true); // Register on-load actions. registrar_.Add(this, NotificationType::LOAD_STOP, Source<NavigationController>(&tab_contents_->controller())); registrar_.Add(this, NotificationType::TAB_CLOSING, Source<NavigationController>(&tab_contents_->controller())); registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, NotificationService::AllSources()); inspected_tab_ = inspected_rvh->delegate()->GetAsTabContents(); } DevToolsWindow::~DevToolsWindow() { } DevToolsWindow* DevToolsWindow::AsDevToolsWindow() { return this; } void DevToolsWindow::SendMessageToClient(const IPC::Message& message) { RenderViewHost* target_host = tab_contents_->tab_contents()->render_view_host(); IPC::Message* m = new IPC::Message(message); m->set_routing_id(target_host->routing_id()); target_host->Send(m); } void DevToolsWindow::InspectedTabClosing() { if (docked_) { // Update dev tools to reflect removed dev tools window. BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) inspected_window->UpdateDevTools(); // In case of docked tab_contents we own it, so delete here. delete tab_contents_; delete this; } else { // First, initiate self-destruct to free all the registrars. // Then close all tabs. Browser will take care of deleting tab_contents // for us. Browser* browser = browser_; delete this; browser->CloseAllTabs(); } } void DevToolsWindow::TabReplaced(TabContentsWrapper* new_tab) { DCHECK_EQ(profile_, new_tab->profile()); inspected_tab_ = new_tab->tab_contents(); } void DevToolsWindow::Show(DevToolsToggleAction action) { if (docked_) { Browser* inspected_browser; int inspected_tab_index; // Tell inspected browser to update splitter and switch to inspected panel. if (!IsInspectedBrowserPopup() && FindInspectedBrowserAndTabIndex(&inspected_browser, &inspected_tab_index)) { BrowserWindow* inspected_window = inspected_browser->window(); tab_contents_->tab_contents()->set_delegate(this); inspected_window->UpdateDevTools(); tab_contents_->view()->SetInitialFocus(); inspected_window->Show(); TabStripModel* tabstrip_model = inspected_browser->tabstrip_model(); tabstrip_model->ActivateTabAt(inspected_tab_index, true); ScheduleAction(action); return; } else { // Sometimes we don't know where to dock. Stay undocked. docked_ = false; } } // Avoid consecutive window switching if the devtools window has been opened // and the Inspect Element shortcut is pressed in the inspected tab. bool should_show_window = !browser_ || action != DEVTOOLS_TOGGLE_ACTION_INSPECT; if (!browser_) CreateDevToolsBrowser(); if (should_show_window) { browser_->window()->Show(); tab_contents_->view()->SetInitialFocus(); } ScheduleAction(action); } void DevToolsWindow::Activate() { if (!docked_) { if (!browser_->window()->IsActive()) { browser_->window()->Activate(); } } else { BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) tab_contents_->view()->Focus(); } } void DevToolsWindow::SetDocked(bool docked) { if (docked_ == docked) return; if (docked && (!GetInspectedBrowserWindow() || IsInspectedBrowserPopup())) { // Cannot dock, avoid window flashing due to close-reopen cycle. return; } docked_ = docked; if (docked) { // Detach window from the external devtools browser. It will lead to // the browser object's close and delete. Remove observer first. TabStripModel* tabstrip_model = browser_->tabstrip_model(); tabstrip_model->DetachTabContentsAt( tabstrip_model->GetIndexOfTabContents(tab_contents_)); browser_ = NULL; } else { // Update inspected window to hide split and reset it. BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) { inspected_window->UpdateDevTools(); inspected_window = NULL; } } Show(DEVTOOLS_TOGGLE_ACTION_NONE); } RenderViewHost* DevToolsWindow::GetRenderViewHost() { return tab_contents_->render_view_host(); } void DevToolsWindow::CreateDevToolsBrowser() { // TODO(pfeldman): Make browser's getter for this key static. std::string wp_key; wp_key.append(prefs::kBrowserWindowPlacement); wp_key.append("_"); wp_key.append(kDevToolsApp); PrefService* prefs = profile_->GetPrefs(); if (!prefs->FindPreference(wp_key.c_str())) { prefs->RegisterDictionaryPref(wp_key.c_str()); } const DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str()); if (!wp_pref || wp_pref->empty()) { DictionaryPrefUpdate update(prefs, wp_key.c_str()); DictionaryValue* defaults = update.Get(); defaults->SetInteger("left", 100); defaults->SetInteger("top", 100); defaults->SetInteger("right", 740); defaults->SetInteger("bottom", 740); defaults->SetBoolean("maximized", false); defaults->SetBoolean("always_on_top", false); } browser_ = Browser::CreateForDevTools(profile_); browser_->tabstrip_model()->AddTabContents( tab_contents_, -1, PageTransition::START_PAGE, TabStripModel::ADD_ACTIVE); } bool DevToolsWindow::FindInspectedBrowserAndTabIndex(Browser** browser, int* tab) { const NavigationController& controller = inspected_tab_->controller(); for (BrowserList::const_iterator it = BrowserList::begin(); it != BrowserList::end(); ++it) { int tab_index = (*it)->GetIndexOfController(&controller); if (tab_index != TabStripModel::kNoTab) { *browser = *it; *tab = tab_index; return true; } } return false; } BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() { Browser* browser = NULL; int tab; return FindInspectedBrowserAndTabIndex(&browser, &tab) ? browser->window() : NULL; } bool DevToolsWindow::IsInspectedBrowserPopup() { Browser* browser = NULL; int tab; if (!FindInspectedBrowserAndTabIndex(&browser, &tab)) return false; return (browser->type() & Browser::TYPE_POPUP) != 0; } void DevToolsWindow::UpdateFrontendAttachedState() { tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame( string16(), docked_ ? ASCIIToUTF16("WebInspector.setAttachedWindow(true);") : ASCIIToUTF16("WebInspector.setAttachedWindow(false);")); } void DevToolsWindow::AddDevToolsExtensionsToClient() { if (inspected_tab_) { FundamentalValue tabId(inspected_tab_->controller().session_id().id()); CallClientFunction(ASCIIToUTF16("WebInspector.setInspectedTabId"), tabId); } ListValue results; const ExtensionService* extension_service = tab_contents_->tab_contents()->profile()-> GetOriginalProfile()->GetExtensionService(); if (!extension_service) return; const ExtensionList* extensions = extension_service->extensions(); for (ExtensionList::const_iterator extension = extensions->begin(); extension != extensions->end(); ++extension) { if ((*extension)->devtools_url().is_empty()) continue; DictionaryValue* extension_info = new DictionaryValue(); extension_info->Set("startPage", new StringValue((*extension)->devtools_url().spec())); results.Append(extension_info); } CallClientFunction(ASCIIToUTF16("WebInspector.addExtensions"), results); } void DevToolsWindow::OpenURLFromTab(TabContents* source, const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) { if (inspected_tab_) inspected_tab_->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); } void DevToolsWindow::CallClientFunction(const string16& function_name, const Value& arg) { std::string json; base::JSONWriter::Write(&arg, false, &json); string16 javascript = function_name + char16('(') + UTF8ToUTF16(json) + ASCIIToUTF16(");"); tab_contents_->render_view_host()-> ExecuteJavascriptInWebFrame(string16(), javascript); } void DevToolsWindow::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::LOAD_STOP && !is_loaded_) { is_loaded_ = true; UpdateTheme(); DoAction(); AddDevToolsExtensionsToClient(); } else if (type == NotificationType::TAB_CLOSING) { if (Source<NavigationController>(source).ptr() == &tab_contents_->controller()) { // This happens when browser closes all of its tabs as a result // of window.Close event. // Notify manager that this DevToolsClientHost no longer exists and // initiate self-destuct here. NotifyCloseListener(); delete this; } } else if (type == NotificationType::BROWSER_THEME_CHANGED) { UpdateTheme(); } } void DevToolsWindow::ScheduleAction(DevToolsToggleAction action) { action_on_load_ = action; if (is_loaded_) DoAction(); } void DevToolsWindow::DoAction() { UpdateFrontendAttachedState(); // TODO: these messages should be pushed through the WebKit API instead. switch (action_on_load_) { case DEVTOOLS_TOGGLE_ACTION_SHOW_CONSOLE: tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame( string16(), ASCIIToUTF16("WebInspector.showConsole();")); break; case DEVTOOLS_TOGGLE_ACTION_INSPECT: tab_contents_->render_view_host()->ExecuteJavascriptInWebFrame( string16(), ASCIIToUTF16("WebInspector.toggleSearchingForNode();")); case DEVTOOLS_TOGGLE_ACTION_NONE: // Do nothing. break; default: NOTREACHED(); } action_on_load_ = DEVTOOLS_TOGGLE_ACTION_NONE; } std::string SkColorToRGBAString(SkColor color) { // We convert the alpha using DoubleToString because StringPrintf will use // locale specific formatters (e.g., use , instead of . in German). return StringPrintf("rgba(%d,%d,%d,%s)", SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), base::DoubleToString(SkColorGetA(color) / 255.0).c_str()); } GURL DevToolsWindow::GetDevToolsUrl() { ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_); CHECK(tp); SkColor color_toolbar = tp->GetColor(ThemeService::COLOR_TOOLBAR); SkColor color_tab_text = tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT); std::string url_string = StringPrintf( "%sdevtools.html?docked=%s&toolbar_color=%s&text_color=%s", chrome::kChromeUIDevToolsURL, docked_ ? "true" : "false", SkColorToRGBAString(color_toolbar).c_str(), SkColorToRGBAString(color_tab_text).c_str()); return GURL(url_string); } void DevToolsWindow::UpdateTheme() { ThemeService* tp = ThemeServiceFactory::GetForProfile(profile_); CHECK(tp); SkColor color_toolbar = tp->GetColor(ThemeService::COLOR_TOOLBAR); SkColor color_tab_text = tp->GetColor(ThemeService::COLOR_BOOKMARK_TEXT); std::string command = StringPrintf( "WebInspector.setToolbarColors(\"%s\", \"%s\")", SkColorToRGBAString(color_toolbar).c_str(), SkColorToRGBAString(color_tab_text).c_str()); tab_contents_->render_view_host()-> ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(command)); } void DevToolsWindow::AddNewContents(TabContents* source, TabContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) { inspected_tab_->delegate()->AddNewContents(source, new_contents, disposition, initial_pos, user_gesture); } bool DevToolsWindow::CanReloadContents(TabContents* source) const { return false; } bool DevToolsWindow::PreHandleKeyboardEvent( const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { if (docked_) { BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) return inspected_window->PreHandleKeyboardEvent( event, is_keyboard_shortcut); } return false; } void DevToolsWindow::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) { if (docked_) { BrowserWindow* inspected_window = GetInspectedBrowserWindow(); if (inspected_window) inspected_window->HandleKeyboardEvent(event); } }