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