1// Copyright (c) 2009 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#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h"
5
6#include <gdk/gdkkeysyms.h>
7#include <string>
8#include <utility>
9#include <vector>
10
11#include "base/basictypes.h"
12#include "base/file_util.h"
13#include "base/logging.h"
14#include "base/path_service.h"
15#include "base/string_util.h"
16#include "chrome/common/chrome_paths.h"
17#include "content/common/edit_command.h"
18#include "content/common/native_web_keyboard_event.h"
19#include "testing/gtest/include/gtest/gtest.h"
20
21class GtkKeyBindingsHandlerTest : public testing::Test {
22 protected:
23  struct EditCommand {
24    const char* name;
25    const char* value;
26  };
27
28  GtkKeyBindingsHandlerTest()
29      : window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
30        handler_(NULL) {
31    FilePath gtkrc;
32    PathService::Get(chrome::DIR_TEST_DATA, &gtkrc);
33    gtkrc = gtkrc.AppendASCII("gtk_key_bindings_test_gtkrc");
34
35    gtk_rc_parse(gtkrc.value().c_str());
36
37    GtkWidget* fixed = gtk_fixed_new();
38    handler_ = new GtkKeyBindingsHandler(fixed);
39    gtk_container_add(GTK_CONTAINER(window_), fixed);
40    gtk_widget_show(fixed);
41    gtk_widget_show(window_);
42  }
43  ~GtkKeyBindingsHandlerTest() {
44    gtk_widget_destroy(window_);
45    delete handler_;
46  }
47
48  NativeWebKeyboardEvent NewNativeWebKeyboardEvent(guint keyval, guint state) {
49    GdkKeymap* keymap =
50        gdk_keymap_get_for_display(gtk_widget_get_display(window_));
51
52    GdkKeymapKey *keys = NULL;
53    gint n_keys = 0;
54    if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
55      GdkEventKey event;
56      event.type = GDK_KEY_PRESS;
57      event.window = NULL;
58      event.send_event = 0;
59      event.time = 0;
60      event.state = state;
61      event.keyval = keyval;
62      event.length = 0;
63      event.string = NULL;
64      event.hardware_keycode = keys[0].keycode;
65      event.group = keys[0].group;
66      event.is_modifier = 0;
67      g_free(keys);
68      return NativeWebKeyboardEvent(&event);
69    }
70    LOG(ERROR) << "Failed to create key event for keyval:" << keyval;
71    return NativeWebKeyboardEvent();
72  }
73
74  void TestKeyBinding(const NativeWebKeyboardEvent& event,
75                      const EditCommand expected_result[],
76                      size_t size) {
77    EditCommands result;
78    ASSERT_TRUE(handler_->Match(event, &result));
79    ASSERT_EQ(size, result.size());
80    for (size_t i = 0; i < size; ++i) {
81      ASSERT_STREQ(expected_result[i].name, result[i].name.c_str());
82      ASSERT_STREQ(expected_result[i].value, result[i].value.c_str());
83    }
84  }
85
86 protected:
87  GtkWidget* window_;
88  GtkKeyBindingsHandler* handler_;
89};
90
91TEST_F(GtkKeyBindingsHandlerTest, MoveCursor) {
92  static const EditCommand kEditCommands[] = {
93    // "move-cursor" (logical-positions, -2, 0)
94    { "MoveBackward", "" },
95    { "MoveBackward", "" },
96    // "move-cursor" (logical-positions, 2, 0)
97    { "MoveForward", "" },
98    { "MoveForward", "" },
99    // "move-cursor" (visual-positions, -1, 1)
100    { "MoveLeftAndModifySelection", "" },
101    // "move-cursor" (visual-positions, 1, 1)
102    { "MoveRightAndModifySelection", "" },
103    // "move-cursor" (words, -1, 0)
104    { "MoveWordBackward", "" },
105    // "move-cursor" (words, 1, 0)
106    { "MoveWordForward", "" },
107    // "move-cursor" (display-lines, -1, 0)
108    { "MoveUp", "" },
109    // "move-cursor" (display-lines, 1, 0)
110    { "MoveDown", "" },
111    // "move-cursor" (display-line-ends, -1, 0)
112    { "MoveToBeginningOfLine", "" },
113    // "move-cursor" (display-line-ends, 1, 0)
114    { "MoveToEndOfLine", "" },
115    // "move-cursor" (paragraph-ends, -1, 0)
116    { "MoveToBeginningOfParagraph", "" },
117    // "move-cursor" (paragraph-ends, 1, 0)
118    { "MoveToEndOfParagraph", "" },
119    // "move-cursor" (pages, -1, 0)
120    { "MovePageUp", "" },
121    // "move-cursor" (pages, 1, 0)
122    { "MovePageDown", "" },
123    // "move-cursor" (buffer-ends, -1, 0)
124    { "MoveToBeginningOfDocument", "" },
125    // "move-cursor" (buffer-ends, 1, 0)
126    { "MoveToEndOfDocument", "" }
127  };
128
129  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_1, GDK_CONTROL_MASK),
130                 kEditCommands, arraysize(kEditCommands));
131}
132
133TEST_F(GtkKeyBindingsHandlerTest, DeleteFromCursor) {
134  static const EditCommand kEditCommands[] = {
135    // "delete-from-cursor" (chars, -2)
136    { "DeleteBackward", "" },
137    { "DeleteBackward", "" },
138    // "delete-from-cursor" (chars, 2)
139    { "DeleteForward", "" },
140    { "DeleteForward", "" },
141    // "delete-from-cursor" (word-ends, -1)
142    { "DeleteWordBackward", "" },
143    // "delete-from-cursor" (word-ends, 1)
144    { "DeleteWordForward", "" },
145    // "delete-from-cursor" (words, -1)
146    { "MoveWordBackward", "" },
147    { "DeleteWordForward", "" },
148    // "delete-from-cursor" (words, 1)
149    { "MoveWordForward", "" },
150    { "DeleteWordBackward", "" },
151    // "delete-from-cursor" (display-lines, -1)
152    { "MoveToBeginningOfLine", "" },
153    { "DeleteToEndOfLine", "" },
154    // "delete-from-cursor" (display-lines, 1)
155    { "MoveToBeginningOfLine", "" },
156    { "DeleteToEndOfLine", "" },
157    // "delete-from-cursor" (display-line-ends, -1)
158    { "DeleteToBeginningOfLine", "" },
159    // "delete-from-cursor" (display-line-ends, 1)
160    { "DeleteToEndOfLine", "" },
161    // "delete-from-cursor" (paragraph-ends, -1)
162    { "DeleteToBeginningOfParagraph", "" },
163    // "delete-from-cursor" (paragraph-ends, 1)
164    { "DeleteToEndOfParagraph", "" },
165    // "delete-from-cursor" (paragraphs, -1)
166    { "MoveToBeginningOfParagraph", "" },
167    { "DeleteToEndOfParagraph", "" },
168    // "delete-from-cursor" (paragraphs, 1)
169    { "MoveToBeginningOfParagraph", "" },
170    { "DeleteToEndOfParagraph", "" },
171  };
172
173  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_2, GDK_CONTROL_MASK),
174                 kEditCommands, arraysize(kEditCommands));
175}
176
177TEST_F(GtkKeyBindingsHandlerTest, OtherActions) {
178  static const EditCommand kBackspace[] = {
179    { "DeleteBackward", "" }
180  };
181  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_3, GDK_CONTROL_MASK),
182                 kBackspace, arraysize(kBackspace));
183
184  static const EditCommand kCopyClipboard[] = {
185    { "Copy", "" }
186  };
187  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_4, GDK_CONTROL_MASK),
188                 kCopyClipboard, arraysize(kCopyClipboard));
189
190  static const EditCommand kCutClipboard[] = {
191    { "Cut", "" }
192  };
193  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_5, GDK_CONTROL_MASK),
194                 kCutClipboard, arraysize(kCutClipboard));
195
196  static const EditCommand kInsertAtCursor[] = {
197    { "InsertText", "hello" }
198  };
199  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_6, GDK_CONTROL_MASK),
200                 kInsertAtCursor, arraysize(kInsertAtCursor));
201
202  static const EditCommand kPasteClipboard[] = {
203    { "Paste", "" }
204  };
205  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_7, GDK_CONTROL_MASK),
206                 kPasteClipboard, arraysize(kPasteClipboard));
207
208  static const EditCommand kSelectAll[] = {
209    { "Unselect", "" },
210    { "SelectAll", "" }
211  };
212  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_8, GDK_CONTROL_MASK),
213                 kSelectAll, arraysize(kSelectAll));
214
215  static const EditCommand kSetAnchor[] = {
216    { "SetMark", "" }
217  };
218  TestKeyBinding(NewNativeWebKeyboardEvent(GDK_9, GDK_CONTROL_MASK),
219                 kSetAnchor, arraysize(kSetAnchor));
220}
221