summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsuzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-10 07:22:48 +0000
committersuzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-10 07:22:48 +0000
commit4467058769b543946faf628c3b20a0803ca3f061 (patch)
treef4011c5e25465f291de65e72f688a46ad2b6677d
parent85f07ece0d4e006389f238c804e77b0496c02c30 (diff)
downloadchromium_src-4467058769b543946faf628c3b20a0803ca3f061.zip
chromium_src-4467058769b543946faf628c3b20a0803ca3f061.tar.gz
chromium_src-4467058769b543946faf628c3b20a0803ca3f061.tar.bz2
Supports Gtk keyboard themes.
This CL fixes issue 11480: Support GTK keyboard themes (emacs keybindings). A new class GtkKeyBindingsHandler has been added, which matches a key event against key bindings defined in current Gtk keyboard theme. A new render message ViewMsg_SetEditCommandsForNextKeyEvent has been added for sending edit commands associated to a key event to renderer. This message shall be sent just before sending the key event. RenderView will handle this event and cache the edit commands until the key event is processed. When processing the key event, EditClientImpl::handleKeyboardEvent() will eventually be called to handle the key event, if it's not handled by DOM and the focus is inside an input box. Then a newly added method WebViewDelegate::ExecuteEditCommandsForCurrentKeyEvent(), which is implemented in RenderView, will be called by EditClientImpl::handleKeyboardEvent() to execute edit commands previously sent from browser by ViewMsg_SetEditCommandsForNextKeyEvent message. If WebViewDelegate::ExecuteEditCommandsForCurrentKeyEvent() returns false, which means the key event doesn't have edit command associated, EditClientImpl will handle the key event with built-in logic, which may trigger a built-in key binding. With this approach, system defined key bindings always have higher priority than built-in key bindings defined in editor_client_impl.cc. Known issue: If a key event matches not only a system defined key binding but also an accesskey of a DOM element, then both corresponding edit commands and accesskey action will be executed. Because accesskey is handled in WebViewImpl::CharEvent(), while edit commands are bound to RawKeyDown or KeyUp events. BUG=11480 "Support GTK keyboard themes (emacs keybindings)" TEST=Switch to Emacs keyboard theme by changing the value of gconf key "/desktop/gnome/interface/gtk_key_theme" to "Emacs", then starts chrome and opens a webpage with a text input box. Input something into the text box, then press any of the Emacs key bindings defined in /usr/share/themes/Emacs/gtk-2.0-key/gtkrc, to see if it works as expected. For example, ctrl-p should move the cursor up one line, and ctrl-k should delete to the end of paragraph. Review URL: http://codereview.chromium.org/165293 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25852 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/renderer_host/gtk_im_context_wrapper.cc12
-rw-r--r--chrome/browser/renderer_host/gtk_key_bindings_handler.cc304
-rw-r--r--chrome/browser/renderer_host/gtk_key_bindings_handler.h130
-rw-r--r--chrome/browser/renderer_host/gtk_key_bindings_handler_unittest.cc220
-rw-r--r--chrome/browser/renderer_host/render_view_host.cc7
-rw-r--r--chrome/browser/renderer_host/render_view_host.h2
-rw-r--r--chrome/browser/renderer_host/render_widget_host.cc6
-rw-r--r--chrome/browser/renderer_host/render_widget_host.h3
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_gtk.cc15
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_gtk.h17
-rw-r--r--chrome/chrome.gyp5
-rw-r--r--chrome/common/edit_command.h24
-rw-r--r--chrome/common/render_messages.h21
-rw-r--r--chrome/common/render_messages_internal.h14
-rw-r--r--chrome/renderer/render_view.cc31
-rw-r--r--chrome/renderer/render_view.h9
-rw-r--r--chrome/renderer/render_widget.cc5
-rw-r--r--chrome/renderer/render_widget.h4
-rw-r--r--chrome/test/data/gtk_key_bindings_test_gtkrc83
-rw-r--r--webkit/glue/editor_client_impl.cc6
-rw-r--r--webkit/glue/webview_delegate.h13
-rw-r--r--webkit/glue/webview_impl.cc11
22 files changed, 925 insertions, 17 deletions
diff --git a/chrome/browser/renderer_host/gtk_im_context_wrapper.cc b/chrome/browser/renderer_host/gtk_im_context_wrapper.cc
index 265c9f1..90cc00c 100644
--- a/chrome/browser/renderer_host/gtk_im_context_wrapper.cc
+++ b/chrome/browser/renderer_host/gtk_im_context_wrapper.cc
@@ -146,7 +146,7 @@ void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
if (event->type == GDK_KEY_PRESS && !filtered)
ProcessUnfilteredKeyPressEvent(&wke);
else if (event->type == GDK_KEY_RELEASE)
- host_view_->GetRenderWidgetHost()->ForwardKeyboardEvent(wke);
+ host_view_->ForwardKeyboardEvent(wke);
// End of key event processing.
is_in_key_event_handler_ = false;
@@ -291,15 +291,13 @@ void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
wke->os_event->state = 0;
}
}
- host_view_->GetRenderWidgetHost()->ForwardKeyboardEvent(*wke);
+ host_view_->ForwardKeyboardEvent(*wke);
}
void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
NativeWebKeyboardEvent* wke) {
- RenderWidgetHost* host = host_view_->GetRenderWidgetHost();
-
// Send keydown event as it, because it's not filtered by IME.
- host->ForwardKeyboardEvent(*wke);
+ host_view_->ForwardKeyboardEvent(*wke);
// IME is disabled by WebKit or the GtkIMContext object cannot handle
// this key event.
@@ -315,7 +313,7 @@ void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
// see WebInputEventFactory::keyboardEvent() for details.
if (wke->text[0]) {
wke->type = WebKit::WebInputEvent::Char;
- host->ForwardKeyboardEvent(*wke);
+ host_view_->ForwardKeyboardEvent(*wke);
}
}
@@ -338,7 +336,7 @@ void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
NativeWebKeyboardEvent char_event(commit_text_[0],
event->state,
base::Time::Now().ToDoubleT());
- host->ForwardKeyboardEvent(char_event);
+ host_view_->ForwardKeyboardEvent(char_event);
} else {
committed = true;
// Send an IME event.
diff --git a/chrome/browser/renderer_host/gtk_key_bindings_handler.cc b/chrome/browser/renderer_host/gtk_key_bindings_handler.cc
new file mode 100644
index 0000000..f947574
--- /dev/null
+++ b/chrome/browser/renderer_host/gtk_key_bindings_handler.cc
@@ -0,0 +1,304 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "chrome/common/native_web_keyboard_event.h"
+
+GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget)
+ : handler_(CreateNewHandler()),
+ edit_commands_(NULL),
+ enabled_(false) {
+ DCHECK(GTK_IS_FIXED(parent_widget));
+ // We need add the |handler_| object into gtk widget hierarchy, so that
+ // gtk_bindings_activate_event() can find correct display and keymaps from
+ // the |handler_| object.
+ gtk_fixed_put(GTK_FIXED(parent_widget), handler_.get(), -1, -1);
+}
+
+GtkKeyBindingsHandler::~GtkKeyBindingsHandler() {
+ handler_.Destroy();
+}
+
+bool GtkKeyBindingsHandler::Match(const NativeWebKeyboardEvent& wke,
+ EditCommands* edit_commands) {
+ if (!enabled_ || wke.type == WebKit::WebInputEvent::Char ||
+ !wke.os_event || wke.os_event->keyval == GDK_VoidSymbol)
+ return false;
+
+ edit_commands_.clear();
+ // If this key event matches a predefined key binding, corresponding signal
+ // will be emitted.
+ gtk_bindings_activate_event(GTK_OBJECT(handler_.get()), wke.os_event);
+
+ bool matched = !edit_commands_.empty();
+ if (edit_commands)
+ edit_commands->swap(edit_commands_);
+ return matched;
+}
+
+GtkWidget* GtkKeyBindingsHandler::CreateNewHandler() {
+ Handler* handler =
+ static_cast<Handler*>(g_object_new(HandlerGetType(), NULL));
+
+ handler->owner = this;
+
+ // We don't need to show the |handler| object on screen, so set its size to
+ // zero.
+ gtk_widget_set_size_request(GTK_WIDGET(handler), 0, 0);
+
+ // Prevents it from handling any events by itself.
+ gtk_widget_set_sensitive(GTK_WIDGET(handler), FALSE);
+ gtk_widget_set_events(GTK_WIDGET(handler), 0);
+ GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(handler), GTK_CAN_FOCUS);
+
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // "move-viewport", "select-all" and "toggle-cursor-visible" have no
+ // corresponding virtual methods. Prior to glib 2.18 (gtk 2.14), there is no
+ // way to override the default class handler of a signal. So we need hook
+ // these signal explicitly.
+ g_signal_connect(handler, "move-viewport", G_CALLBACK(MoveViewport), NULL);
+ g_signal_connect(handler, "select-all", G_CALLBACK(SelectAll), NULL);
+ g_signal_connect(handler, "toggle-cursor-visible",
+ G_CALLBACK(ToggleCursorVisible), NULL);
+#endif
+ return GTK_WIDGET(handler);
+}
+
+void GtkKeyBindingsHandler::EditCommandMatched(
+ const std::string& name, const std::string& value) {
+ edit_commands_.push_back(EditCommand(name, value));
+}
+
+void GtkKeyBindingsHandler::HandlerInit(Handler *self) {
+ self->owner = NULL;
+}
+
+void GtkKeyBindingsHandler::HandlerClassInit(HandlerClass *klass) {
+ GtkTextViewClass* text_view_class = GTK_TEXT_VIEW_CLASS(klass);
+
+ // Overrides all virtual methods related to editor key bindings.
+ text_view_class->backspace = BackSpace;
+ text_view_class->copy_clipboard = CopyClipboard;
+ text_view_class->cut_clipboard = CutClipboard;
+ text_view_class->delete_from_cursor = DeleteFromCursor;
+ text_view_class->insert_at_cursor = InsertAtCursor;
+ text_view_class->move_cursor = MoveCursor;
+ text_view_class->paste_clipboard = PasteClipboard;
+ text_view_class->set_anchor = SetAnchor;
+ text_view_class->toggle_overwrite = ToggleOverwrite;
+
+#if GTK_CHECK_VERSION(2, 14, 0)
+ // "move-viewport", "select-all" and "toggle-cursor-visible" have no
+ // corresponding virtual methods. Since glib 2.18 (gtk 2.14),
+ // g_signal_override_class_handler() is introduced to override a signal
+ // handler.
+ g_signal_override_class_handler("move-viewport",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(MoveViewport));
+
+ g_signal_override_class_handler("select-all",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(SelectAll));
+
+ g_signal_override_class_handler("toggle-cursor-visible",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(ToggleCursorVisible));
+#endif
+}
+
+GType GtkKeyBindingsHandler::HandlerGetType() {
+ static volatile gsize type_id_volatile = 0;
+ if (g_once_init_enter(&type_id_volatile)) {
+ GType type_id = g_type_register_static_simple(
+ GTK_TYPE_TEXT_VIEW,
+ g_intern_static_string("GtkKeyBindingsHandler"),
+ sizeof(HandlerClass),
+ reinterpret_cast<GClassInitFunc>(HandlerClassInit),
+ sizeof(Handler),
+ reinterpret_cast<GInstanceInitFunc>(HandlerInit),
+ static_cast<GTypeFlags>(0));
+ g_once_init_leave(&type_id_volatile, type_id);
+ }
+ return type_id_volatile;
+}
+
+GtkKeyBindingsHandler* GtkKeyBindingsHandler::GetHandlerOwner(
+ GtkTextView* text_view) {
+ Handler* handler = G_TYPE_CHECK_INSTANCE_CAST(
+ text_view, HandlerGetType(), Handler);
+ DCHECK(handler);
+ return handler->owner;
+}
+
+void GtkKeyBindingsHandler::BackSpace(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("DeleteBackward", "");
+}
+
+void GtkKeyBindingsHandler::CopyClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Copy", "");
+}
+
+void GtkKeyBindingsHandler::CutClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Cut", "");
+}
+
+void GtkKeyBindingsHandler::DeleteFromCursor(
+ GtkTextView* text_view, GtkDeleteType type, gint count) {
+ if (!count)
+ return;
+
+ const char *commands[3] = { NULL, NULL, NULL };
+ switch (type) {
+ case GTK_DELETE_CHARS:
+ commands[0] = (count > 0 ? "DeleteForward" : "DeleteBackward");
+ break;
+ case GTK_DELETE_WORD_ENDS:
+ commands[0] = (count > 0 ? "DeleteWordForward" : "DeleteWordBackward");
+ break;
+ case GTK_DELETE_WORDS:
+ if (count > 0) {
+ commands[0] = "MoveWordForward";
+ commands[1] = "DeleteWordBackward";
+ } else {
+ commands[0] = "MoveWordBackward";
+ commands[1] = "DeleteWordForward";
+ }
+ break;
+ case GTK_DELETE_DISPLAY_LINES:
+ commands[0] = "MoveToBeginningOfLine";
+ commands[1] = "DeleteToEndOfLine";
+ break;
+ case GTK_DELETE_DISPLAY_LINE_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfLine" :
+ "DeleteToBeginningOfLine");
+ break;
+ case GTK_DELETE_PARAGRAPH_ENDS:
+ commands[0] = (count > 0 ? "DeleteToEndOfParagraph" :
+ "DeleteToBeginningOfParagraph");
+ break;
+ case GTK_DELETE_PARAGRAPHS:
+ commands[0] = "MoveToBeginningOfParagraph";
+ commands[1] = "DeleteToEndOfParagraph";
+ break;
+ default:
+ // GTK_DELETE_WHITESPACE has no corresponding editor command.
+ return;
+ }
+
+ GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count) {
+ for (const char* const* p = commands; *p; ++p)
+ owner->EditCommandMatched(*p, "");
+ }
+}
+
+void GtkKeyBindingsHandler::InsertAtCursor(GtkTextView* text_view,
+ const gchar* str) {
+ if (str && *str)
+ GetHandlerOwner(text_view)->EditCommandMatched("InsertText", str);
+}
+
+void GtkKeyBindingsHandler::MoveCursor(
+ GtkTextView* text_view, GtkMovementStep step, gint count,
+ gboolean extend_selection) {
+ if (!count)
+ return;
+
+ std::string command;
+ switch (step) {
+ case GTK_MOVEMENT_LOGICAL_POSITIONS:
+ command = (count > 0 ? "MoveForward" : "MoveBackward");
+ break;
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ command = (count > 0 ? "MoveRight" : "MoveLeft");
+ break;
+ case GTK_MOVEMENT_WORDS:
+ command = (count > 0 ? "MoveWordForward" : "MoveWordBackward");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ command = (count > 0 ? "MoveDown" : "MoveUp");
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
+ command = (count > 0 ? "MoveToEndOfLine" : "MoveToBeginningOfLine");
+ break;
+ case GTK_MOVEMENT_PARAGRAPH_ENDS:
+ command = (count > 0 ? "MoveToEndOfParagraph" :
+ "MoveToBeginningOfParagraph");
+ break;
+ case GTK_MOVEMENT_PAGES:
+ command = (count > 0 ? "MovePageDown" : "MovePageUp");
+ break;
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ command = (count > 0 ? "MoveToEndOfDocument" :
+ "MoveToBeginningOfDocument");
+ break;
+ default:
+ // GTK_MOVEMENT_PARAGRAPHS and GTK_MOVEMENT_HORIZONTAL_PAGES have
+ // no corresponding editor commands.
+ return;
+ }
+
+ GtkKeyBindingsHandler* owner = GetHandlerOwner(text_view);
+ if (extend_selection)
+ command.append("AndModifySelection");
+ if (count < 0)
+ count = -count;
+ for (; count > 0; --count)
+ owner->EditCommandMatched(command, "");
+}
+
+void GtkKeyBindingsHandler::MoveViewport(
+ GtkTextView* text_view, GtkScrollStep step, gint count) {
+ // Not supported by webkit.
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // Before gtk 2.14.0, there is no way to override a non-virtual default signal
+ // handler, so we need stop the signal emission explicitly to prevent the
+ // default handler from being executed.
+ g_signal_stop_emission_by_name(text_view, "move-viewport");
+#endif
+}
+
+void GtkKeyBindingsHandler::PasteClipboard(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("Paste", "");
+}
+
+void GtkKeyBindingsHandler::SelectAll(GtkTextView* text_view, gboolean select) {
+ if (select)
+ GetHandlerOwner(text_view)->EditCommandMatched("SelectAll", "");
+ else
+ GetHandlerOwner(text_view)->EditCommandMatched("Unselect", "");
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // Before gtk 2.14.0, there is no way to override a non-virtual default signal
+ // handler, so we need stop the signal emission explicitly to prevent the
+ // default handler from being executed.
+ g_signal_stop_emission_by_name(text_view, "select-all");
+#endif
+}
+
+void GtkKeyBindingsHandler::SetAnchor(GtkTextView* text_view) {
+ GetHandlerOwner(text_view)->EditCommandMatched("SetMark", "");
+}
+
+void GtkKeyBindingsHandler::ToggleCursorVisible(GtkTextView* text_view) {
+ // Not supported by webkit.
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // Before gtk 2.14.0, there is no way to override a non-virtual default signal
+ // handler, so we need stop the signal emission explicitly to prevent the
+ // default handler from being executed.
+ g_signal_stop_emission_by_name(text_view, "toggle-cursor-visible");
+#endif
+}
+
+void GtkKeyBindingsHandler::ToggleOverwrite(GtkTextView* text_view) {
+ // Not supported by webkit.
+}
diff --git a/chrome/browser/renderer_host/gtk_key_bindings_handler.h b/chrome/browser/renderer_host/gtk_key_bindings_handler.h
new file mode 100644
index 0000000..b2c93e6
--- /dev/null
+++ b/chrome/browser/renderer_host/gtk_key_bindings_handler.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+#define CHROME_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+
+#include <gtk/gtk.h>
+
+#include <string>
+
+#include "chrome/common/edit_command.h"
+#include "chrome/common/owned_widget_gtk.h"
+
+class NativeWebKeyboardEvent;
+
+// This class is a convenience class for handling editor key bindings defined
+// in gtk keyboard theme.
+// In gtk, only GtkEntry and GtkTextView support customizing editor key bindings
+// through keyboard theme. And in gtk keyboard theme definition file, each key
+// binding must be bound to a specific class or object. So existing keyboard
+// themes only define editor key bindings exactly for GtkEntry and GtkTextView.
+// Then, the only way for us to intercept editor key bindings defined in
+// keyboard theme, is to create a GtkEntry or GtkTextView object and call
+// gtk_bindings_activate_event() against it for the key events. If a key event
+// matches a predefined key binding, corresponding signal will be emitted.
+// GtkTextView is used here because it supports more key bindings than GtkEntry,
+// but in order to minimize the side effect of using a GtkTextView object, a new
+// class derived from GtkTextView is used, which overrides all signals related
+// to key bindings, to make sure GtkTextView won't receive them.
+//
+// See third_party/WebKit/WebCore/editing/EditorCommand.cpp for detailed
+// definition of webkit edit commands.
+// See webkit/glue/editor_client_impl.cc for key bindings predefined in our
+// webkit glue.
+class GtkKeyBindingsHandler {
+ public:
+ explicit GtkKeyBindingsHandler(GtkWidget* parent_widget);
+ ~GtkKeyBindingsHandler();
+
+ // Key bindings handler will be disabled when IME is disabled by webkit.
+ void set_enabled(bool enabled) {
+ enabled_ = enabled;
+ }
+
+ // Matches a key event against predefined gtk key bindings, false will be
+ // returned if the key event doesn't correspond to a predefined key binding.
+ // Edit commands matched with |wke| will be stored in |edit_commands|.
+ bool Match(const NativeWebKeyboardEvent& wke, EditCommands* edit_commands);
+
+ private:
+ // Object structure of Handler class, which is derived from GtkTextView.
+ struct Handler {
+ GtkTextView parent_object;
+ GtkKeyBindingsHandler *owner;
+ };
+
+ // Class structure of Handler class.
+ struct HandlerClass {
+ GtkTextViewClass parent_class;
+ };
+
+ // Creates a new instance of Handler class.
+ GtkWidget* CreateNewHandler();
+
+ // Adds an edit command to the key event.
+ void EditCommandMatched(const std::string& name, const std::string& value);
+
+ // Initializes Handler structure.
+ static void HandlerInit(Handler *self);
+
+ // Initializes HandlerClass structure.
+ static void HandlerClassInit(HandlerClass *klass);
+
+ // Registeres Handler class to GObject type system and return its type id.
+ static GType HandlerGetType();
+
+ // Gets the GtkKeyBindingsHandler object which owns the Handler object.
+ static GtkKeyBindingsHandler* GetHandlerOwner(GtkTextView* text_view);
+
+ // Handler of "backspace" signal.
+ static void BackSpace(GtkTextView* text_view);
+
+ // Handler of "copy-clipboard" signal.
+ static void CopyClipboard(GtkTextView* text_view);
+
+ // Handler of "cut-clipboard" signal.
+ static void CutClipboard(GtkTextView* text_view);
+
+ // Handler of "delete-from-cursor" signal.
+ static void DeleteFromCursor(GtkTextView* text_view, GtkDeleteType type,
+ gint count);
+
+ // Handler of "insert-at-cursor" signal.
+ static void InsertAtCursor(GtkTextView* text_view, const gchar* str);
+
+ // Handler of "move-cursor" signal.
+ static void MoveCursor(GtkTextView* text_view, GtkMovementStep step,
+ gint count, gboolean extend_selection);
+
+ // Handler of "move-viewport" signal.
+ static void MoveViewport(GtkTextView* text_view, GtkScrollStep step,
+ gint count);
+
+ // Handler of "paste-clipboard" signal.
+ static void PasteClipboard(GtkTextView* text_view);
+
+ // Handler of "select-all" signal.
+ static void SelectAll(GtkTextView* text_view, gboolean select);
+
+ // Handler of "set-anchor" signal.
+ static void SetAnchor(GtkTextView* text_view);
+
+ // Handler of "toggle-cursor-visible" signal.
+ static void ToggleCursorVisible(GtkTextView* text_view);
+
+ // Handler of "toggle-overwrite" signal.
+ static void ToggleOverwrite(GtkTextView* text_view);
+
+ OwnedWidgetGtk handler_;
+
+ // Buffer to store the match results.
+ EditCommands edit_commands_;
+
+ // Indicates if key bindings handler is enabled or not.
+ // It'll only be enabled if IME is enabled by webkit.
+ bool enabled_;
+};
+
+#endif // CHROME_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
diff --git a/chrome/browser/renderer_host/gtk_key_bindings_handler_unittest.cc b/chrome/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
new file mode 100644
index 0000000..a6c6753
--- /dev/null
+++ b/chrome/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/edit_command.h"
+#include "chrome/common/native_web_keyboard_event.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class GtkKeyBindingsHandlerTest : public testing::Test {
+ protected:
+ struct EditCommand {
+ const char* name;
+ const char* value;
+ };
+
+ GtkKeyBindingsHandlerTest()
+ : window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)),
+ handler_(NULL) {
+ std::wstring gtkrc;
+ PathService::Get(chrome::DIR_TEST_DATA, &gtkrc);
+ file_util::AppendToPath(&gtkrc, L"gtk_key_bindings_test_gtkrc");
+ gtk_rc_parse(WideToUTF8(gtkrc).c_str());
+
+ GtkWidget* fixed = gtk_fixed_new();
+ handler_ = new GtkKeyBindingsHandler(fixed);
+ gtk_container_add(GTK_CONTAINER(window_), fixed);
+ gtk_widget_show(fixed);
+ gtk_widget_show(window_);
+ }
+ ~GtkKeyBindingsHandlerTest() {
+ gtk_widget_destroy(window_);
+ delete handler_;
+ }
+
+ NativeWebKeyboardEvent NewNativeWebKeyboardEvent(guint keyval, guint state) {
+ GdkKeymap* keymap =
+ gdk_keymap_get_for_display(gtk_widget_get_display(window_));
+
+ GdkKeymapKey *keys = NULL;
+ gint n_keys = 0;
+ if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &n_keys)) {
+ GdkEventKey event;
+ event.type = GDK_KEY_PRESS;
+ event.window = NULL;
+ event.send_event = 0;
+ event.time = 0;
+ event.state = state;
+ event.keyval = keyval;
+ event.length = 0;
+ event.string = NULL;
+ event.hardware_keycode = keys[0].keycode;
+ event.group = keys[0].group;
+ event.is_modifier = 0;
+ g_free(keys);
+ return NativeWebKeyboardEvent(&event);
+ }
+ return NativeWebKeyboardEvent();
+ }
+
+ void TestKeyBinding(const NativeWebKeyboardEvent& event,
+ const EditCommand expected_result[],
+ size_t size) {
+ EditCommands result;
+ handler_->set_enabled(false);
+ ASSERT_FALSE(handler_->Match(event, &result));
+ handler_->set_enabled(true);
+ ASSERT_TRUE(handler_->Match(event, &result));
+ ASSERT_EQ(size, result.size());
+ for (size_t i = 0; i < size; ++i) {
+ ASSERT_STREQ(expected_result[i].name, result[i].name.c_str());
+ ASSERT_STREQ(expected_result[i].value, result[i].value.c_str());
+ }
+ }
+
+ protected:
+ GtkWidget* window_;
+ GtkKeyBindingsHandler* handler_;
+};
+
+TEST_F(GtkKeyBindingsHandlerTest, MoveCursor) {
+ static const EditCommand kEditCommands[] = {
+ // "move-cursor" (logical-positions, -2, 0)
+ { "MoveBackward", "" },
+ { "MoveBackward", "" },
+ // "move-cursor" (logical-positions, 2, 0)
+ { "MoveForward", "" },
+ { "MoveForward", "" },
+ // "move-cursor" (visual-positions, -1, 1)
+ { "MoveLeftAndModifySelection", "" },
+ // "move-cursor" (visual-positions, 1, 1)
+ { "MoveRightAndModifySelection", "" },
+ // "move-cursor" (words, -1, 0)
+ { "MoveWordBackward", "" },
+ // "move-cursor" (words, 1, 0)
+ { "MoveWordForward", "" },
+ // "move-cursor" (display-lines, -1, 0)
+ { "MoveUp", "" },
+ // "move-cursor" (display-lines, 1, 0)
+ { "MoveDown", "" },
+ // "move-cursor" (display-line-ends, -1, 0)
+ { "MoveToBeginningOfLine", "" },
+ // "move-cursor" (display-line-ends, 1, 0)
+ { "MoveToEndOfLine", "" },
+ // "move-cursor" (paragraph-ends, -1, 0)
+ { "MoveToBeginningOfParagraph", "" },
+ // "move-cursor" (paragraph-ends, 1, 0)
+ { "MoveToEndOfParagraph", "" },
+ // "move-cursor" (pages, -1, 0)
+ { "MovePageUp", "" },
+ // "move-cursor" (pages, 1, 0)
+ { "MovePageDown", "" },
+ // "move-cursor" (buffer-ends, -1, 0)
+ { "MoveToBeginningOfDocument", "" },
+ // "move-cursor" (buffer-ends, 1, 0)
+ { "MoveToEndOfDocument", "" }
+ };
+
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_1, GDK_CONTROL_MASK),
+ kEditCommands, arraysize(kEditCommands));
+}
+
+TEST_F(GtkKeyBindingsHandlerTest, DeleteFromCursor) {
+ static const EditCommand kEditCommands[] = {
+ // "delete-from-cursor" (chars, -2)
+ { "DeleteBackward", "" },
+ { "DeleteBackward", "" },
+ // "delete-from-cursor" (chars, 2)
+ { "DeleteForward", "" },
+ { "DeleteForward", "" },
+ // "delete-from-cursor" (word-ends, -1)
+ { "DeleteWordBackward", "" },
+ // "delete-from-cursor" (word-ends, 1)
+ { "DeleteWordForward", "" },
+ // "delete-from-cursor" (words, -1)
+ { "MoveWordBackward", "" },
+ { "DeleteWordForward", "" },
+ // "delete-from-cursor" (words, 1)
+ { "MoveWordForward", "" },
+ { "DeleteWordBackward", "" },
+ // "delete-from-cursor" (display-lines, -1)
+ { "MoveToBeginningOfLine", "" },
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (display-lines, 1)
+ { "MoveToBeginningOfLine", "" },
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (display-line-ends, -1)
+ { "DeleteToBeginningOfLine", "" },
+ // "delete-from-cursor" (display-line-ends, 1)
+ { "DeleteToEndOfLine", "" },
+ // "delete-from-cursor" (paragraph-ends, -1)
+ { "DeleteToBeginningOfParagraph", "" },
+ // "delete-from-cursor" (paragraph-ends, 1)
+ { "DeleteToEndOfParagraph", "" },
+ // "delete-from-cursor" (paragraphs, -1)
+ { "MoveToBeginningOfParagraph", "" },
+ { "DeleteToEndOfParagraph", "" },
+ // "delete-from-cursor" (paragraphs, 1)
+ { "MoveToBeginningOfParagraph", "" },
+ { "DeleteToEndOfParagraph", "" },
+ };
+
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_2, GDK_CONTROL_MASK),
+ kEditCommands, arraysize(kEditCommands));
+}
+
+TEST_F(GtkKeyBindingsHandlerTest, OtherActions) {
+ static const EditCommand kBackspace[] = {
+ { "DeleteBackward", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_3, GDK_CONTROL_MASK),
+ kBackspace, arraysize(kBackspace));
+
+ static const EditCommand kCopyClipboard[] = {
+ { "Copy", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_4, GDK_CONTROL_MASK),
+ kCopyClipboard, arraysize(kCopyClipboard));
+
+ static const EditCommand kCutClipboard[] = {
+ { "Cut", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_5, GDK_CONTROL_MASK),
+ kCutClipboard, arraysize(kCutClipboard));
+
+ static const EditCommand kInsertAtCursor[] = {
+ { "InsertText", "hello" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_6, GDK_CONTROL_MASK),
+ kInsertAtCursor, arraysize(kInsertAtCursor));
+
+ static const EditCommand kPasteClipboard[] = {
+ { "Paste", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_7, GDK_CONTROL_MASK),
+ kPasteClipboard, arraysize(kPasteClipboard));
+
+ static const EditCommand kSelectAll[] = {
+ { "Unselect", "" },
+ { "SelectAll", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_8, GDK_CONTROL_MASK),
+ kSelectAll, arraysize(kSelectAll));
+
+ static const EditCommand kSetAnchor[] = {
+ { "SetMark", "" }
+ };
+ TestKeyBinding(NewNativeWebKeyboardEvent(GDK_9, GDK_CONTROL_MASK),
+ kSetAnchor, arraysize(kSetAnchor));
+}
diff --git a/chrome/browser/renderer_host/render_view_host.cc b/chrome/browser/renderer_host/render_view_host.cc
index 64fc364..2acd476 100644
--- a/chrome/browser/renderer_host/render_view_host.cc
+++ b/chrome/browser/renderer_host/render_view_host.cc
@@ -1592,6 +1592,13 @@ void RenderViewHost::ForwardEditCommand(const std::string& name,
Send(message);
}
+void RenderViewHost::ForwardEditCommandsForNextKeyEvent(
+ const EditCommands& edit_commands) {
+ IPC::Message* message = new ViewMsg_SetEditCommandsForNextKeyEvent(
+ routing_id(), edit_commands);
+ Send(message);
+}
+
void RenderViewHost::ForwardMessageFromExternalHost(const std::string& message,
const std::string& origin,
const std::string& target) {
diff --git a/chrome/browser/renderer_host/render_view_host.h b/chrome/browser/renderer_host/render_view_host.h
index 28cadfe..e4b6332 100644
--- a/chrome/browser/renderer_host/render_view_host.h
+++ b/chrome/browser/renderer_host/render_view_host.h
@@ -410,6 +410,8 @@ class RenderViewHost : public RenderWidgetHost,
virtual void ForwardMouseEvent(const WebKit::WebMouseEvent& mouse_event);
virtual void ForwardEditCommand(const std::string& name,
const std::string& value);
+ virtual void ForwardEditCommandsForNextKeyEvent(
+ const EditCommands& edit_commands);
virtual gfx::Rect GetRootWindowResizerRect() const;
// Creates a new RenderView with the given route id.
diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc
index 473f129..05b90c98 100644
--- a/chrome/browser/renderer_host/render_widget_host.cc
+++ b/chrome/browser/renderer_host/render_widget_host.cc
@@ -436,6 +436,12 @@ void RenderWidgetHost::ForwardEditCommand(const std::string& name,
// edge cases for which edit commands don't make sense.
}
+void RenderWidgetHost::ForwardEditCommandsForNextKeyEvent(
+ const EditCommands& edit_commands) {
+ // We don't need an implementation of this function here since this message is
+ // only handled by RenderView.
+}
+
void RenderWidgetHost::RendererExited() {
// Clearing this flag causes us to re-create the renderer when recovering
// from a crashed renderer.
diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h
index 90e0b9c..b76d4c7 100644
--- a/chrome/browser/renderer_host/render_widget_host.h
+++ b/chrome/browser/renderer_host/render_widget_host.h
@@ -13,6 +13,7 @@
#include "base/gfx/size.h"
#include "base/scoped_ptr.h"
#include "base/timer.h"
+#include "chrome/common/edit_command.h"
#include "chrome/common/native_web_keyboard_event.h"
#include "chrome/common/property_bag.h"
#include "ipc/ipc_channel.h"
@@ -245,6 +246,8 @@ class RenderWidgetHost : public IPC::Channel::Listener,
void ForwardKeyboardEvent(const NativeWebKeyboardEvent& key_event);
virtual void ForwardEditCommand(const std::string& name,
const std::string& value);
+ virtual void ForwardEditCommandsForNextKeyEvent(
+ const EditCommands& edit_commands);
// Update the text direction of the focused input element and notify it to a
// renderer process.
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
index 460d4f7..0fb62b39 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -28,6 +28,7 @@
#include "chrome/common/x11_util.h"
#include "chrome/browser/renderer_host/backing_store.h"
#include "chrome/browser/renderer_host/gtk_im_context_wrapper.h"
+#include "chrome/browser/renderer_host/gtk_key_bindings_handler.h"
#include "chrome/browser/renderer_host/render_widget_host.h"
#include "webkit/api/public/gtk/WebInputEventFactory.h"
#include "webkit/glue/webcursor_gtk_data.h"
@@ -299,6 +300,8 @@ void RenderWidgetHostViewGtk::InitAsChild() {
view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
// |im_context_| must be created after creating |view_| widget.
im_context_.reset(new GtkIMContextWrapper(this));
+ // |key_bindings_handler_| must be created after creating |view_| widget.
+ key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
plugin_container_manager_.set_host_widget(view_.get());
gtk_widget_show(view_.get());
}
@@ -311,6 +314,8 @@ void RenderWidgetHostViewGtk::InitAsPopup(
view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
// |im_context_| must be created after creating |view_| widget.
im_context_.reset(new GtkIMContextWrapper(this));
+ // |key_bindings_handler_| must be created after creating |view_| widget.
+ key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
plugin_container_manager_.set_host_widget(view_.get());
gtk_container_add(GTK_CONTAINER(popup), view_.get());
@@ -462,6 +467,7 @@ void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) {
void RenderWidgetHostViewGtk::IMEUpdateStatus(int control,
const gfx::Rect& caret_rect) {
im_context_->UpdateStatus(control, caret_rect);
+ key_bindings_handler_->set_enabled(control != IME_DISABLE);
}
void RenderWidgetHostViewGtk::DidPaintRect(const gfx::Rect& rect) {
@@ -618,3 +624,12 @@ void RenderWidgetHostViewGtk::DestroyPluginContainer(
gfx::PluginWindowHandle id) {
plugin_container_manager_.DestroyPluginContainer(id);
}
+
+void RenderWidgetHostViewGtk::ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ EditCommands edit_commands;
+ if (key_bindings_handler_->Match(event, &edit_commands)) {
+ host_->ForwardEditCommandsForNextKeyEvent(edit_commands);
+ }
+ host_->ForwardKeyboardEvent(event);
+}
diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
index c0e3d6f..e0b3d64 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h
@@ -21,6 +21,10 @@
class RenderWidgetHost;
// A conveience wrapper class for GtkIMContext;
class GtkIMContextWrapper;
+// A convenience class for handling editor key bindings defined in gtk keyboard
+// theme.
+class GtkKeyBindingsHandler;
+class NativeWebKeyboardEvent;
typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkSelectionData GtkSelectionData;
@@ -71,6 +75,13 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView {
void Paint(const gfx::Rect&);
+ // Called by GtkIMContextWrapper to forward a keyboard event to renderer.
+ // Before calling RenderWidgetHost::ForwardKeyboardEvent(), this method
+ // calls GtkKeyBindingsHandler::Match() against the event and send matched
+ // edit commands to renderer by calling
+ // RenderWidgetHost::ForwardEditCommandsForNextKeyEvent().
+ void ForwardKeyboardEvent(const NativeWebKeyboardEvent& event);
+
private:
friend class RenderWidgetHostViewGtkWidget;
@@ -116,9 +127,13 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView {
// Used in OnGrabNotify() handler to track the focused state correctly.
bool was_focused_before_grab_;
- // A conveience wrapper object for GtkIMContext;
+ // A convenience wrapper object for GtkIMContext;
scoped_ptr<GtkIMContextWrapper> im_context_;
+ // A convenience object for handling editor key bindings defined in gtk
+ // keyboard theme.
+ scoped_ptr<GtkKeyBindingsHandler> key_bindings_handler_;
+
// Helper class that lets us allocate plugin containers and move them.
GtkPluginContainerManager plugin_container_manager_;
};
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 19c81fa..0bb0e8c 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1683,6 +1683,8 @@
'browser/renderer_host/file_system_accessor.h',
'browser/renderer_host/gtk_im_context_wrapper.cc',
'browser/renderer_host/gtk_im_context_wrapper.h',
+ 'browser/renderer_host/gtk_key_bindings_handler.cc',
+ 'browser/renderer_host/gtk_key_bindings_handler.h',
'browser/renderer_host/render_process_host.cc',
'browser/renderer_host/render_process_host.h',
'browser/renderer_host/render_sandbox_host_linux.h',
@@ -4214,6 +4216,9 @@
'../build/linux/system.gyp:nss',
'../sandbox/sandbox.gyp:*',
],
+ 'sources': [
+ 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc',
+ ],
'sources!': [
# This test is mostly about renaming downloads to safe file
# names. As such we don't need/want to port it to linux. We
diff --git a/chrome/common/edit_command.h b/chrome/common/edit_command.h
new file mode 100644
index 0000000..4fc373e
--- /dev/null
+++ b/chrome/common/edit_command.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_COMMON_EDIT_COMMAND_H_
+#define CHROME_COMMON_EDIT_COMMAND_H_
+
+#include <string>
+#include <vector>
+
+// Types related to sending edit commands to the renderer.
+struct EditCommand {
+ EditCommand() { }
+ EditCommand(const std::string& n, const std::string& v)
+ : name(n), value(v) {
+ }
+
+ std::string name;
+ std::string value;
+};
+
+typedef std::vector<EditCommand> EditCommands;
+
+#endif // CHROME_COMMON_EDIT_COMMAND_H_
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 1353149..22b9b45 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -17,6 +17,7 @@
#include "chrome/browser/renderer_host/resource_handler.h"
#include "chrome/common/common_param_traits.h"
#include "chrome/common/css_colors.h"
+#include "chrome/common/edit_command.h"
#include "chrome/common/extensions/update_manifest.h"
#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/filter_policy.h"
@@ -2221,6 +2222,26 @@ struct ParamTraits<Clipboard::Buffer> {
}
};
+// Traits for EditCommand structure.
+template <>
+struct ParamTraits<EditCommand> {
+ typedef EditCommand param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.name);
+ WriteParam(m, p.value);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return ReadParam(m, iter, &p->name) && ReadParam(m, iter, &p->value);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.name, l);
+ l->append(L":");
+ LogParam(p.value, l);
+ l->append(L")");
+ }
+};
+
} // namespace IPC
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index 4a2e5b0..7b6ee02 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -139,6 +139,20 @@ IPC_BEGIN_MESSAGES(View)
// Message payload is a blob that should be cast to WebInputEvent
IPC_MESSAGE_ROUTED0(ViewMsg_HandleInputEvent)
+ // This message notifies the renderer that the next key event is bound to one
+ // or more pre-defined edit commands. If the next key event is not handled
+ // by webkit, the specified edit commands shall be executed against current
+ // focused frame.
+ // Parameters
+ // * edit_commands (see chrome/common/edit_command_types.h)
+ // Contains one or more edit commands.
+ // See third_party/WebKit/WebCore/editing/EditorCommand.cpp for detailed
+ // definition of webkit edit commands.
+ //
+ // This message must be sent just before sending a key event.
+ IPC_MESSAGE_ROUTED1(ViewMsg_SetEditCommandsForNextKeyEvent,
+ EditCommands /* edit_commands */)
+
// Message payload is the name/value of a WebCore edit command to execute.
IPC_MESSAGE_ROUTED2(ViewMsg_ExecuteEditCommand,
std::string, /* name */
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index d19dcd0..f4204aa 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -427,6 +427,8 @@ void RenderView::OnMessageReceived(const IPC::Message& message) {
OnNotifyRendererViewType)
IPC_MESSAGE_HANDLER(ViewMsg_MediaPlayerActionAt, OnMediaPlayerActionAt)
IPC_MESSAGE_HANDLER(ViewMsg_SetActive, OnSetActive)
+ IPC_MESSAGE_HANDLER(ViewMsg_SetEditCommandsForNextKeyEvent,
+ OnSetEditCommandsForNextKeyEvent);
// Have the super handle all other messages.
IPC_MESSAGE_UNHANDLED(RenderWidget::OnMessageReceived(message))
@@ -3439,3 +3441,32 @@ void RenderView::Print(WebFrame* frame, bool script_initiated) {
}
print_helper_->Print(frame, script_initiated);
}
+
+void RenderView::OnSetEditCommandsForNextKeyEvent(
+ const EditCommands& edit_commands) {
+ edit_commands_ = edit_commands;
+}
+
+void RenderView::DidHandleKeyEvent() {
+ edit_commands_.clear();
+}
+
+bool RenderView::HandleCurrentKeyboardEvent() {
+ if (edit_commands_.empty())
+ return false;
+
+ WebFrame* frame = webview()->GetFocusedFrame();
+ if (!frame)
+ return false;
+
+ EditCommands::iterator it = edit_commands_.begin();
+ EditCommands::iterator end = edit_commands_.end();
+
+ for (; it != end; ++it) {
+ if (!frame->executeCommand(WebString::fromUTF8(it->name),
+ WebString::fromUTF8(it->value)))
+ break;
+ }
+
+ return true;
+}
diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h
index d580f49..3c99ec3 100644
--- a/chrome/renderer/render_view.h
+++ b/chrome/renderer/render_view.h
@@ -20,6 +20,7 @@
#include "base/values.h"
#include "base/weak_ptr.h"
#include "build/build_config.h"
+#include "chrome/common/edit_command.h"
#include "chrome/common/navigation_gesture.h"
#include "chrome/common/renderer_preferences.h"
#include "chrome/common/view_types.h"
@@ -262,6 +263,7 @@ class RenderView : public RenderWidget,
virtual void ScriptedPrint(WebKit::WebFrame* frame);
virtual void UserMetricsRecordAction(const std::wstring& action);
virtual void DnsPrefetch(const std::vector<std::string>& host_names);
+ virtual bool HandleCurrentKeyboardEvent();
// WebKit::WebWidgetClient
// Most methods are handled by RenderWidget.
@@ -433,6 +435,8 @@ class RenderView : public RenderWidget,
const gfx::Rect& resizer_rect);
// RenderWidget override
virtual void DidPaint();
+ // RenderWidget override.
+ virtual void DidHandleKeyEvent();
private:
// For unit tests.
@@ -542,6 +546,7 @@ class RenderView : public RenderWidget,
void OnSelectAll();
void OnCopyImageAt(int x, int y);
void OnExecuteEditCommand(const std::string& name, const std::string& value);
+ void OnSetEditCommandsForNextKeyEvent(const EditCommands& edit_commands);
void OnSetupDevToolsClient();
void OnCancelDownload(int32 download_id);
void OnFind(int request_id, const string16&, const WebKit::WebFindOptions&);
@@ -911,6 +916,10 @@ class RenderView : public RenderWidget,
// The settings this render view initialized WebKit with.
WebPreferences webkit_preferences_;
+ // Stores edit commands associated to the next key event.
+ // Shall be cleared as soon as the next key event is processed.
+ EditCommands edit_commands_;
+
DISALLOW_COPY_AND_ASSIGN(RenderView);
};
diff --git a/chrome/renderer/render_widget.cc b/chrome/renderer/render_widget.cc
index f668896..3aa58e3 100644
--- a/chrome/renderer/render_widget.cc
+++ b/chrome/renderer/render_widget.cc
@@ -331,6 +331,11 @@ void RenderWidget::OnHandleInputEvent(const IPC::Message& message) {
}
handling_input_event_ = false;
+
+ WebInputEvent::Type type = input_event->type;
+ if (type == WebInputEvent::RawKeyDown || type == WebInputEvent::KeyDown ||
+ type == WebInputEvent::KeyUp || type == WebInputEvent::Char)
+ DidHandleKeyEvent();
}
void RenderWidget::OnMouseCaptureLost() {
diff --git a/chrome/renderer/render_widget.h b/chrome/renderer/render_widget.h
index 38dfad8..100aee0 100644
--- a/chrome/renderer/render_widget.h
+++ b/chrome/renderer/render_widget.h
@@ -196,6 +196,10 @@ class RenderWidget : public IPC::Channel::Listener,
// GetWindowRect() we'll use this pending window rect as the size.
void SetPendingWindowRect(const WebKit::WebRect& r);
+ // Called by OnHandleInputEvent() to notify subclasses that a key event was
+ // just handled.
+ virtual void DidHandleKeyEvent() {}
+
// Routing ID that allows us to communicate to the parent browser process
// RenderWidgetHost. When MSG_ROUTING_NONE, no messages may be sent.
int32 routing_id_;
diff --git a/chrome/test/data/gtk_key_bindings_test_gtkrc b/chrome/test/data/gtk_key_bindings_test_gtkrc
new file mode 100644
index 0000000..812971c
--- /dev/null
+++ b/chrome/test/data/gtk_key_bindings_test_gtkrc
@@ -0,0 +1,83 @@
+# A keybinding set for testing GtkKeyBindingsHandler.
+# chrome/browser/render_host/gtk_key_bindings_handler_unittest.cc and this
+# file must be kept in sync.
+# This file covers all key bindings supported by GtkKeyBindingsHandler.
+
+binding "gtk-key-bindings-handler"
+{
+ # Test "move-cursor"
+ bind "<ctrl>1" {
+ "move-cursor" (logical-positions, -2, 0)
+ "move-cursor" (logical-positions, 2, 0)
+ "move-cursor" (visual-positions, -1, 1)
+ "move-cursor" (visual-positions, 1, 1)
+ "move-cursor" (words, -1, 0)
+ "move-cursor" (words, 1, 0)
+ "move-cursor" (display-lines, -1, 0)
+ "move-cursor" (display-lines, 1, 0)
+ "move-cursor" (display-line-ends, -1, 0)
+ "move-cursor" (display-line-ends, 1, 0)
+ "move-cursor" (paragraph-ends, -1, 0)
+ "move-cursor" (paragraph-ends, 1, 0)
+ "move-cursor" (pages, -1, 0)
+ "move-cursor" (pages, 1, 0)
+ "move-cursor" (buffer-ends, -1, 0)
+ "move-cursor" (buffer-ends, 1, 0)
+ }
+
+ # Test "delete-from-cursor"
+ bind "<ctrl>2" {
+ "delete-from-cursor" (chars, -2)
+ "delete-from-cursor" (chars, 2)
+ "delete-from-cursor" (word-ends, -1)
+ "delete-from-cursor" (word-ends, 1)
+ "delete-from-cursor" (words, -1)
+ "delete-from-cursor" (words, 1)
+ "delete-from-cursor" (display-lines, -1)
+ "delete-from-cursor" (display-lines, 1)
+ "delete-from-cursor" (display-line-ends, -1)
+ "delete-from-cursor" (display-line-ends, 1)
+ "delete-from-cursor" (paragraph-ends, -1)
+ "delete-from-cursor" (paragraph-ends, 1)
+ "delete-from-cursor" (paragraphs, -1)
+ "delete-from-cursor" (paragraphs, 1)
+ }
+
+ # Test backspace
+ bind "<ctrl>3" {
+ "backspace" ()
+ }
+
+ # Test copy-clipboard
+ bind "<ctrl>4" {
+ "copy-clipboard" ()
+ }
+
+ # Test cut-clipboard
+ bind "<ctrl>5" {
+ "cut-clipboard" ()
+ }
+
+ # Test insert-at-cursor
+ bind "<ctrl>6" {
+ "insert-at-cursor" ("hello")
+ }
+
+ # Test paste-clipboard
+ bind "<ctrl>7" {
+ "paste-clipboard" ()
+ }
+
+ # Test select-all
+ bind "<ctrl>8" {
+ "select-all" (0)
+ "select-all" (1)
+ }
+
+ # Test set-anchor
+ bind "<ctrl>9" {
+ "set-anchor" ()
+ }
+}
+
+class "GtkTextView" binding "gtk-key-bindings-handler"
diff --git a/webkit/glue/editor_client_impl.cc b/webkit/glue/editor_client_impl.cc
index 4106ff6..e1187f7 100644
--- a/webkit/glue/editor_client_impl.cc
+++ b/webkit/glue/editor_client_impl.cc
@@ -643,7 +643,11 @@ void EditorClientImpl::handleKeyboardEvent(WebCore::KeyboardEvent* evt) {
ShowFormAutofillForNode(evt->target()->toNode());
}
- if (handleEditingKeyboardEvent(evt))
+ // Calls WebViewDelegate's HandleCurrentKeyboardEvent() first to give it a
+ // chance to handle the keyboard event. Bypass handleEditingKeyboardEvent(),
+ // if WebViewDelegate handles the event.
+ WebViewDelegate* d = web_view_->delegate();
+ if ((d && d->HandleCurrentKeyboardEvent()) || handleEditingKeyboardEvent(evt))
evt->setDefaultHandled();
}
diff --git a/webkit/glue/webview_delegate.h b/webkit/glue/webview_delegate.h
index c5c6b6a..2e4c333 100644
--- a/webkit/glue/webview_delegate.h
+++ b/webkit/glue/webview_delegate.h
@@ -415,6 +415,19 @@ class WebViewDelegate : virtual public WebKit::WebWidgetClient {
// Called when an item was added to the history
virtual void DidAddHistoryItem() { }
+ // The "CurrentKeyboardEvent" refers to the keyboard event passed to
+ // WebView's handleInputEvent method.
+ //
+ // This method is called in response to WebView's handleInputEvent() when
+ // the default action for the current keyboard event is not suppressed by the
+ // page, to give WebViewDelegate a chance to handle the keyboard event
+ // specially.
+ //
+ // Returns true if the keyboard event was handled by WebViewDelegate.
+ virtual bool HandleCurrentKeyboardEvent() {
+ return false;
+ }
+
protected:
~WebViewDelegate() { }
};
diff --git a/webkit/glue/webview_impl.cc b/webkit/glue/webview_impl.cc
index f82c6c7..2ff98d2 100644
--- a/webkit/glue/webview_impl.cc
+++ b/webkit/glue/webview_impl.cc
@@ -635,15 +635,10 @@ bool WebViewImpl::KeyEvent(const WebKeyboardEvent& event) {
PlatformKeyboardEventBuilder evt(event);
- if (WebInputEvent::RawKeyDown == event.type) {
- if (handler->keyEvent(evt) && !evt.isSystemKey()) {
+ if (handler->keyEvent(evt)) {
+ if (WebInputEvent::RawKeyDown == event.type && !evt.isSystemKey())
suppress_next_keypress_event_ = true;
- return true;
- }
- } else {
- if (handler->keyEvent(evt)) {
- return true;
- }
+ return true;
}
return KeyEventDefault(event);