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