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// Need to include this before any other file because it defines
6// IPC_MESSAGE_LOG_ENABLED. We need to use it to define
7// IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
8// ViewMsgLog et al. functions.
9#include "ipc/ipc_message.h"
10
11#ifdef IPC_MESSAGE_LOG_ENABLED
12#define IPC_MESSAGE_MACROS_LOG_ENABLED
13
14#include "chrome/browser/ui/views/about_ipc_dialog.h"
15
16#include <set>
17
18#include "base/string_util.h"
19#include "base/threading/thread.h"
20#include "base/time.h"
21#include "base/utf_string_conversions.h"
22#include "chrome/app/chrome_command_ids.h"
23#include "chrome/app/chrome_dll_resource.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/browser/ui/browser_dialogs.h"
26#include "chrome/common/chrome_constants.h"
27#include "chrome/common/devtools_messages.h"
28#include "chrome/common/render_messages.h"
29#include "content/common/plugin_messages.h"
30#include "net/url_request/url_request.h"
31#include "net/url_request/url_request_job.h"
32#include "net/url_request/url_request_job_tracker.h"
33#include "views/controls/button/text_button.h"
34#include "views/controls/native/native_view_host.h"
35#include "views/layout/grid_layout.h"
36#include "views/layout/layout_constants.h"
37#include "views/widget/root_view.h"
38#include "views/widget/widget.h"
39#include "views/window/window.h"
40
41namespace {
42
43// We don't localize this UI since this is a developer-only feature.
44const wchar_t kStartTrackingLabel[] = L"Start tracking";
45const wchar_t kStopTrackingLabel[] = L"Stop tracking";
46const wchar_t kClearLabel[] = L"Clear";
47const wchar_t kFilterLabel[] = L"Filter...";
48
49enum {
50  kTimeColumn = 0,
51  kChannelColumn,
52  kMessageColumn,
53  kFlagsColumn,
54  kDispatchColumn,
55  kProcessColumn,
56  kParamsColumn,
57};
58
59// This class registers the browser IPC logger functions with IPC::Logging.
60class RegisterLoggerFuncs {
61 public:
62  RegisterLoggerFuncs() {
63    IPC::Logging::set_log_function_map(&g_log_function_mapping);
64  }
65};
66
67RegisterLoggerFuncs g_register_logger_funcs;
68
69// The singleton dialog box. This is non-NULL when a dialog is active so we
70// know not to create a new one.
71AboutIPCDialog* g_active_dialog = NULL;
72
73std::set<int> disabled_messages;
74
75// Settings dialog -------------------------------------------------------------
76
77bool init_done = false;
78HWND settings_dialog = NULL;
79// Settings.
80CListViewCtrl* messages = NULL;
81
82void OnCheck(int id, bool checked) {
83  if (!init_done)
84    return;
85
86  if (checked)
87    disabled_messages.erase(id);
88  else
89    disabled_messages.insert(id);
90}
91
92void InitDialog(HWND hwnd) {
93  messages = new CListViewCtrl(::GetDlgItem(hwnd, IDC_Messages));
94
95  messages->SetViewType(LVS_REPORT);
96  messages->SetExtendedListViewStyle(LVS_EX_CHECKBOXES);
97  messages->ModifyStyle(0, LVS_SORTASCENDING | LVS_NOCOLUMNHEADER);
98  messages->InsertColumn(0, L"id", LVCFMT_LEFT, 230);
99
100  LogFunctionMap* log_functions = IPC::Logging::log_function_map();
101  for (LogFunctionMap::iterator i = log_functions->begin();
102       i != log_functions->end(); ++i) {
103    std::string name;
104    (*i->second)(&name, NULL, NULL);
105    if (name.empty())
106      continue;  // Will happen if the message file isn't included above.
107    std::wstring wname = UTF8ToWide(name);
108
109    int index = messages->InsertItem(
110        LVIF_TEXT | LVIF_PARAM, 0, wname.c_str(), 0, 0, 0, i->first);
111
112    messages->SetItemText(index, 0, wname.c_str());
113
114    if (disabled_messages.find(i->first) == disabled_messages.end())
115      messages->SetCheckState(index, TRUE);
116  }
117
118  init_done = true;
119}
120
121void CloseDialog() {
122  delete messages;
123  messages = NULL;
124
125  init_done = false;
126
127  ::DestroyWindow(settings_dialog);
128  settings_dialog = NULL;
129
130  /* The old version of this code stored the last settings in the preferences.
131     But with this dialog, there currently isn't an easy way to get the profile
132     to save in the preferences.
133  Profile* current_profile = profile();
134  if (!current_profile)
135    return;
136  PrefService* prefs = current_profile->GetPrefs();
137  if (!prefs->FindPreference(prefs::kIpcDisabledMessages))
138    return;
139  ListValue* list = prefs->GetMutableList(prefs::kIpcDisabledMessages);
140  list->Clear();
141  for (std::set<int>::const_iterator itr = disabled_messages_.begin();
142       itr != disabled_messages_.end();
143       ++itr) {
144    list->Append(Value::CreateIntegerValue(*itr));
145  }
146  */
147}
148
149void OnButtonClick(int id) {
150  int count = messages->GetItemCount();
151  for (int i = 0; i < count; ++i)
152    messages->SetCheckState(i, id == IDC_MessagesAll);
153}
154
155INT_PTR CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
156  switch (msg) {
157    case WM_INITDIALOG:
158      InitDialog(hwnd);
159      return FALSE;  // Don't set keyboard focus.
160    case WM_SYSCOMMAND:
161      if (wparam == SC_CLOSE) {
162        CloseDialog();
163        return FALSE;
164      }
165      break;
166    case WM_NOTIFY: {
167      NMLISTVIEW* info = reinterpret_cast<NM_LISTVIEW*>(lparam);
168      if (wparam == IDC_Messages && info->hdr.code == LVN_ITEMCHANGED) {
169        if (info->uChanged & LVIF_STATE) {
170          bool checked = (info->uNewState >> 12) == 2;
171          OnCheck(static_cast<int>(info->lParam), checked);
172        }
173        return FALSE;
174      }
175      break;
176    }
177    case WM_COMMAND:
178      if (HIWORD(wparam) == BN_CLICKED)
179        OnButtonClick(LOWORD(wparam));
180      break;
181  }
182  return FALSE;
183}
184
185void RunSettingsDialog(HWND parent) {
186  if (settings_dialog)
187    return;
188  HINSTANCE module_handle = GetModuleHandle(chrome::kBrowserResourcesDll);
189  settings_dialog = CreateDialog(module_handle,
190                                 MAKEINTRESOURCE(IDD_IPC_SETTINGS),
191                                 NULL,
192                                 &DialogProc);
193  ::ShowWindow(settings_dialog, SW_SHOW);
194}
195
196}  // namespace
197
198// AboutIPCDialog --------------------------------------------------------------
199
200AboutIPCDialog::AboutIPCDialog()
201    : track_toggle_(NULL),
202      clear_button_(NULL),
203      filter_button_(NULL),
204      table_(NULL),
205      tracking_(false) {
206  SetupControls();
207  IPC::Logging::GetInstance()->SetConsumer(this);
208}
209
210AboutIPCDialog::~AboutIPCDialog() {
211  g_active_dialog = NULL;
212  IPC::Logging::GetInstance()->SetConsumer(NULL);
213}
214
215// static
216void AboutIPCDialog::RunDialog() {
217  if (!g_active_dialog) {
218    g_active_dialog = new AboutIPCDialog;
219    views::Window::CreateChromeWindow(NULL, gfx::Rect(),
220                                      g_active_dialog)->Show();
221  } else {
222    // TODO(brettw) it would be nice to focus the existing window.
223  }
224}
225
226void AboutIPCDialog::SetupControls() {
227  views::GridLayout* layout = views::GridLayout::CreatePanel(this);
228  SetLayoutManager(layout);
229
230  track_toggle_ = new views::TextButton(this, kStartTrackingLabel);
231  clear_button_ = new views::TextButton(this, kClearLabel);
232  filter_button_ = new views::TextButton(this, kFilterLabel);
233
234  table_ = new views::NativeViewHost;
235
236  static const int first_column_set = 1;
237  views::ColumnSet* column_set = layout->AddColumnSet(first_column_set);
238  column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
239                        33.33f, views::GridLayout::FIXED, 0, 0);
240  column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
241                        33.33f, views::GridLayout::FIXED, 0, 0);
242  column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER,
243                        33.33f, views::GridLayout::FIXED, 0, 0);
244
245  static const int table_column_set = 2;
246  column_set = layout->AddColumnSet(table_column_set);
247  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL,
248                        100.0f, views::GridLayout::FIXED, 0, 0);
249
250  layout->StartRow(0, first_column_set);
251  layout->AddView(track_toggle_);
252  layout->AddView(clear_button_);
253  layout->AddView(filter_button_);
254  layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
255  layout->StartRow(1.0f, table_column_set);
256  layout->AddView(table_);
257}
258
259gfx::Size AboutIPCDialog::GetPreferredSize() {
260  return gfx::Size(800, 400);
261}
262
263views::View* AboutIPCDialog::GetContentsView() {
264  return this;
265}
266
267int AboutIPCDialog::GetDialogButtons() const {
268  // Don't want OK or Cancel.
269  return 0;
270}
271
272std::wstring AboutIPCDialog::GetWindowTitle() const {
273  return L"about:ipc";
274}
275
276void AboutIPCDialog::Layout() {
277  if (!message_list_.m_hWnd) {
278    HWND parent_window = GetRootView()->GetWidget()->GetNativeView();
279
280    RECT rect = {0, 0, 10, 10};
281    HWND list_hwnd = message_list_.Create(parent_window,
282        rect, NULL, WS_CHILD | WS_VISIBLE | LVS_SORTASCENDING);
283    message_list_.SetViewType(LVS_REPORT);
284    message_list_.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT);
285
286    int column_index = 0;
287    message_list_.InsertColumn(kTimeColumn, L"time", LVCFMT_LEFT, 80);
288    message_list_.InsertColumn(kChannelColumn, L"channel", LVCFMT_LEFT, 110);
289    message_list_.InsertColumn(kMessageColumn, L"message", LVCFMT_LEFT, 240);
290    message_list_.InsertColumn(kFlagsColumn, L"flags", LVCFMT_LEFT, 50);
291    message_list_.InsertColumn(kDispatchColumn, L"dispatch (ms)", LVCFMT_RIGHT,
292                               80);
293    message_list_.InsertColumn(kProcessColumn, L"process (ms)", LVCFMT_RIGHT,
294                               80);
295    message_list_.InsertColumn(kParamsColumn, L"parameters", LVCFMT_LEFT, 500);
296
297    table_->Attach(list_hwnd);
298  }
299
300  View::Layout();
301}
302
303void AboutIPCDialog::Log(const IPC::LogData& data) {
304  if (disabled_messages.find(data.type) != disabled_messages.end())
305    return;  // Message type is filtered out.
306
307  base::Time sent = base::Time::FromInternalValue(data.sent);
308  base::Time::Exploded exploded;
309  sent.LocalExplode(&exploded);
310  if (exploded.hour > 12)
311    exploded.hour -= 12;
312
313  std::wstring sent_str = StringPrintf(L"%02d:%02d:%02d.%03d",
314      exploded.hour, exploded.minute, exploded.second, exploded.millisecond);
315
316  int count = message_list_.GetItemCount();
317  int index = message_list_.InsertItem(count, sent_str.c_str());
318
319  message_list_.SetItemText(index, kTimeColumn, sent_str.c_str());
320  message_list_.SetItemText(index, kChannelColumn,
321                            ASCIIToWide(data.channel).c_str());
322
323  std::string message_name;
324  IPC::Logging::GetMessageText(data.type, &message_name, NULL, NULL);
325  message_list_.SetItemText(index, kMessageColumn,
326                            UTF8ToWide(message_name).c_str());
327  message_list_.SetItemText(index, kFlagsColumn,
328                            UTF8ToWide(data.flags).c_str());
329
330  int64 time_to_send = (base::Time::FromInternalValue(data.receive) -
331      sent).InMilliseconds();
332  // time can go backwards by a few ms (see Time), don't show that.
333  time_to_send = std::max(static_cast<int>(time_to_send), 0);
334  std::wstring temp = StringPrintf(L"%d", time_to_send);
335  message_list_.SetItemText(index, kDispatchColumn, temp.c_str());
336
337  int64 time_to_process = (base::Time::FromInternalValue(data.dispatch) -
338      base::Time::FromInternalValue(data.receive)).InMilliseconds();
339  time_to_process = std::max(static_cast<int>(time_to_process), 0);
340  temp = StringPrintf(L"%d", time_to_process);
341  message_list_.SetItemText(index, kProcessColumn, temp.c_str());
342
343  message_list_.SetItemText(index, kParamsColumn,
344                            UTF8ToWide(data.params).c_str());
345  message_list_.EnsureVisible(index, FALSE);
346}
347
348bool AboutIPCDialog::CanResize() const {
349  return true;
350}
351
352void AboutIPCDialog::ButtonPressed(
353    views::Button* button, const views::Event& event) {
354  if (button == track_toggle_) {
355    if (tracking_) {
356      track_toggle_->SetText(kStartTrackingLabel);
357      tracking_ = false;
358      g_browser_process->SetIPCLoggingEnabled(false);
359    } else {
360      track_toggle_->SetText(kStopTrackingLabel);
361      tracking_ = true;
362      g_browser_process->SetIPCLoggingEnabled(true);
363    }
364    track_toggle_->SchedulePaint();
365  } else if (button == clear_button_) {
366    message_list_.DeleteAllItems();
367  } else if (button == filter_button_) {
368    RunSettingsDialog(GetRootView()->GetWidget()->GetNativeView());
369  }
370}
371
372namespace browser {
373
374void ShowAboutIPCDialog() {
375  AboutIPCDialog::RunDialog();
376}
377
378} // namespace browser
379
380#endif  // IPC_MESSAGE_LOG_ENABLED
381