Home | History | Annotate | Download | only in debugger
      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