summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorerg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-22 22:13:29 +0000
committererg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-08-22 22:13:29 +0000
commitbacf470d2cd46c0d3b145675e471ab9c6976cee8 (patch)
tree08b07add5d1736cb5b435dbcc8fdca968b6b97b8 /content
parent0adf649c85353e90478a994dac74e9f1575b7e6e (diff)
downloadchromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.zip
chromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.tar.gz
chromium_src-bacf470d2cd46c0d3b145675e471ab9c6976cee8.tar.bz2
content: Move render_widget_host_view_gtk to content/
This also moves some other files: - OwnedWidgetGtk now goes in ui/base/gtk/ - TruncateString moved from l10n_util:: to ui:: - GtkIMContextWrapper has part of its code split into chrome/ (IDC using code goes in RenderViewContextMenu) and the rest go in content/ (gtk using code goes with GtkIMContextWrapper). - gtk_key_bindings_handler[_unittest] now goes in content, as it's a utility class to RenderWidgetHostGtk. BUG=93804 TEST=existing unit tests Review URL: http://codereview.chromium.org/7669040 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97750 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/renderer_host/gtk_im_context_wrapper.cc619
-rw-r--r--content/browser/renderer_host/gtk_im_context_wrapper.h197
-rw-r--r--content/browser/renderer_host/gtk_key_bindings_handler.cc325
-rw-r--r--content/browser/renderer_host/gtk_key_bindings_handler.h128
-rw-r--r--content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc222
-rw-r--r--content/browser/renderer_host/render_widget_host_view_gtk.cc1210
-rw-r--r--content/browser/renderer_host/render_widget_host_view_gtk.h273
-rw-r--r--content/common/content_paths.cc17
-rw-r--r--content/common/content_paths.h3
-rw-r--r--content/common/content_switches.cc5
-rw-r--r--content/common/content_switches.h4
-rw-r--r--content/content_browser.gypi20
-rw-r--r--content/content_tests.gypi6
-rw-r--r--content/test/content_test_suite.cc5
-rw-r--r--content/test/data/gtk_key_bindings_test_gtkrc83
15 files changed, 3116 insertions, 1 deletions
diff --git a/content/browser/renderer_host/gtk_im_context_wrapper.cc b/content/browser/renderer_host/gtk_im_context_wrapper.cc
new file mode 100644
index 0000000..c5c4e03
--- /dev/null
+++ b/content/browser/renderer_host/gtk_im_context_wrapper.cc
@@ -0,0 +1,619 @@
+// Copyright (c) 2011 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 "content/browser/renderer_host/gtk_im_context_wrapper.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/renderer_host/render_widget_host.h"
+#include "content/browser/renderer_host/render_widget_host_view_gtk.h"
+#include "content/common/native_web_keyboard_event.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderline.h"
+#include "ui/base/gtk/gtk_im_context_util.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/rect.h"
+
+namespace {
+// Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp
+//
+// Match key code of composition keydown event on windows.
+// IE sends VK_PROCESSKEY which has value 229;
+//
+// Please refer to following documents for detals:
+// - Virtual-Key Codes
+// http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx
+// - How the IME System Works
+// http://msdn.microsoft.com/en-us/library/cc194848.aspx
+// - ImmGetVirtualKey Function
+// http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx
+const int kCompositionEventKeyCode = 229;
+} // namespace
+
+// ui::CompositionUnderline should be identical to
+// WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely.
+// TODO(suzhe): remove it after migrating all code in chrome to use
+// ui::CompositionUnderline.
+COMPILE_ASSERT(sizeof(ui::CompositionUnderline) ==
+ sizeof(WebKit::WebCompositionUnderline),
+ ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff);
+
+GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view)
+ : host_view_(host_view),
+ context_(gtk_im_multicontext_new()),
+ context_simple_(gtk_im_context_simple_new()),
+ is_focused_(false),
+ is_composing_text_(false),
+ is_enabled_(false),
+ is_in_key_event_handler_(false),
+ is_composition_changed_(false),
+ suppress_next_commit_(false),
+ last_key_code_(0),
+ last_key_was_up_(false),
+ last_key_filtered_no_result_(false) {
+ DCHECK(context_);
+ DCHECK(context_simple_);
+
+ // context_ and context_simple_ share the same callback handlers.
+ // All data come from them are treated equally.
+ // context_ is for full input method support.
+ // context_simple_ is for supporting dead/compose keys when input method is
+ // disabled by webkit, eg. in password input box.
+ g_signal_connect(context_, "preedit_start",
+ G_CALLBACK(HandlePreeditStartThunk), this);
+ g_signal_connect(context_, "preedit_end",
+ G_CALLBACK(HandlePreeditEndThunk), this);
+ g_signal_connect(context_, "preedit_changed",
+ G_CALLBACK(HandlePreeditChangedThunk), this);
+ g_signal_connect(context_, "commit",
+ G_CALLBACK(HandleCommitThunk), this);
+
+ g_signal_connect(context_simple_, "preedit_start",
+ G_CALLBACK(HandlePreeditStartThunk), this);
+ g_signal_connect(context_simple_, "preedit_end",
+ G_CALLBACK(HandlePreeditEndThunk), this);
+ g_signal_connect(context_simple_, "preedit_changed",
+ G_CALLBACK(HandlePreeditChangedThunk), this);
+ g_signal_connect(context_simple_, "commit",
+ G_CALLBACK(HandleCommitThunk), this);
+
+ GtkWidget* widget = host_view->native_view();
+ DCHECK(widget);
+
+ g_signal_connect(widget, "realize",
+ G_CALLBACK(HandleHostViewRealizeThunk), this);
+ g_signal_connect(widget, "unrealize",
+ G_CALLBACK(HandleHostViewUnrealizeThunk), this);
+
+ // Set client window if the widget is already realized.
+ HandleHostViewRealize(widget);
+}
+
+GtkIMContextWrapper::~GtkIMContextWrapper() {
+ if (context_)
+ g_object_unref(context_);
+ if (context_simple_)
+ g_object_unref(context_simple_);
+}
+
+void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) {
+ suppress_next_commit_ = false;
+
+ // Indicates preedit-changed and commit signal handlers that we are
+ // processing a key event.
+ is_in_key_event_handler_ = true;
+ // Reset this flag so that we can know if preedit is changed after
+ // processing this key event.
+ is_composition_changed_ = false;
+ // Clear it so that we can know if something needs committing after
+ // processing this key event.
+ commit_text_.clear();
+
+ // According to Document Object Model (DOM) Level 3 Events Specification
+ // Appendix A: Keyboard events and key identifiers
+ // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html:
+ // The event sequence would be:
+ // 1. keydown
+ // 2. textInput
+ // 3. keyup
+ //
+ // So keydown must be sent to webkit before sending input method result,
+ // while keyup must be sent afterwards.
+ // However on Windows, if a keydown event has been processed by IME, its
+ // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent
+ // to application.
+ // To emulate the windows behavior as much as possible, we need to send the
+ // key event to the GtkIMContext object first, and decide whether or not to
+ // send the original key event to webkit according to the result from IME.
+ //
+ // If IME is enabled by WebKit, this event will be dispatched to context_
+ // to get full IME support. Otherwise it'll be dispatched to
+ // context_simple_, so that dead/compose keys can still work.
+ //
+ // It sends a "commit" signal when it has a character to be inserted
+ // even when we use a US keyboard so that we can send a Char event
+ // (or an IME event) to the renderer in our "commit"-signal handler.
+ // We should send a KeyDown (or a KeyUp) event before dispatching this
+ // event to the GtkIMContext object (and send a Char event) so that WebKit
+ // can dispatch the JavaScript events in the following order: onkeydown(),
+ // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.)
+ gboolean filtered = false;
+ if (is_enabled_) {
+ filtered = gtk_im_context_filter_keypress(context_, event);
+ } else {
+ filtered = gtk_im_context_filter_keypress(context_simple_, event);
+ }
+
+ // Reset this flag here, as it's only used in input method callbacks.
+ is_in_key_event_handler_ = false;
+
+ NativeWebKeyboardEvent wke(event);
+
+ // If the key event was handled by the input method, then we need to prevent
+ // RenderView::UnhandledKeyboardEvent() from processing it.
+ // Otherwise unexpected result may occur. For example if it's a
+ // Backspace key event, the browser may go back to previous page.
+ // We just send all keyup events to the browser to avoid breaking the
+ // browser's MENU key function, which is actually the only keyup event
+ // handled in the browser.
+ if (filtered && event->type == GDK_KEY_PRESS)
+ wke.skip_in_browser = true;
+
+ const int key_code = wke.windowsKeyCode;
+ const bool has_result = HasInputMethodResult();
+
+ // Send filtered keydown event before sending IME result.
+ // In order to workaround http://crosbug.com/6582, we only send a filtered
+ // keydown event if it generated any input method result.
+ if (event->type == GDK_KEY_PRESS && filtered && has_result)
+ ProcessFilteredKeyPressEvent(&wke);
+
+ // Send IME results. In most cases, it's only available if the key event
+ // is filtered by IME. But in rare cases, an unfiltered key event may also
+ // generate IME results.
+ // Any IME results generated by a unfiltered key down event must be sent
+ // before the key down event, to avoid some tricky issues. For example,
+ // when using latin-post input method, pressing 'a' then Backspace, may
+ // generate following events in sequence:
+ // 1. keydown 'a' (filtered)
+ // 2. preedit changed to "a"
+ // 3. keyup 'a' (unfiltered)
+ // 4. keydown Backspace (unfiltered)
+ // 5. commit "a"
+ // 6. preedit end
+ // 7. keyup Backspace (unfiltered)
+ //
+ // In this case, the input box will be in a strange state if keydown
+ // Backspace is sent to webkit before commit "a" and preedit end.
+ if (has_result)
+ ProcessInputMethodResult(event, filtered);
+
+ // Send unfiltered keydown and keyup events after sending IME result.
+ if (event->type == GDK_KEY_PRESS && !filtered) {
+ ProcessUnfilteredKeyPressEvent(&wke);
+ } else if (event->type == GDK_KEY_RELEASE) {
+ // In order to workaround http://crosbug.com/6582, we need to suppress
+ // the keyup event if corresponding keydown event was suppressed, or
+ // the last key event was a keyup event with the same keycode.
+ const bool suppress = (last_key_code_ == key_code) &&
+ (last_key_was_up_ || last_key_filtered_no_result_);
+
+ if (!suppress)
+ host_view_->ForwardKeyboardEvent(wke);
+ }
+
+ last_key_code_ = key_code;
+ last_key_was_up_ = (event->type == GDK_KEY_RELEASE);
+ last_key_filtered_no_result_ = (filtered && !has_result);
+}
+
+void GtkIMContextWrapper::UpdateInputMethodState(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect) {
+ suppress_next_commit_ = false;
+
+ // The renderer has updated its IME status.
+ // Control the GtkIMContext object according to this status.
+ if (!context_ || !is_focused_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE &&
+ type != ui::TEXT_INPUT_TYPE_PASSWORD);
+ if (is_enabled_ != is_enabled) {
+ is_enabled_ = is_enabled;
+ if (is_enabled)
+ gtk_im_context_focus_in(context_);
+ else
+ gtk_im_context_focus_out(context_);
+ }
+
+ if (is_enabled) {
+ // If the focused element supports inline rendering of composition text,
+ // we receive and send related events to it. Otherwise, the events related
+ // to the updates of composition text are directed to the candidate window.
+ gtk_im_context_set_use_preedit(context_, can_compose_inline);
+ // Updates the position of the IME candidate window.
+ // The position sent from the renderer is a relative one, so we need to
+ // attach the GtkIMContext object to this window before changing the
+ // position.
+ GdkRectangle cursor_rect(caret_rect.ToGdkRectangle());
+ gtk_im_context_set_cursor_location(context_, &cursor_rect);
+ }
+}
+
+void GtkIMContextWrapper::OnFocusIn() {
+ if (is_focused_)
+ return;
+
+ // Tracks the focused state so that we can give focus to the
+ // GtkIMContext object correctly later when IME is enabled by WebKit.
+ is_focused_ = true;
+
+ last_key_code_ = 0;
+ last_key_was_up_ = false;
+ last_key_filtered_no_result_ = false;
+
+ // Notify the GtkIMContext object of this focus-in event only if IME is
+ // enabled by WebKit.
+ if (is_enabled_)
+ gtk_im_context_focus_in(context_);
+
+ // context_simple_ is always enabled.
+ // Actually it doesn't care focus state at all.
+ gtk_im_context_focus_in(context_simple_);
+
+ // Enables RenderWidget's IME related events, so that we can be notified
+ // when WebKit wants to enable or disable IME.
+ if (host_view_->GetRenderWidgetHost())
+ host_view_->GetRenderWidgetHost()->SetInputMethodActive(true);
+}
+
+void GtkIMContextWrapper::OnFocusOut() {
+ if (!is_focused_)
+ return;
+
+ // Tracks the focused state so that we won't give focus to the
+ // GtkIMContext object unexpectly.
+ is_focused_ = false;
+
+ // Notify the GtkIMContext object of this focus-out event only if IME is
+ // enabled by WebKit.
+ if (is_enabled_) {
+ // To reset the GtkIMContext object and prevent data loss.
+ ConfirmComposition();
+ gtk_im_context_focus_out(context_);
+ }
+
+ // To make sure it'll be in correct state when focused in again.
+ gtk_im_context_reset(context_simple_);
+ gtk_im_context_focus_out(context_simple_);
+
+ is_composing_text_ = false;
+
+ // Disable RenderWidget's IME related events to save bandwidth.
+ if (host_view_->GetRenderWidgetHost())
+ host_view_->GetRenderWidgetHost()->SetInputMethodActive(false);
+}
+
+#if !defined(TOOLKIT_VIEWS)
+// Not defined for views because the views context menu doesn't
+// implement input methods yet.
+GtkWidget* GtkIMContextWrapper::BuildInputMethodsGtkMenu() {
+ GtkWidget* submenu = gtk_menu_new();
+ gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_),
+ GTK_MENU_SHELL(submenu));
+ return submenu;
+}
+#endif
+
+void GtkIMContextWrapper::CancelComposition() {
+ if (!is_enabled_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ // To prevent any text from being committed when resetting the |context_|;
+ is_in_key_event_handler_ = true;
+ suppress_next_commit_ = true;
+
+ gtk_im_context_reset(context_);
+ gtk_im_context_reset(context_simple_);
+
+ if (is_focused_) {
+ // Some input methods may not honour the reset call. Focusing out/in the
+ // |context_| to make sure it gets reset correctly.
+ gtk_im_context_focus_out(context_);
+ gtk_im_context_focus_in(context_);
+ }
+
+ is_composing_text_ = false;
+ composition_.Clear();
+ commit_text_.clear();
+
+ is_in_key_event_handler_ = false;
+}
+
+bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const {
+ // If there is no composition text and has only one character to be
+ // committed, then the character will be send to webkit as a Char event
+ // instead of a confirmed composition text.
+ // It should be fine to handle BMP character only, as non-BMP characters
+ // can always be committed as confirmed composition text.
+ return !is_composing_text_ && commit_text_.length() == 1;
+}
+
+bool GtkIMContextWrapper::HasInputMethodResult() const {
+ return commit_text_.length() || is_composition_changed_;
+}
+
+void GtkIMContextWrapper::ProcessFilteredKeyPressEvent(
+ NativeWebKeyboardEvent* wke) {
+ // If IME has filtered this event, then replace virtual key code with
+ // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details.
+ // It's only required for keydown events.
+ // To emulate windows behavior, when input method is enabled, if the commit
+ // text can be emulated by a Char event, then don't do this replacement.
+ if (!NeedCommitByForwardingCharEvent()) {
+ wke->windowsKeyCode = kCompositionEventKeyCode;
+ // keyidentifier must be updated accordingly, otherwise this key event may
+ // still be processed by webkit.
+ wke->setKeyIdentifierFromWindowsKeyCode();
+ }
+ host_view_->ForwardKeyboardEvent(*wke);
+}
+
+void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent(
+ NativeWebKeyboardEvent* wke) {
+ // Send keydown event as it, because it's not filtered by IME.
+ host_view_->ForwardKeyboardEvent(*wke);
+
+ // IME is disabled by WebKit or the GtkIMContext object cannot handle
+ // this key event.
+ // This case is caused by two reasons:
+ // 1. The given key event is a control-key event, (e.g. return, page up,
+ // page down, tab, arrows, etc.) or;
+ // 2. The given key event is not a control-key event but printable
+ // characters aren't assigned to the event, (e.g. alt+d, etc.)
+ // Create a Char event manually from this key event and send it to the
+ // renderer when this Char event contains a printable character which
+ // should be processed by WebKit.
+ // isSystemKey will be set to true if this key event has Alt modifier,
+ // see WebInputEventFactory::keyboardEvent() for details.
+ if (wke->text[0]) {
+ wke->type = WebKit::WebInputEvent::Char;
+ wke->skip_in_browser = true;
+ host_view_->ForwardKeyboardEvent(*wke);
+ }
+}
+
+void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event,
+ bool filtered) {
+ RenderWidgetHost* host = host_view_->GetRenderWidgetHost();
+ if (!host)
+ return;
+
+ bool committed = false;
+ // We do commit before preedit change, so that we can optimize some
+ // unnecessary preedit changes.
+ if (commit_text_.length()) {
+ if (filtered && NeedCommitByForwardingCharEvent()) {
+ // Send a Char event when we input a composed character without IMEs
+ // so that this event is to be dispatched to onkeypress() handlers,
+ // autofill, etc.
+ // Only commit text generated by a filtered key down event can be sent
+ // as a Char event, because a unfiltered key down event will probably
+ // generate another Char event.
+ // TODO(james.su@gmail.com): Is it necessary to support non BMP chars
+ // here?
+ NativeWebKeyboardEvent char_event(commit_text_[0],
+ event->state,
+ base::Time::Now().ToDoubleT());
+ char_event.skip_in_browser = true;
+ host_view_->ForwardKeyboardEvent(char_event);
+ } else {
+ committed = true;
+ // Send an IME event.
+ // Unlike a Char event, an IME event is NOT dispatched to onkeypress()
+ // handlers or autofill.
+ host->ImeConfirmComposition(commit_text_);
+ // Set this flag to false, as this composition session has been
+ // finished.
+ is_composing_text_ = false;
+ }
+ }
+
+ // Send preedit text only if it's changed.
+ // If a text has been committed, then we don't need to send the empty
+ // preedit text again.
+ if (is_composition_changed_) {
+ if (composition_.text.length()) {
+ // Another composition session has been started.
+ is_composing_text_ = true;
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition_.underlines);
+ host->ImeSetComposition(composition_.text, underlines,
+ composition_.selection.start(),
+ composition_.selection.end());
+ } else if (!committed) {
+ host->ImeCancelComposition();
+ }
+ }
+}
+
+void GtkIMContextWrapper::ConfirmComposition() {
+ if (!is_enabled_)
+ return;
+
+ DCHECK(!is_in_key_event_handler_);
+
+ if (is_composing_text_) {
+ if (host_view_->GetRenderWidgetHost())
+ host_view_->GetRenderWidgetHost()->ImeConfirmComposition();
+
+ // Reset the input method.
+ CancelComposition();
+ }
+}
+
+void GtkIMContextWrapper::HandleCommit(const string16& text) {
+ if (suppress_next_commit_) {
+ suppress_next_commit_ = false;
+ return;
+ }
+
+ // Append the text to the buffer, because commit signal might be fired
+ // multiple times when processing a key event.
+ commit_text_.append(text);
+ // Nothing needs to do, if it's currently in ProcessKeyEvent()
+ // handler, which will send commit text to webkit later. Otherwise,
+ // we need send it here.
+ // It's possible that commit signal is fired without a key event, for
+ // example when user input via a voice or handwriting recognition software.
+ // In this case, the text must be committed directly.
+ if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) {
+ // Workaround http://crbug.com/45478 by sending fake key down/up events.
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
+ host_view_->GetRenderWidgetHost()->ImeConfirmComposition(text);
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
+ }
+}
+
+void GtkIMContextWrapper::HandlePreeditStart() {
+ // Ignore preedit related signals triggered by CancelComposition() method.
+ if (suppress_next_commit_)
+ return;
+ is_composing_text_ = true;
+}
+
+void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text,
+ PangoAttrList* attrs,
+ int cursor_position) {
+ // Ignore preedit related signals triggered by CancelComposition() method.
+ if (suppress_next_commit_)
+ return;
+
+ // Don't set is_composition_changed_ to false if there is no change, because
+ // this handler might be called multiple times with the same data.
+ is_composition_changed_ = true;
+ composition_.Clear();
+
+ ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position,
+ &composition_);
+
+ // TODO(suzhe): due to a bug of webkit, we currently can't use selection range
+ // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805
+ composition_.selection = ui::Range(cursor_position);
+
+ // In case we are using a buggy input method which doesn't fire
+ // "preedit_start" signal.
+ if (composition_.text.length())
+ is_composing_text_ = true;
+
+ // Nothing needs to do, if it's currently in ProcessKeyEvent()
+ // handler, which will send preedit text to webkit later.
+ // Otherwise, we need send it here if it's been changed.
+ if (!is_in_key_event_handler_ && is_composing_text_ &&
+ host_view_->GetRenderWidgetHost()) {
+ // Workaround http://crbug.com/45478 by sending fake key down/up events.
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown);
+ // TODO(suzhe): convert both renderer_host and renderer to use
+ // ui::CompositionText.
+ const std::vector<WebKit::WebCompositionUnderline>& underlines =
+ reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>(
+ composition_.underlines);
+ host_view_->GetRenderWidgetHost()->ImeSetComposition(
+ composition_.text, underlines, composition_.selection.start(),
+ composition_.selection.end());
+ SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp);
+ }
+}
+
+void GtkIMContextWrapper::HandlePreeditEnd() {
+ if (composition_.text.length()) {
+ // The composition session has been finished.
+ composition_.Clear();
+ is_composition_changed_ = true;
+
+ // If there is still a preedit text when firing "preedit-end" signal,
+ // we need inform webkit to clear it.
+ // It's only necessary when it's not in ProcessKeyEvent ().
+ if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost())
+ host_view_->GetRenderWidgetHost()->ImeCancelComposition();
+ }
+
+ // Don't set is_composing_text_ to false here, because "preedit_end"
+ // signal may be fired before "commit" signal.
+}
+
+void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) {
+ // We should only set im context's client window once, because when setting
+ // client window.im context may destroy and recreate its internal states and
+ // objects.
+ if (widget->window) {
+ gtk_im_context_set_client_window(context_, widget->window);
+ gtk_im_context_set_client_window(context_simple_, widget->window);
+ }
+}
+
+void GtkIMContextWrapper::HandleHostViewUnrealize() {
+ gtk_im_context_set_client_window(context_, NULL);
+ gtk_im_context_set_client_window(context_simple_, NULL);
+}
+
+void GtkIMContextWrapper::SendFakeCompositionKeyEvent(
+ WebKit::WebInputEvent::Type type) {
+ NativeWebKeyboardEvent fake_event;
+ fake_event.windowsKeyCode = kCompositionEventKeyCode;
+ fake_event.skip_in_browser = true;
+ fake_event.type = type;
+ host_view_->ForwardKeyboardEvent(fake_event);
+}
+
+void GtkIMContextWrapper::HandleCommitThunk(
+ GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) {
+ self->HandleCommit(UTF8ToUTF16(text));
+}
+
+void GtkIMContextWrapper::HandlePreeditStartThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ self->HandlePreeditStart();
+}
+
+void GtkIMContextWrapper::HandlePreeditChangedThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ gchar* text = NULL;
+ PangoAttrList* attrs = NULL;
+ gint cursor_position = 0;
+ gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position);
+ self->HandlePreeditChanged(text, attrs, cursor_position);
+ g_free(text);
+ pango_attr_list_unref(attrs);
+}
+
+void GtkIMContextWrapper::HandlePreeditEndThunk(
+ GtkIMContext* context, GtkIMContextWrapper* self) {
+ self->HandlePreeditEnd();
+}
+
+void GtkIMContextWrapper::HandleHostViewRealizeThunk(
+ GtkWidget* widget, GtkIMContextWrapper* self) {
+ self->HandleHostViewRealize(widget);
+}
+
+void GtkIMContextWrapper::HandleHostViewUnrealizeThunk(
+ GtkWidget* widget, GtkIMContextWrapper* self) {
+ self->HandleHostViewUnrealize();
+}
diff --git a/content/browser/renderer_host/gtk_im_context_wrapper.h b/content/browser/renderer_host/gtk_im_context_wrapper.h
new file mode 100644
index 0000000..a783296
--- /dev/null
+++ b/content/browser/renderer_host/gtk_im_context_wrapper.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2011 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 CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
+#pragma once
+
+#include <gdk/gdk.h>
+#include <pango/pango-attributes.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/string16.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
+#include "ui/base/ime/composition_text.h"
+#include "ui/base/ime/text_input_type.h"
+
+namespace gfx {
+class Rect;
+}
+
+class RenderWidgetHostViewGtk;
+struct NativeWebKeyboardEvent;
+typedef struct _GtkIMContext GtkIMContext;
+typedef struct _GtkWidget GtkWidget;
+
+// This class is a convenience wrapper for GtkIMContext.
+// It creates and manages two GtkIMContext instances, one is GtkIMMulticontext,
+// for plain text input box, another is GtkIMContextSimple, for password input
+// box.
+//
+// This class is in charge of dispatching key events to these two GtkIMContext
+// instances and handling signals emitted by them. Key events then will be
+// forwarded to renderer along with input method results via corresponding host
+// view.
+//
+// This class is used solely by RenderWidgetHostViewGtk.
+class GtkIMContextWrapper {
+ public:
+ explicit GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view);
+ ~GtkIMContextWrapper();
+
+ // Processes a gdk key event received by |host_view|.
+ void ProcessKeyEvent(GdkEventKey* event);
+
+ void UpdateInputMethodState(ui::TextInputType type,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect);
+ void OnFocusIn();
+ void OnFocusOut();
+ bool is_focused() const { return is_focused_; }
+
+#if !defined(TOOLKIT_VIEWS)
+ // Not defined for views because the views context menu doesn't
+ // implement input methods yet.
+ GtkWidget* BuildInputMethodsGtkMenu();
+#endif
+
+ void CancelComposition();
+
+ void ConfirmComposition();
+
+ private:
+ // Check if a text needs commit by forwarding a char event instead of
+ // by confirming as a composition text.
+ bool NeedCommitByForwardingCharEvent() const;
+
+ // Check if the input method returned any result, eg. preedit and commit text.
+ bool HasInputMethodResult() const;
+
+ void ProcessFilteredKeyPressEvent(NativeWebKeyboardEvent* wke);
+ void ProcessUnfilteredKeyPressEvent(NativeWebKeyboardEvent* wke);
+
+ // Processes result returned from input method after filtering a key event.
+ // |filtered| indicates if the key event was filtered by the input method.
+ void ProcessInputMethodResult(const GdkEventKey* event, bool filtered);
+
+ // Real code of "commit" signal handler.
+ void HandleCommit(const string16& text);
+
+ // Real code of "preedit-start" signal handler.
+ void HandlePreeditStart();
+
+ // Real code of "preedit-changed" signal handler.
+ void HandlePreeditChanged(const gchar* text,
+ PangoAttrList* attrs,
+ int cursor_position);
+
+ // Real code of "preedit-end" signal handler.
+ void HandlePreeditEnd();
+
+ // Real code of "realize" signal handler, used for setting im context's client
+ // window.
+ void HandleHostViewRealize(GtkWidget* widget);
+
+ // Real code of "unrealize" signal handler, used for unsetting im context's
+ // client window.
+ void HandleHostViewUnrealize();
+
+ // Sends a fake composition key event with specified event type. A composition
+ // key event is a key event with special key code 229.
+ void SendFakeCompositionKeyEvent(WebKit::WebInputEvent::Type type);
+
+ // Signal handlers of GtkIMContext object.
+ static void HandleCommitThunk(GtkIMContext* context, gchar* text,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditStartThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditChangedThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+ static void HandlePreeditEndThunk(GtkIMContext* context,
+ GtkIMContextWrapper* self);
+
+ // Signal handlers connecting to |host_view_|'s native view widget.
+ static void HandleHostViewRealizeThunk(GtkWidget* widget,
+ GtkIMContextWrapper* self);
+ static void HandleHostViewUnrealizeThunk(GtkWidget* widget,
+ GtkIMContextWrapper* self);
+
+ // The parent object.
+ RenderWidgetHostViewGtk* host_view_;
+
+ // The GtkIMContext object.
+ // In terms of the DOM event specification Appendix A
+ // <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>,
+ // GTK uses a GtkIMContext object for the following two purposes:
+ // 1. Composing Latin characters (A.1.2), and;
+ // 2. Composing CJK characters with an IME (A.1.3).
+ // Many JavaScript pages assume composed Latin characters are dispatched to
+ // their onkeypress() handlers but not dispatched CJK characters composed
+ // with an IME. To emulate this behavior, we should monitor the status of
+ // this GtkIMContext object and prevent sending Char events when a
+ // GtkIMContext object sends a "commit" signal with the CJK characters
+ // composed by an IME.
+ GtkIMContext* context_;
+
+ // A GtkIMContextSimple object, for supporting dead/compose keys when input
+ // method is disabled, eg. in password input box.
+ GtkIMContext* context_simple_;
+
+ // Whether or not this widget is focused.
+ bool is_focused_;
+
+ // Whether or not the above GtkIMContext is composing a text with an IME.
+ // This flag is used in "commit" signal handler of the GtkIMContext object,
+ // which determines how to submit the result text to WebKit according to this
+ // flag.
+ // If this flag is true or there are more than one characters in the result,
+ // then the result text will be committed to WebKit as a confirmed
+ // composition. Otherwise, it'll be forwarded as a key event.
+ //
+ // The GtkIMContext object sends a "preedit_start" before it starts composing
+ // a text and a "preedit_end" signal after it finishes composing it.
+ // "preedit_start" signal is monitored to turn it on.
+ // We don't monitor "preedit_end" signal to turn it off, because an input
+ // method may fire "preedit_end" signal before "commit" signal.
+ // A buggy input method may not fire "preedit_start" and/or "preedit_end"
+ // at all, so this flag will also be set to true when "preedit_changed" signal
+ // is fired with non-empty preedit text.
+ bool is_composing_text_;
+
+ // Whether or not the IME is enabled.
+ bool is_enabled_;
+
+ // Whether or not it's currently running inside key event handler.
+ // If it's true, then preedit-changed and commit handler will backup the
+ // preedit or commit text instead of sending them down to webkit.
+ // key event handler will send them later.
+ bool is_in_key_event_handler_;
+
+ // The most recent composition text information retrieved from context_;
+ ui::CompositionText composition_;
+
+ // Whether or not the composition has been changed since last key event.
+ bool is_composition_changed_;
+
+ // Stores a copy of the most recent commit text received by commit signal
+ // handler.
+ string16 commit_text_;
+
+ // If it's true then the next "commit" signal will be suppressed.
+ // It's only used to workaround http://crbug.com/50485.
+ // TODO(suzhe): Remove it after input methods get fixed.
+ bool suppress_next_commit_;
+
+ // Information of the last key event, for working around
+ // http://crosbug.com/6582
+ int last_key_code_;
+ bool last_key_was_up_;
+ bool last_key_filtered_no_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(GtkIMContextWrapper);
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_IM_CONTEXT_WRAPPER_H_
diff --git a/content/browser/renderer_host/gtk_key_bindings_handler.cc b/content/browser/renderer_host/gtk_key_bindings_handler.cc
new file mode 100644
index 0000000..94e3234
--- /dev/null
+++ b/content/browser/renderer_host/gtk_key_bindings_handler.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2011 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 "content/browser/renderer_host/gtk_key_bindings_handler.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "content/common/native_web_keyboard_event.h"
+
+GtkKeyBindingsHandler::GtkKeyBindingsHandler(GtkWidget* parent_widget)
+ : handler_(CreateNewHandler()) {
+ 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 (wke.type == WebKit::WebInputEvent::Char || !wke.os_event)
+ 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_set_can_focus(GTK_WIDGET(handler), TRUE);
+
+#if !GTK_CHECK_VERSION(2, 14, 0)
+ // "move-focus", "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-focus", G_CALLBACK(MoveFocus), NULL);
+ 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);
+ GtkWidgetClass* widget_class = GTK_WIDGET_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;
+ widget_class->show_help = ShowHelp;
+
+#if GTK_CHECK_VERSION(2, 14, 0)
+ // "move-focus", "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-focus",
+ G_TYPE_FROM_CLASS(klass),
+ G_CALLBACK(MoveFocus));
+
+ 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.
+}
+
+gboolean GtkKeyBindingsHandler::ShowHelp(GtkWidget* widget,
+ GtkWidgetHelpType arg1) {
+ // Just for disabling the default handler.
+ return FALSE;
+}
+
+void GtkKeyBindingsHandler::MoveFocus(GtkWidget* widget,
+ GtkDirectionType arg1) {
+ // Just for disabling the default handler.
+#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(widget, "move-focus");
+#endif
+}
diff --git a/content/browser/renderer_host/gtk_key_bindings_handler.h b/content/browser/renderer_host/gtk_key_bindings_handler.h
new file mode 100644
index 0000000..071a3b1
--- /dev/null
+++ b/content/browser/renderer_host/gtk_key_bindings_handler.h
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 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 CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include <string>
+
+#include "content/common/edit_command.h"
+#include "ui/base/gtk/owned_widget_gtk.h"
+
+struct 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/Source/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();
+
+ // 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);
+
+ // Handler of "show-help" signal.
+ static gboolean ShowHelp(GtkWidget* widget, GtkWidgetHelpType arg1);
+
+ // Handler of "move-focus" signal.
+ static void MoveFocus(GtkWidget* widget, GtkDirectionType arg1);
+
+ OwnedWidgetGtk handler_;
+
+ // Buffer to store the match results.
+ EditCommands edit_commands_;
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_GTK_KEY_BINDINGS_HANDLER_H_
diff --git a/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc b/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
new file mode 100644
index 0000000..10c824f
--- /dev/null
+++ b/content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2011 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 "content/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/logging.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "content/common/content_paths.h"
+#include "content/common/edit_command.h"
+#include "content/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) {
+ FilePath gtkrc;
+ PathService::Get(content::DIR_TEST_DATA, &gtkrc);
+ gtkrc = gtkrc.AppendASCII("gtk_key_bindings_test_gtkrc");
+ EXPECT_TRUE(file_util::PathExists(gtkrc));
+
+ gtk_rc_parse(gtkrc.value().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);
+ }
+ LOG(ERROR) << "Failed to create key event for keyval:" << keyval;
+ return NativeWebKeyboardEvent();
+ }
+
+ void TestKeyBinding(const NativeWebKeyboardEvent& event,
+ const EditCommand expected_result[],
+ size_t size) {
+ EditCommands result;
+ 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/content/browser/renderer_host/render_widget_host_view_gtk.cc b/content/browser/renderer_host/render_widget_host_view_gtk.cc
new file mode 100644
index 0000000..403ed10
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_gtk.cc
@@ -0,0 +1,1210 @@
+// Copyright (c) 2011 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 "content/browser/renderer_host/render_widget_host_view_gtk.h"
+
+// If this gets included after the gtk headers, then a bunch of compiler
+// errors happen because of a "#define Status int" in Xlib.h, which interacts
+// badly with net::URLRequestStatus::Status.
+#include "content/common/view_messages.h"
+
+#include <cairo/cairo.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/string_number_conversions.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/renderer_host/backing_store_x.h"
+#include "content/browser/renderer_host/gtk_im_context_wrapper.h"
+#include "content/browser/renderer_host/render_view_host.h"
+#include "content/browser/renderer_host/render_view_host_delegate.h"
+#include "content/browser/renderer_host/render_widget_host.h"
+#include "content/common/content_switches.h"
+#include "content/common/native_web_keyboard_event.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/gtk/WebInputEventFactory.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/base/x/x11_util.h"
+#include "ui/gfx/gtk_native_view_id_manager.h"
+#include "ui/gfx/gtk_preserve_window.h"
+#include "webkit/glue/webaccessibility.h"
+#include "webkit/glue/webcursor_gtk_data.h"
+#include "webkit/plugins/npapi/webplugin.h"
+
+#if defined(OS_CHROMEOS)
+#include "views/widget/tooltip_window_gtk.h"
+#else
+#include "content/browser/renderer_host/gtk_key_bindings_handler.h"
+#endif // defined(OS_CHROMEOS)
+
+namespace {
+
+const int kMaxWindowWidth = 4000;
+const int kMaxWindowHeight = 4000;
+const char* kRenderWidgetHostViewKey = "__RENDER_WIDGET_HOST_VIEW__";
+
+// The duration of the fade-out animation. See |overlay_animation_|.
+const int kFadeEffectDuration = 300;
+
+#if defined(OS_CHROMEOS)
+// TODO(davemoore) Under Chromeos we are increasing the rate that the trackpad
+// generates events to get better precisions. Eventually we will coordinate the
+// driver and this setting to ensure they match.
+const float kDefaultScrollPixelsPerTick = 20;
+#else
+// See WebInputEventFactor.cpp for a reason for this being the default
+// scroll size for linux.
+const float kDefaultScrollPixelsPerTick = 160.0f / 3.0f;
+#endif
+
+const GdkColor kBGColor =
+#if defined(NDEBUG)
+ { 0, 0xff * 257, 0xff * 257, 0xff * 257 };
+#else
+ { 0, 0x00 * 257, 0xff * 257, 0x00 * 257 };
+#endif
+
+// Returns the spinning cursor used for loading state.
+GdkCursor* GetMozSpinningCursor() {
+ static GdkCursor* moz_spinning_cursor = NULL;
+ if (!moz_spinning_cursor) {
+ const GdkColor fg = { 0, 0, 0, 0 };
+ const GdkColor bg = { 65535, 65535, 65535, 65535 };
+ GdkPixmap* source =
+ gdk_bitmap_create_from_data(NULL, moz_spinning_bits, 32, 32);
+ GdkPixmap* mask =
+ gdk_bitmap_create_from_data(NULL, moz_spinning_mask_bits, 32, 32);
+ moz_spinning_cursor =
+ gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, 2, 2);
+ g_object_unref(source);
+ g_object_unref(mask);
+ }
+ return moz_spinning_cursor;
+}
+
+} // namespace
+
+using WebKit::WebInputEventFactory;
+using WebKit::WebMouseWheelEvent;
+
+// This class is a simple convenience wrapper for Gtk functions. It has only
+// static methods.
+class RenderWidgetHostViewGtkWidget {
+ public:
+ static GtkWidget* CreateNewWidget(RenderWidgetHostViewGtk* host_view) {
+ GtkWidget* widget = gtk_preserve_window_new();
+ gtk_widget_set_name(widget, "chrome-render-widget-host-view");
+ // We manually double-buffer in Paint() because Paint() may or may not be
+ // called in repsonse to an "expose-event" signal.
+ gtk_widget_set_double_buffered(widget, FALSE);
+ gtk_widget_set_redraw_on_allocate(widget, FALSE);
+ gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &kBGColor);
+ // Allow the browser window to be resized freely.
+ gtk_widget_set_size_request(widget, 0, 0);
+
+ gtk_widget_add_events(widget, GDK_EXPOSURE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK |
+ GDK_FOCUS_CHANGE_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK);
+ gtk_widget_set_can_focus(widget, TRUE);
+
+ g_signal_connect(widget, "expose-event",
+ G_CALLBACK(OnExposeEvent), host_view);
+ g_signal_connect(widget, "key-press-event",
+ G_CALLBACK(OnKeyPressReleaseEvent), host_view);
+ g_signal_connect(widget, "key-release-event",
+ G_CALLBACK(OnKeyPressReleaseEvent), host_view);
+ g_signal_connect(widget, "focus-in-event",
+ G_CALLBACK(OnFocusIn), host_view);
+ g_signal_connect(widget, "focus-out-event",
+ G_CALLBACK(OnFocusOut), host_view);
+ g_signal_connect(widget, "grab-notify",
+ G_CALLBACK(OnGrabNotify), host_view);
+ g_signal_connect(widget, "button-press-event",
+ G_CALLBACK(OnButtonPressReleaseEvent), host_view);
+ g_signal_connect(widget, "button-release-event",
+ G_CALLBACK(OnButtonPressReleaseEvent), host_view);
+ g_signal_connect(widget, "motion-notify-event",
+ G_CALLBACK(OnMouseMoveEvent), host_view);
+ g_signal_connect(widget, "enter-notify-event",
+ G_CALLBACK(OnCrossingEvent), host_view);
+ g_signal_connect(widget, "leave-notify-event",
+ G_CALLBACK(OnCrossingEvent), host_view);
+ g_signal_connect(widget, "client-event",
+ G_CALLBACK(OnClientEvent), host_view);
+
+
+ // Connect after so that we are called after the handler installed by the
+ // TabContentsView which handles zoom events.
+ g_signal_connect_after(widget, "scroll-event",
+ G_CALLBACK(OnMouseScrollEvent), host_view);
+
+ g_object_set_data(G_OBJECT(widget), kRenderWidgetHostViewKey,
+ static_cast<RenderWidgetHostView*>(host_view));
+
+ return widget;
+ }
+
+ private:
+ static gboolean OnExposeEvent(GtkWidget* widget,
+ GdkEventExpose* expose,
+ RenderWidgetHostViewGtk* host_view) {
+ if (host_view->is_hidden_)
+ return FALSE;
+ const gfx::Rect damage_rect(expose->area);
+ host_view->Paint(damage_rect);
+ return FALSE;
+ }
+
+ static gboolean OnKeyPressReleaseEvent(GtkWidget* widget,
+ GdkEventKey* event,
+ RenderWidgetHostViewGtk* host_view) {
+ // Force popups or fullscreen windows to close on Escape so they won't keep
+ // the keyboard grabbed or be stuck onscreen if the renderer is hanging.
+ bool should_close_on_escape =
+ (host_view->IsPopup() && host_view->NeedsInputGrab()) ||
+ host_view->is_fullscreen_;
+ if (should_close_on_escape && GDK_Escape == event->keyval) {
+ host_view->host_->Shutdown();
+ } else {
+ // Send key event to input method.
+ host_view->im_context_->ProcessKeyEvent(event);
+ }
+
+ // We return TRUE because we did handle the event. If it turns out webkit
+ // can't handle the event, we'll deal with it in
+ // RenderView::UnhandledKeyboardEvent().
+ return TRUE;
+ }
+
+ static gboolean OnFocusIn(GtkWidget* widget,
+ GdkEventFocus* focus,
+ RenderWidgetHostViewGtk* host_view) {
+ host_view->ShowCurrentCursor();
+ host_view->GetRenderWidgetHost()->GotFocus();
+
+ // The only way to enable a GtkIMContext object is to call its focus in
+ // handler.
+ host_view->im_context_->OnFocusIn();
+
+ return TRUE;
+ }
+
+ static gboolean OnFocusOut(GtkWidget* widget,
+ GdkEventFocus* focus,
+ RenderWidgetHostViewGtk* host_view) {
+ // Whenever we lose focus, set the cursor back to that of our parent window,
+ // which should be the default arrow.
+ gdk_window_set_cursor(widget->window, NULL);
+ // If we are showing a context menu, maintain the illusion that webkit has
+ // focus.
+ if (!host_view->is_showing_context_menu_)
+ host_view->GetRenderWidgetHost()->Blur();
+
+ // Prevents us from stealing input context focus in OnGrabNotify() handler.
+ host_view->was_imcontext_focused_before_grab_ = false;
+
+ // Disable the GtkIMContext object.
+ host_view->im_context_->OnFocusOut();
+
+ return TRUE;
+ }
+
+ // Called when we are shadowed or unshadowed by a keyboard grab (which will
+ // occur for activatable popups, such as dropdown menus). Popup windows do not
+ // take focus, so we never get a focus out or focus in event when they are
+ // shown, and must rely on this signal instead.
+ static void OnGrabNotify(GtkWidget* widget, gboolean was_grabbed,
+ RenderWidgetHostViewGtk* host_view) {
+ if (was_grabbed) {
+ if (host_view->was_imcontext_focused_before_grab_)
+ host_view->im_context_->OnFocusIn();
+ } else {
+ host_view->was_imcontext_focused_before_grab_ =
+ host_view->im_context_->is_focused();
+ if (host_view->was_imcontext_focused_before_grab_) {
+ gdk_window_set_cursor(widget->window, NULL);
+ host_view->im_context_->OnFocusOut();
+ }
+ }
+ }
+
+ static gboolean OnButtonPressReleaseEvent(
+ GtkWidget* widget,
+ GdkEventButton* event,
+ RenderWidgetHostViewGtk* host_view) {
+#if defined (OS_CHROMEOS)
+ // We support buttons 8 & 9 for scrolling with an attached USB mouse
+ // in ChromeOS. We do this separately from the builtin scrolling support
+ // because we want to support the user's expectations about the amount
+ // scrolled on each event. xorg.conf on chromeos specifies buttons
+ // 8 & 9 for the scroll wheel for the attached USB mouse.
+ if (event->type == GDK_BUTTON_RELEASE &&
+ (event->button == 8 || event->button == 9)) {
+ GdkEventScroll scroll_event;
+ scroll_event.type = GDK_SCROLL;
+ scroll_event.window = event->window;
+ scroll_event.send_event = event->send_event;
+ scroll_event.time = event->time;
+ scroll_event.x = event->x;
+ scroll_event.y = event->y;
+ scroll_event.state = event->state;
+ if (event->state & GDK_SHIFT_MASK) {
+ scroll_event.direction =
+ event->button == 8 ? GDK_SCROLL_LEFT : GDK_SCROLL_RIGHT;
+ } else {
+ scroll_event.direction =
+ event->button == 8 ? GDK_SCROLL_UP : GDK_SCROLL_DOWN;
+ }
+ scroll_event.device = event->device;
+ scroll_event.x_root = event->x_root;
+ scroll_event.y_root = event->y_root;
+ WebMouseWheelEvent web_event =
+ WebInputEventFactory::mouseWheelEvent(&scroll_event);
+ host_view->GetRenderWidgetHost()->ForwardWheelEvent(web_event);
+ }
+#endif
+
+ if (event->type != GDK_BUTTON_RELEASE)
+ host_view->set_last_mouse_down(event);
+
+ if (!(event->button == 1 || event->button == 2 || event->button == 3))
+ return FALSE; // We do not forward any other buttons to the renderer.
+ if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS)
+ return FALSE;
+
+ // If we don't have focus already, this mouse click will focus us.
+ if (!gtk_widget_is_focus(widget))
+ host_view->host_->OnMouseActivate();
+
+ // Confirm existing composition text on mouse click events, to make sure
+ // the input caret won't be moved with an ongoing composition session.
+ if (event->type != GDK_BUTTON_RELEASE)
+ host_view->im_context_->ConfirmComposition();
+
+ // We want to translate the coordinates of events that do not originate
+ // from this widget to be relative to the top left of the widget.
+ GtkWidget* event_widget = gtk_get_event_widget(
+ reinterpret_cast<GdkEvent*>(event));
+ if (event_widget != widget) {
+ int x = 0;
+ int y = 0;
+ gtk_widget_get_pointer(widget, &x, &y);
+ // If the mouse event happens outside our popup, force the popup to
+ // close. We do this so a hung renderer doesn't prevent us from
+ // releasing the x pointer grab.
+ bool click_in_popup = x >= 0 && y >= 0 && x < widget->allocation.width &&
+ y < widget->allocation.height;
+ // Only Shutdown on mouse downs. Mouse ups can occur outside the render
+ // view if the user drags for DnD or while using the scrollbar on a select
+ // dropdown. Don't shutdown if we are not a popup.
+ if (event->type != GDK_BUTTON_RELEASE && host_view->IsPopup() &&
+ !host_view->is_popup_first_mouse_release_ && !click_in_popup) {
+ host_view->host_->Shutdown();
+ return FALSE;
+ }
+ event->x = x;
+ event->y = y;
+ }
+
+ // TODO(evanm): why is this necessary here but not in test shell?
+ // This logic is the same as GtkButton.
+ if (event->type == GDK_BUTTON_PRESS && !gtk_widget_has_focus(widget))
+ gtk_widget_grab_focus(widget);
+
+ host_view->is_popup_first_mouse_release_ = false;
+ host_view->GetRenderWidgetHost()->ForwardMouseEvent(
+ WebInputEventFactory::mouseEvent(event));
+
+ // Although we did handle the mouse event, we need to let other handlers
+ // run (in particular the one installed by TabContentsViewGtk).
+ return FALSE;
+ }
+
+ static gboolean OnMouseMoveEvent(GtkWidget* widget,
+ GdkEventMotion* event,
+ RenderWidgetHostViewGtk* host_view) {
+ // We want to translate the coordinates of events that do not originate
+ // from this widget to be relative to the top left of the widget.
+ GtkWidget* event_widget = gtk_get_event_widget(
+ reinterpret_cast<GdkEvent*>(event));
+ if (event_widget != widget) {
+ int x = 0;
+ int y = 0;
+ gtk_widget_get_pointer(widget, &x, &y);
+ event->x = x;
+ event->y = y;
+ }
+
+ host_view->ModifyEventForEdgeDragging(widget, event);
+ host_view->GetRenderWidgetHost()->ForwardMouseEvent(
+ WebInputEventFactory::mouseEvent(event));
+ return FALSE;
+ }
+
+ static gboolean OnCrossingEvent(GtkWidget* widget,
+ GdkEventCrossing* event,
+ RenderWidgetHostViewGtk* host_view) {
+ const int any_button_mask =
+ GDK_BUTTON1_MASK |
+ GDK_BUTTON2_MASK |
+ GDK_BUTTON3_MASK |
+ GDK_BUTTON4_MASK |
+ GDK_BUTTON5_MASK;
+
+ // Only forward crossing events if the mouse button is not down.
+ // (When the mouse button is down, the proper events are already being
+ // sent by ButtonPressReleaseEvent and MouseMoveEvent, above, and if we
+ // additionally send this crossing event with the state indicating the
+ // button is down, it causes problems with drag and drop in WebKit.)
+ if (!(event->state & any_button_mask)) {
+ host_view->GetRenderWidgetHost()->ForwardMouseEvent(
+ WebInputEventFactory::mouseEvent(event));
+ }
+
+ return FALSE;
+ }
+
+ static gboolean OnClientEvent(GtkWidget* widget,
+ GdkEventClient* event,
+ RenderWidgetHostViewGtk* host_view) {
+ VLOG(1) << "client event type: " << event->message_type
+ << " data_format: " << event->data_format
+ << " data: " << event->data.l;
+ return TRUE;
+ }
+
+ // Allow the vertical scroll delta to be overridden from the command line.
+ // This will allow us to test more easily to discover the amount
+ // (either hard coded or computed) that's best.
+ static float GetScrollPixelsPerTick() {
+ static float scroll_pixels = -1;
+ if (scroll_pixels < 0) {
+ // TODO(brettw): Remove the command line switch (crbug.com/63525)
+ scroll_pixels = kDefaultScrollPixelsPerTick;
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ std::string scroll_pixels_option =
+ command_line->GetSwitchValueASCII(switches::kScrollPixels);
+ if (!scroll_pixels_option.empty()) {
+ double v;
+ if (base::StringToDouble(scroll_pixels_option, &v))
+ scroll_pixels = static_cast<float>(v);
+ }
+ DCHECK_GT(scroll_pixels, 0);
+ }
+ return scroll_pixels;
+ }
+
+ // Return the net up / down (or left / right) distance represented by events
+ // in the events will be removed from the queue. We only look at the top of
+ // queue...any other type of event will cause us not to look farther.
+ // If there is a change to the set of modifier keys or scroll axis
+ // in the events we will stop looking as well.
+ static int GetPendingScrollDelta(bool vert, guint current_event_state) {
+ int num_clicks = 0;
+ GdkEvent* event;
+ bool event_coalesced = true;
+ while ((event = gdk_event_get()) && event_coalesced) {
+ event_coalesced = false;
+ if (event->type == GDK_SCROLL) {
+ GdkEventScroll scroll = event->scroll;
+ if (scroll.state & GDK_SHIFT_MASK) {
+ if (scroll.direction == GDK_SCROLL_UP)
+ scroll.direction = GDK_SCROLL_LEFT;
+ else if (scroll.direction == GDK_SCROLL_DOWN)
+ scroll.direction = GDK_SCROLL_RIGHT;
+ }
+ if (vert) {
+ if (scroll.direction == GDK_SCROLL_UP ||
+ scroll.direction == GDK_SCROLL_DOWN) {
+ if (scroll.state == current_event_state) {
+ num_clicks += (scroll.direction == GDK_SCROLL_UP ? 1 : -1);
+ gdk_event_free(event);
+ event_coalesced = true;
+ }
+ }
+ } else {
+ if (scroll.direction == GDK_SCROLL_LEFT ||
+ scroll.direction == GDK_SCROLL_RIGHT) {
+ if (scroll.state == current_event_state) {
+ num_clicks += (scroll.direction == GDK_SCROLL_LEFT ? 1 : -1);
+ gdk_event_free(event);
+ event_coalesced = true;
+ }
+ }
+ }
+ }
+ }
+ // If we have an event left we put it back on the queue.
+ if (event) {
+ gdk_event_put(event);
+ gdk_event_free(event);
+ }
+ return num_clicks * GetScrollPixelsPerTick();
+ }
+
+ static gboolean OnMouseScrollEvent(GtkWidget* widget,
+ GdkEventScroll* event,
+ RenderWidgetHostViewGtk* host_view) {
+ // If the user is holding shift, translate it into a horizontal scroll. We
+ // don't care what other modifiers the user may be holding (zooming is
+ // handled at the TabContentsView level).
+ if (event->state & GDK_SHIFT_MASK) {
+ if (event->direction == GDK_SCROLL_UP)
+ event->direction = GDK_SCROLL_LEFT;
+ else if (event->direction == GDK_SCROLL_DOWN)
+ event->direction = GDK_SCROLL_RIGHT;
+ }
+
+ WebMouseWheelEvent web_event = WebInputEventFactory::mouseWheelEvent(event);
+ // We peek ahead at the top of the queue to look for additional pending
+ // scroll events.
+ if (event->direction == GDK_SCROLL_UP ||
+ event->direction == GDK_SCROLL_DOWN) {
+ if (event->direction == GDK_SCROLL_UP)
+ web_event.deltaY = GetScrollPixelsPerTick();
+ else
+ web_event.deltaY = -GetScrollPixelsPerTick();
+ web_event.deltaY += GetPendingScrollDelta(true, event->state);
+ } else {
+ if (event->direction == GDK_SCROLL_LEFT)
+ web_event.deltaX = GetScrollPixelsPerTick();
+ else
+ web_event.deltaX = -GetScrollPixelsPerTick();
+ web_event.deltaX += GetPendingScrollDelta(false, event->state);
+ }
+ host_view->GetRenderWidgetHost()->ForwardWheelEvent(web_event);
+ return FALSE;
+ }
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget);
+};
+
+// static
+RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
+ RenderWidgetHost* widget) {
+ return new RenderWidgetHostViewGtk(widget);
+}
+
+RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host)
+ : host_(widget_host),
+ about_to_validate_and_paint_(false),
+ is_hidden_(false),
+ is_loading_(false),
+ is_showing_context_menu_(false),
+ overlay_color_(0),
+ overlay_animation_(this),
+ parent_(NULL),
+ is_popup_first_mouse_release_(true),
+ was_imcontext_focused_before_grab_(false),
+ do_x_grab_(false),
+ is_fullscreen_(false),
+ destroy_handler_id_(0),
+ dragged_at_horizontal_edge_(0),
+ dragged_at_vertical_edge_(0),
+ compositing_surface_(gfx::kNullPluginWindow),
+ last_mouse_down_(NULL) {
+ host_->SetView(this);
+}
+
+RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() {
+ set_last_mouse_down(NULL);
+ view_.Destroy();
+}
+
+void RenderWidgetHostViewGtk::InitAsChild() {
+ DoSharedInit();
+ overlay_animation_.SetDuration(kFadeEffectDuration);
+ overlay_animation_.SetSlideDuration(kFadeEffectDuration);
+ gtk_widget_show(view_.get());
+}
+
+void RenderWidgetHostViewGtk::InitAsPopup(
+ RenderWidgetHostView* parent_host_view, const gfx::Rect& pos) {
+ // If we aren't a popup, then |window| will be leaked.
+ DCHECK(IsPopup());
+
+ DoSharedInit();
+ parent_ = parent_host_view->GetNativeView();
+ GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
+ gtk_container_add(GTK_CONTAINER(window), view_.get());
+ DoPopupOrFullscreenInit(window, pos);
+
+ // The underlying X window needs to be created and mapped by the above code
+ // before we can grab the input devices.
+ if (NeedsInputGrab()) {
+ // Grab all input for the app. If a click lands outside the bounds of the
+ // popup, WebKit will notice and destroy us. Before doing this we need
+ // to ensure that the the popup is added to the browser's window group,
+ // to allow for the grabs to work correctly.
+ gtk_window_group_add_window(gtk_window_get_group(
+ GTK_WINDOW(gtk_widget_get_toplevel(parent_))), window);
+ gtk_grab_add(view_.get());
+
+ // We need for the application to do an X grab as well. However if the app
+ // already has an X grab (as in the case of extension popup), an app grab
+ // will suffice.
+ do_x_grab_ = !gdk_pointer_is_grabbed();
+
+ // Now grab all of X's input.
+ if (do_x_grab_) {
+ gdk_pointer_grab(
+ parent_->window,
+ TRUE, // Only events outside of the window are reported with respect
+ // to |parent_->window|.
+ static_cast<GdkEventMask>(GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK),
+ NULL,
+ NULL,
+ GDK_CURRENT_TIME);
+ // We grab keyboard events too so things like alt+tab are eaten.
+ gdk_keyboard_grab(parent_->window, TRUE, GDK_CURRENT_TIME);
+ }
+ }
+}
+
+void RenderWidgetHostViewGtk::InitAsFullscreen(
+ RenderWidgetHostView* /*reference_host_view*/) {
+ DoSharedInit();
+
+ is_fullscreen_ = true;
+ GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ gtk_window_set_decorated(window, FALSE);
+ gtk_window_fullscreen(window);
+ g_signal_connect(GTK_WIDGET(window),
+ "window-state-event",
+ G_CALLBACK(&OnWindowStateEventThunk),
+ this);
+ destroy_handler_id_ = g_signal_connect(GTK_WIDGET(window),
+ "destroy",
+ G_CALLBACK(OnDestroyThunk),
+ this);
+ gtk_container_add(GTK_CONTAINER(window), view_.get());
+
+ // Try to move and resize the window to cover the screen in case the window
+ // manager doesn't support _NET_WM_STATE_FULLSCREEN.
+ GdkScreen* screen = gtk_window_get_screen(window);
+ gfx::Rect bounds(
+ 0, 0, gdk_screen_get_width(screen), gdk_screen_get_height(screen));
+ DoPopupOrFullscreenInit(window, bounds);
+}
+
+RenderWidgetHost* RenderWidgetHostViewGtk::GetRenderWidgetHost() const {
+ return host_;
+}
+
+void RenderWidgetHostViewGtk::DidBecomeSelected() {
+ if (!is_hidden_)
+ return;
+
+ if (tab_switch_paint_time_.is_null())
+ tab_switch_paint_time_ = base::TimeTicks::Now();
+ is_hidden_ = false;
+ host_->WasRestored();
+}
+
+void RenderWidgetHostViewGtk::WasHidden() {
+ if (is_hidden_)
+ return;
+
+ // If we receive any more paint messages while we are hidden, we want to
+ // ignore them so we don't re-allocate the backing store. We will paint
+ // everything again when we become selected again.
+ is_hidden_ = true;
+
+ // If we have a renderer, then inform it that we are being hidden so it can
+ // reduce its resource utilization.
+ GetRenderWidgetHost()->WasHidden();
+}
+
+void RenderWidgetHostViewGtk::SetSize(const gfx::Size& size) {
+ int width = std::min(size.width(), kMaxWindowWidth);
+ int height = std::min(size.height(), kMaxWindowHeight);
+ if (IsPopup()) {
+ // We're a popup, honor the size request.
+ gtk_widget_set_size_request(view_.get(), width, height);
+ } else {
+#if defined(TOOLKIT_VIEWS)
+ // TOOLKIT_VIEWS' resize logic flow matches windows. so we go ahead and
+ // size the widget. In GTK+, the size of the widget is determined by its
+ // children.
+ gtk_widget_set_size_request(view_.get(), width, height);
+#endif
+ }
+
+ // Update the size of the RWH.
+ if (requested_size_.width() != width ||
+ requested_size_.height() != height) {
+ requested_size_ = gfx::Size(width, height);
+ host_->WasResized();
+ }
+}
+
+void RenderWidgetHostViewGtk::SetBounds(const gfx::Rect& rect) {
+ // This is called when webkit has sent us a Move message.
+ if (IsPopup()) {
+ gtk_window_move(GTK_WINDOW(gtk_widget_get_toplevel(view_.get())),
+ rect.x(), rect.y());
+ }
+
+ SetSize(rect.size());
+}
+
+gfx::NativeView RenderWidgetHostViewGtk::GetNativeView() {
+ return view_.get();
+}
+
+void RenderWidgetHostViewGtk::MovePluginWindows(
+ const std::vector<webkit::npapi::WebPluginGeometry>& moves) {
+ for (size_t i = 0; i < moves.size(); ++i) {
+ plugin_container_manager_.MovePluginContainer(moves[i]);
+ }
+}
+
+void RenderWidgetHostViewGtk::Focus() {
+ gtk_widget_grab_focus(view_.get());
+}
+
+void RenderWidgetHostViewGtk::Blur() {
+ // TODO(estade): We should be clearing native focus as well, but I know of no
+ // way to do that without focusing another widget.
+ host_->Blur();
+}
+
+bool RenderWidgetHostViewGtk::HasFocus() {
+ return gtk_widget_is_focus(view_.get());
+}
+
+void RenderWidgetHostViewGtk::Show() {
+ gtk_widget_show(view_.get());
+}
+
+void RenderWidgetHostViewGtk::Hide() {
+ gtk_widget_hide(view_.get());
+}
+
+bool RenderWidgetHostViewGtk::IsShowing() {
+ return gtk_widget_get_visible(view_.get());
+}
+
+gfx::Rect RenderWidgetHostViewGtk::GetViewBounds() const {
+ GtkAllocation* alloc = &view_.get()->allocation;
+ return gfx::Rect(alloc->x, alloc->y,
+ requested_size_.width(),
+ requested_size_.height());
+}
+
+void RenderWidgetHostViewGtk::UpdateCursor(const WebCursor& cursor) {
+ // Optimize the common case, where the cursor hasn't changed.
+ // However, we can switch between different pixmaps, so only on the
+ // non-pixmap branch.
+ if (current_cursor_.GetCursorType() != GDK_CURSOR_IS_PIXMAP &&
+ current_cursor_.GetCursorType() == cursor.GetCursorType()) {
+ return;
+ }
+
+ current_cursor_ = cursor;
+ ShowCurrentCursor();
+}
+
+void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) {
+ is_loading_ = is_loading;
+ // Only call ShowCurrentCursor() when it will actually change the cursor.
+ if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR)
+ ShowCurrentCursor();
+}
+
+void RenderWidgetHostViewGtk::ImeUpdateTextInputState(
+ ui::TextInputType type,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect) {
+ im_context_->UpdateInputMethodState(type, can_compose_inline, caret_rect);
+}
+
+void RenderWidgetHostViewGtk::ImeCancelComposition() {
+ im_context_->CancelComposition();
+}
+
+void RenderWidgetHostViewGtk::DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy,
+ const std::vector<gfx::Rect>& copy_rects) {
+ if (is_hidden_)
+ return;
+
+ // TODO(darin): Implement the equivalent of Win32's ScrollWindowEX. Can that
+ // be done using XCopyArea? Perhaps similar to
+ // BackingStore::ScrollBackingStore?
+ if (about_to_validate_and_paint_)
+ invalid_rect_ = invalid_rect_.Union(scroll_rect);
+ else
+ Paint(scroll_rect);
+
+ for (size_t i = 0; i < copy_rects.size(); ++i) {
+ // Avoid double painting. NOTE: This is only relevant given the call to
+ // Paint(scroll_rect) above.
+ gfx::Rect rect = copy_rects[i].Subtract(scroll_rect);
+ if (rect.IsEmpty())
+ continue;
+
+ if (about_to_validate_and_paint_)
+ invalid_rect_ = invalid_rect_.Union(rect);
+ else
+ Paint(rect);
+ }
+}
+
+void RenderWidgetHostViewGtk::RenderViewGone(base::TerminationStatus status,
+ int error_code) {
+ Destroy();
+ plugin_container_manager_.set_host_widget(NULL);
+}
+
+void RenderWidgetHostViewGtk::Destroy() {
+ if (compositing_surface_ != gfx::kNullPluginWindow) {
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ manager->ReleasePermanentXID(compositing_surface_);
+ }
+
+ if (do_x_grab_) {
+ // Undo the X grab.
+ GdkDisplay* display = gtk_widget_get_display(parent_);
+ gdk_display_pointer_ungrab(display, GDK_CURRENT_TIME);
+ gdk_display_keyboard_ungrab(display, GDK_CURRENT_TIME);
+ }
+
+ // If this is a popup or fullscreen widget, then we need to destroy the window
+ // that we created to hold it.
+ if (IsPopup() || is_fullscreen_) {
+ GtkWidget* window = gtk_widget_get_parent(view_.get());
+
+ // Disconnect the destroy handler so that we don't try to shutdown twice.
+ if (is_fullscreen_)
+ g_signal_handler_disconnect(window, destroy_handler_id_);
+
+ gtk_widget_destroy(window);
+ }
+
+ // Remove |view_| from all containers now, so nothing else can hold a
+ // reference to |view_|'s widget except possibly a gtk signal handler if
+ // this code is currently executing within the context of a gtk signal
+ // handler. Note that |view_| is still alive after this call. It will be
+ // deallocated in the destructor.
+ // See http://www.crbug.com/11847 for details.
+ gtk_widget_destroy(view_.get());
+
+ // The RenderWidgetHost's destruction led here, so don't call it.
+ host_ = NULL;
+
+ MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+void RenderWidgetHostViewGtk::SetTooltipText(const std::wstring& tooltip_text) {
+ // Maximum number of characters we allow in a tooltip.
+ const int kMaxTooltipLength = 8 << 10;
+ // Clamp the tooltip length to kMaxTooltipLength so that we don't
+ // accidentally DOS the user with a mega tooltip (since GTK doesn't do
+ // this itself).
+ // I filed https://bugzilla.gnome.org/show_bug.cgi?id=604641 upstream.
+ const string16 clamped_tooltip =
+ ui::TruncateString(WideToUTF16Hack(tooltip_text),
+ kMaxTooltipLength);
+
+ if (clamped_tooltip.empty()) {
+ gtk_widget_set_has_tooltip(view_.get(), FALSE);
+ } else {
+ gtk_widget_set_tooltip_text(view_.get(),
+ UTF16ToUTF8(clamped_tooltip).c_str());
+#if defined(OS_CHROMEOS)
+ tooltip_window_->SetTooltipText(UTF16ToWideHack(clamped_tooltip));
+#endif // defined(OS_CHROMEOS)
+ }
+}
+
+void RenderWidgetHostViewGtk::SelectionChanged(const std::string& text,
+ const ui::Range& range,
+ const gfx::Point& start,
+ const gfx::Point& end) {
+ if (!text.empty()) {
+ GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text(x_clipboard, text.c_str(), text.length());
+ }
+}
+
+void RenderWidgetHostViewGtk::ShowingContextMenu(bool showing) {
+ is_showing_context_menu_ = showing;
+}
+
+#if !defined(TOOLKIT_VIEWS)
+GtkWidget* RenderWidgetHostViewGtk::BuildInputMethodsGtkMenu() {
+ return im_context_->BuildInputMethodsGtkMenu();
+}
+#endif
+
+gboolean RenderWidgetHostViewGtk::OnWindowStateEvent(
+ GtkWidget* widget,
+ GdkEventWindowState* event) {
+ if (is_fullscreen_) {
+ // If a fullscreen widget got unfullscreened (e.g. by the window manager),
+ // close it.
+ bool unfullscreened =
+ (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) &&
+ !(event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN);
+ if (unfullscreened) {
+ host_->Shutdown();
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void RenderWidgetHostViewGtk::OnDestroy(GtkWidget* widget) {
+ DCHECK(is_fullscreen_);
+ host_->Shutdown();
+}
+
+bool RenderWidgetHostViewGtk::NeedsInputGrab() {
+ return popup_type_ == WebKit::WebPopupTypeSelect;
+}
+
+bool RenderWidgetHostViewGtk::IsPopup() const {
+ return popup_type_ != WebKit::WebPopupTypeNone;
+}
+
+void RenderWidgetHostViewGtk::DoSharedInit() {
+ view_.Own(RenderWidgetHostViewGtkWidget::CreateNewWidget(this));
+ im_context_.reset(new GtkIMContextWrapper(this));
+ plugin_container_manager_.set_host_widget(view_.get());
+#if defined(OS_CHROMEOS)
+ tooltip_window_.reset(new views::TooltipWindowGtk(view_.get()));
+#else
+ key_bindings_handler_.reset(new GtkKeyBindingsHandler(view_.get()));
+#endif
+}
+
+void RenderWidgetHostViewGtk::DoPopupOrFullscreenInit(GtkWindow* window,
+ const gfx::Rect& bounds) {
+ requested_size_.SetSize(std::min(bounds.width(), kMaxWindowWidth),
+ std::min(bounds.height(), kMaxWindowHeight));
+ host_->WasResized();
+
+ gtk_widget_set_size_request(
+ view_.get(), requested_size_.width(), requested_size_.height());
+
+ // Don't allow the window to be resized. This also forces the window to
+ // shrink down to the size of its child contents.
+ gtk_window_set_resizable(window, FALSE);
+ gtk_window_set_default_size(window, -1, -1);
+ gtk_window_move(window, bounds.x(), bounds.y());
+
+ gtk_widget_show_all(GTK_WIDGET(window));
+}
+
+BackingStore* RenderWidgetHostViewGtk::AllocBackingStore(
+ const gfx::Size& size) {
+ return new BackingStoreX(host_, size,
+ ui::GetVisualFromGtkWidget(view_.get()),
+ gtk_widget_get_visual(view_.get())->depth);
+}
+
+void RenderWidgetHostViewGtk::SetBackground(const SkBitmap& background) {
+ RenderWidgetHostView::SetBackground(background);
+ host_->Send(new ViewMsg_SetBackground(host_->routing_id(), background));
+}
+
+void RenderWidgetHostViewGtk::ModifyEventForEdgeDragging(
+ GtkWidget* widget, GdkEventMotion* event) {
+ // If the widget is aligned with an edge of the monitor its on and the user
+ // attempts to drag past that edge we track the number of times it has
+ // occurred, so that we can force the widget to scroll when it otherwise
+ // would be unable to, by modifying the (x,y) position in the drag
+ // event that we forward on to webkit. If we get a move that's no longer a
+ // drag or a drag indicating the user is no longer at that edge we stop
+ // altering the drag events.
+ int new_dragged_at_horizontal_edge = 0;
+ int new_dragged_at_vertical_edge = 0;
+ // Used for checking the edges of the monitor. We cache the values to save
+ // roundtrips to the X server.
+ static gfx::Size drag_monitor_size;
+ if (event->state & GDK_BUTTON1_MASK) {
+ if (drag_monitor_size.IsEmpty()) {
+ // We can safely cache the monitor size for the duration of a drag.
+ GdkScreen* screen = gtk_widget_get_screen(widget);
+ int monitor =
+ gdk_screen_get_monitor_at_point(screen, event->x_root, event->y_root);
+ GdkRectangle geometry;
+ gdk_screen_get_monitor_geometry(screen, monitor, &geometry);
+ drag_monitor_size.SetSize(geometry.width, geometry.height);
+ }
+
+ // Check X and Y independently, as the user could be dragging into a corner.
+ if (event->x == 0 && event->x_root == 0) {
+ new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ - 1;
+ } else if (widget->allocation.width - 1 == static_cast<gint>(event->x) &&
+ drag_monitor_size.width() - 1 == static_cast<gint>(event->x_root)) {
+ new_dragged_at_horizontal_edge = dragged_at_horizontal_edge_ + 1;
+ }
+
+ if (event->y == 0 && event->y_root == 0) {
+ new_dragged_at_vertical_edge = dragged_at_vertical_edge_ - 1;
+ } else if (widget->allocation.height - 1 == static_cast<gint>(event->y) &&
+ drag_monitor_size.height() - 1 == static_cast<gint>(event->y_root)) {
+ new_dragged_at_vertical_edge = dragged_at_vertical_edge_ + 1;
+ }
+
+ event->x_root += new_dragged_at_horizontal_edge;
+ event->x += new_dragged_at_horizontal_edge;
+ event->y_root += new_dragged_at_vertical_edge;
+ event->y += new_dragged_at_vertical_edge;
+ } else {
+ // Clear whenever we get a non-drag mouse move.
+ drag_monitor_size.SetSize(0, 0);
+ }
+ dragged_at_horizontal_edge_ = new_dragged_at_horizontal_edge;
+ dragged_at_vertical_edge_ = new_dragged_at_vertical_edge;
+}
+
+void RenderWidgetHostViewGtk::Paint(const gfx::Rect& damage_rect) {
+ // If the GPU process is rendering directly into the View,
+ // call the compositor directly.
+ RenderWidgetHost* render_widget_host = GetRenderWidgetHost();
+ if (render_widget_host->is_accelerated_compositing_active()) {
+ host_->ScheduleComposite();
+ return;
+ }
+
+ GdkWindow* window = view_.get()->window;
+ DCHECK(!about_to_validate_and_paint_);
+
+ invalid_rect_ = damage_rect;
+ about_to_validate_and_paint_ = true;
+ BackingStoreX* backing_store = static_cast<BackingStoreX*>(
+ host_->GetBackingStore(true));
+ // Calling GetBackingStore maybe have changed |invalid_rect_|...
+ about_to_validate_and_paint_ = false;
+
+ gfx::Rect paint_rect = gfx::Rect(0, 0, kMaxWindowWidth, kMaxWindowHeight);
+ paint_rect = paint_rect.Intersect(invalid_rect_);
+
+ if (backing_store) {
+ // Only render the widget if it is attached to a window; there's a short
+ // period where this object isn't attached to a window but hasn't been
+ // Destroy()ed yet and it receives paint messages...
+ if (window) {
+ if (SkColorGetA(overlay_color_) == 0) {
+ // In the common case, use XCopyArea. We don't draw more than once, so
+ // we don't need to double buffer.
+ backing_store->XShowRect(gfx::Point(0, 0),
+ paint_rect, ui::GetX11WindowFromGtkWidget(view_.get()));
+ } else {
+ // If the grey blend is showing, we make two drawing calls. Use double
+ // buffering to prevent flicker. Use CairoShowRect because XShowRect
+ // shortcuts GDK's double buffering. We won't be able to draw outside
+ // of |damage_rect|, so invalidate the difference between |paint_rect|
+ // and |damage_rect|.
+ if (paint_rect != damage_rect) {
+ GdkRectangle extra_gdk_rect =
+ paint_rect.Subtract(damage_rect).ToGdkRectangle();
+ gdk_window_invalidate_rect(window, &extra_gdk_rect, false);
+ }
+
+ GdkRectangle rect = { damage_rect.x(), damage_rect.y(),
+ damage_rect.width(), damage_rect.height() };
+ gdk_window_begin_paint_rect(window, &rect);
+
+ backing_store->CairoShowRect(damage_rect, GDK_DRAWABLE(window));
+
+ cairo_t* cr = gdk_cairo_create(window);
+ gdk_cairo_rectangle(cr, &rect);
+ SkColor overlay = SkColorSetA(
+ overlay_color_,
+ SkColorGetA(overlay_color_) *
+ overlay_animation_.GetCurrentValue());
+ float r = SkColorGetR(overlay) / 255.;
+ float g = SkColorGetG(overlay) / 255.;
+ float b = SkColorGetB(overlay) / 255.;
+ float a = SkColorGetA(overlay) / 255.;
+ cairo_set_source_rgba(cr, r, g, b, a);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+
+ gdk_window_end_paint(window);
+ }
+ }
+ if (!whiteout_start_time_.is_null()) {
+ base::TimeDelta whiteout_duration = base::TimeTicks::Now() -
+ whiteout_start_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWHH_WhiteoutDuration", whiteout_duration);
+
+ // Reset the start time to 0 so that we start recording again the next
+ // time the backing store is NULL...
+ whiteout_start_time_ = base::TimeTicks();
+ }
+ if (!tab_switch_paint_time_.is_null()) {
+ base::TimeDelta tab_switch_paint_duration = base::TimeTicks::Now() -
+ tab_switch_paint_time_;
+ UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration",
+ tab_switch_paint_duration);
+ // Reset tab_switch_paint_time_ to 0 so future tab selections are
+ // recorded.
+ tab_switch_paint_time_ = base::TimeTicks();
+ }
+ } else {
+ if (window)
+ gdk_window_clear(window);
+ if (whiteout_start_time_.is_null())
+ whiteout_start_time_ = base::TimeTicks::Now();
+ }
+}
+
+void RenderWidgetHostViewGtk::ShowCurrentCursor() {
+ // The widget may not have a window. If that's the case, abort mission. This
+ // is the same issue as that explained above in Paint().
+ if (!view_.get()->window)
+ return;
+
+ // TODO(port): WebKit bug https://bugs.webkit.org/show_bug.cgi?id=16388 is
+ // that calling gdk_window_set_cursor repeatedly is expensive. We should
+ // avoid it here where possible.
+ GdkCursor* gdk_cursor;
+ if (current_cursor_.GetCursorType() == GDK_LAST_CURSOR) {
+ // Use MOZ_CURSOR_SPINNING if we are showing the default cursor and
+ // the page is loading.
+ gdk_cursor = is_loading_ ? GetMozSpinningCursor() : NULL;
+ } else {
+ gdk_cursor = current_cursor_.GetNativeCursor();
+ }
+ gdk_window_set_cursor(view_.get()->window, gdk_cursor);
+}
+
+void RenderWidgetHostViewGtk::CreatePluginContainer(
+ gfx::PluginWindowHandle id) {
+ plugin_container_manager_.CreatePluginContainer(id);
+}
+
+void RenderWidgetHostViewGtk::DestroyPluginContainer(
+ gfx::PluginWindowHandle id) {
+ plugin_container_manager_.DestroyPluginContainer(id);
+}
+
+void RenderWidgetHostViewGtk::SetVisuallyDeemphasized(
+ const SkColor* color, bool animate) {
+ // Do nothing unless |color| has changed, meaning |animate| is only
+ // respected for the first call.
+ if (color && (*color == overlay_color_))
+ return;
+
+ overlay_color_ = color ? *color : 0;
+
+ if (animate) {
+ overlay_animation_.Reset();
+ overlay_animation_.Show();
+ } else {
+ overlay_animation_.Reset(1.0);
+ gtk_widget_queue_draw(view_.get());
+ }
+}
+
+void RenderWidgetHostViewGtk::UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) {
+}
+
+void RenderWidgetHostViewGtk::SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) {
+}
+
+void RenderWidgetHostViewGtk::SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) {
+}
+
+
+void RenderWidgetHostViewGtk::AcceleratedCompositingActivated(bool activated) {
+ GtkPreserveWindow* widget =
+ reinterpret_cast<GtkPreserveWindow*>(view_.get());
+
+ gtk_preserve_window_delegate_resize(widget, activated);
+}
+
+gfx::PluginWindowHandle RenderWidgetHostViewGtk::GetCompositingSurface() {
+ if (compositing_surface_ == gfx::kNullPluginWindow) {
+ GtkNativeViewManager* manager = GtkNativeViewManager::GetInstance();
+ gfx::NativeViewId view_id = gfx::IdFromNativeView(GetNativeView());
+
+ if (!manager->GetPermanentXIDForId(&compositing_surface_, view_id)) {
+ DLOG(ERROR) << "Can't find XID for view id " << view_id;
+ }
+ }
+ return compositing_surface_;
+}
+
+void RenderWidgetHostViewGtk::ForwardKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ if (!host_)
+ return;
+
+#if !defined(OS_CHROMEOS)
+ EditCommands edit_commands;
+ if (!event.skip_in_browser &&
+ key_bindings_handler_->Match(event, &edit_commands)) {
+ host_->Send(new ViewMsg_SetEditCommandsForNextKeyEvent(
+ host_->routing_id(), edit_commands));
+ NativeWebKeyboardEvent copy_event(event);
+ copy_event.match_edit_command = true;
+ host_->ForwardKeyboardEvent(copy_event);
+ return;
+ }
+#endif
+
+ host_->ForwardKeyboardEvent(event);
+}
+
+void RenderWidgetHostViewGtk::AnimationEnded(const ui::Animation* animation) {
+ gtk_widget_queue_draw(view_.get());
+}
+
+void RenderWidgetHostViewGtk::AnimationProgressed(
+ const ui::Animation* animation) {
+ gtk_widget_queue_draw(view_.get());
+}
+
+void RenderWidgetHostViewGtk::AnimationCanceled(
+ const ui::Animation* animation) {
+ gtk_widget_queue_draw(view_.get());
+}
+
+void RenderWidgetHostViewGtk::set_last_mouse_down(GdkEventButton* event) {
+ GdkEventButton* temp = NULL;
+ if (event) {
+ temp = reinterpret_cast<GdkEventButton*>(
+ gdk_event_copy(reinterpret_cast<GdkEvent*>(event)));
+ }
+
+ if (last_mouse_down_)
+ gdk_event_free(reinterpret_cast<GdkEvent*>(last_mouse_down_));
+
+ last_mouse_down_ = temp;
+}
+
+// static
+RenderWidgetHostView*
+ RenderWidgetHostView::GetRenderWidgetHostViewFromNativeView(
+ gfx::NativeView widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget),
+ kRenderWidgetHostViewKey);
+ return reinterpret_cast<RenderWidgetHostView*>(user_data);
+}
diff --git a/content/browser/renderer_host/render_widget_host_view_gtk.h b/content/browser/renderer_host/render_widget_host_view_gtk.h
new file mode 100644
index 0000000..c857651
--- /dev/null
+++ b/content/browser/renderer_host/render_widget_host_view_gtk.h
@@ -0,0 +1,273 @@
+// Copyright (c) 2011 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 CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
+#define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
+#pragma once
+
+#include <gdk/gdk.h>
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "content/browser/renderer_host/render_widget_host_view.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "ui/base/animation/slide_animation.h"
+#include "ui/base/gtk/gtk_signal.h"
+#include "ui/base/gtk/owned_widget_gtk.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/rect.h"
+#include "webkit/glue/webcursor.h"
+#include "webkit/plugins/npapi/gtk_plugin_container_manager.h"
+
+class RenderWidgetHost;
+class GtkIMContextWrapper;
+struct NativeWebKeyboardEvent;
+
+#if defined(OS_CHROMEOS)
+namespace views {
+class TooltipWindowGtk;
+}
+#else
+class GtkKeyBindingsHandler;
+#endif // defined(OS_CHROMEOS)
+
+typedef struct _GtkClipboard GtkClipboard;
+typedef struct _GtkSelectionData GtkSelectionData;
+
+// -----------------------------------------------------------------------------
+// See comments in render_widget_host_view.h about this class and its members.
+// -----------------------------------------------------------------------------
+class RenderWidgetHostViewGtk : public RenderWidgetHostView,
+ public ui::AnimationDelegate {
+ public:
+ explicit RenderWidgetHostViewGtk(RenderWidgetHost* widget);
+ virtual ~RenderWidgetHostViewGtk();
+
+ // Initialize this object for use as a drawing area.
+ void InitAsChild();
+
+ // RenderWidgetHostView implementation.
+ virtual void InitAsPopup(RenderWidgetHostView* parent_host_view,
+ const gfx::Rect& pos) OVERRIDE;
+ virtual void InitAsFullscreen(
+ RenderWidgetHostView* reference_host_view) OVERRIDE;
+ virtual RenderWidgetHost* GetRenderWidgetHost() const OVERRIDE;
+ virtual void DidBecomeSelected() OVERRIDE;
+ virtual void WasHidden() OVERRIDE;
+ virtual void SetSize(const gfx::Size& size) OVERRIDE;
+ virtual void SetBounds(const gfx::Rect& rect) OVERRIDE;
+ virtual gfx::NativeView GetNativeView() OVERRIDE;
+ virtual void MovePluginWindows(
+ const std::vector<webkit::npapi::WebPluginGeometry>& moves) OVERRIDE;
+ virtual void Focus() OVERRIDE;
+ virtual void Blur() OVERRIDE;
+ virtual bool HasFocus() OVERRIDE;
+ virtual void Show() OVERRIDE;
+ virtual void Hide() OVERRIDE;
+ virtual bool IsShowing() OVERRIDE;
+ virtual gfx::Rect GetViewBounds() const OVERRIDE;
+ virtual void UpdateCursor(const WebCursor& cursor) OVERRIDE;
+ virtual void SetIsLoading(bool is_loading) OVERRIDE;
+ virtual void ImeUpdateTextInputState(ui::TextInputType type,
+ bool can_compose_inline,
+ const gfx::Rect& caret_rect) OVERRIDE;
+ virtual void ImeCancelComposition() OVERRIDE;
+ virtual void DidUpdateBackingStore(
+ const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy,
+ const std::vector<gfx::Rect>& copy_rects) OVERRIDE;
+ virtual void RenderViewGone(base::TerminationStatus status,
+ int error_code) OVERRIDE;
+ virtual void Destroy() OVERRIDE;
+ virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {}
+ virtual void SetTooltipText(const std::wstring& tooltip_text) OVERRIDE;
+ virtual void SelectionChanged(const std::string& text,
+ const ui::Range& range,
+ const gfx::Point& start,
+ const gfx::Point& end) OVERRIDE;
+ virtual void ShowingContextMenu(bool showing) OVERRIDE;
+ virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE;
+ virtual void SetBackground(const SkBitmap& background) OVERRIDE;
+ virtual void CreatePluginContainer(gfx::PluginWindowHandle id) OVERRIDE;
+ virtual void DestroyPluginContainer(gfx::PluginWindowHandle id) OVERRIDE;
+ virtual void SetVisuallyDeemphasized(const SkColor* color,
+ bool animate) OVERRIDE;
+ virtual void UnhandledWheelEvent(
+ const WebKit::WebMouseWheelEvent& event) OVERRIDE;
+ virtual void SetHasHorizontalScrollbar(
+ bool has_horizontal_scrollbar) OVERRIDE;
+ virtual void SetScrollOffsetPinning(
+ bool is_pinned_to_left, bool is_pinned_to_right) OVERRIDE;
+ virtual void AcceleratedCompositingActivated(bool activated) OVERRIDE;
+ virtual gfx::PluginWindowHandle GetCompositingSurface() OVERRIDE;
+
+ // ui::AnimationDelegate implementation.
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationCanceled(const ui::Animation* animation) OVERRIDE;
+
+ gfx::NativeView native_view() const { return view_.get(); }
+
+ // If the widget is aligned with an edge of the monitor its on and the user
+ // attempts to drag past that edge we track the number of times it has
+ // occurred, so that we can force the widget to scroll when it otherwise
+ // would be unable to.
+ void ModifyEventForEdgeDragging(GtkWidget* widget, GdkEventMotion* event);
+ void Paint(const gfx::Rect&);
+
+ // Called by GtkIMContextWrapper to forward a keyboard event to renderer.
+ // On Linux (not ChromeOS):
+ // 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);
+
+ GdkEventButton* last_mouse_down() const {
+ return last_mouse_down_;
+ }
+
+#if !defined(TOOLKIT_VIEWS)
+ // Builds a submenu containing all the gtk input method commands.
+ GtkWidget* BuildInputMethodsGtkMenu();
+#endif
+
+ private:
+ friend class RenderWidgetHostViewGtkWidget;
+
+ CHROMEGTK_CALLBACK_1(RenderWidgetHostViewGtk,
+ gboolean,
+ OnWindowStateEvent,
+ GdkEventWindowState*);
+
+ CHROMEGTK_CALLBACK_0(RenderWidgetHostViewGtk,
+ void,
+ OnDestroy);
+
+ // Returns whether the widget needs an input grab (GTK+ and X) to work
+ // properly.
+ bool NeedsInputGrab();
+
+ // Returns whether this render view is a popup (<select> dropdown or
+ // autocomplete window).
+ bool IsPopup() const;
+
+ // Do initialization needed by all InitAs*() methods.
+ void DoSharedInit();
+
+ // Do initialization needed just by InitAsPopup() and InitAsFullscreen().
+ // We move and resize |window| to |bounds| and show it and its contents.
+ void DoPopupOrFullscreenInit(GtkWindow* window, const gfx::Rect& bounds);
+
+ // Update the display cursor for the render view.
+ void ShowCurrentCursor();
+
+ void set_last_mouse_down(GdkEventButton* event);
+
+ // The model object.
+ RenderWidgetHost* host_;
+
+ // The native UI widget.
+ OwnedWidgetGtk view_;
+
+ // This is true when we are currently painting and thus should handle extra
+ // paint requests by expanding the invalid rect rather than actually
+ // painting.
+ bool about_to_validate_and_paint_;
+
+ // This is the rectangle which we'll paint.
+ gfx::Rect invalid_rect_;
+
+ // Whether or not this widget is hidden.
+ bool is_hidden_;
+
+ // Whether we are currently loading.
+ bool is_loading_;
+
+ // The cursor for the page. This is passed up from the renderer.
+ WebCursor current_cursor_;
+
+ // Whether we are showing a context menu.
+ bool is_showing_context_menu_;
+
+ // The time at which this view started displaying white pixels as a result of
+ // not having anything to paint (empty backing store from renderer). This
+ // value returns true for is_null() if we are not recording whiteout times.
+ base::TimeTicks whiteout_start_time_;
+
+ // The time it took after this view was selected for it to be fully painted.
+ base::TimeTicks tab_switch_paint_time_;
+
+ // A color we use to shade the entire render view. If 100% transparent, we do
+ // not shade the render view.
+ SkColor overlay_color_;
+
+ // The animation used for the abovementioned shade effect. The animation's
+ // value affects the alpha we use for |overlay_color_|.
+ ui::SlideAnimation overlay_animation_;
+
+ // The native view of our parent widget. Used only for popups.
+ GtkWidget* parent_;
+
+ // We ignore the first mouse release on popups so the popup will remain open.
+ bool is_popup_first_mouse_release_;
+
+ // Whether or not this widget's input context was focused before being
+ // shadowed by another widget. Used in OnGrabNotify() handler to track the
+ // focused state correctly.
+ bool was_imcontext_focused_before_grab_;
+
+ // True if we are responsible for creating an X grab. This will only be used
+ // for <select> dropdowns. It should be true for most such cases, but false
+ // for extension popups.
+ bool do_x_grab_;
+
+ // Is the widget fullscreen?
+ bool is_fullscreen_;
+
+ // For full-screen windows we have a OnDestroy handler that we need to remove,
+ // so we keep it ID here.
+ unsigned long destroy_handler_id_;
+
+ // A convenience wrapper object for GtkIMContext;
+ scoped_ptr<GtkIMContextWrapper> im_context_;
+
+#if !defined(OS_CHROMEOS)
+ // A convenience object for handling editor key bindings defined in gtk
+ // keyboard theme.
+ scoped_ptr<GtkKeyBindingsHandler> key_bindings_handler_;
+#endif
+
+ // Helper class that lets us allocate plugin containers and move them.
+ webkit::npapi::GtkPluginContainerManager plugin_container_manager_;
+
+ // The size that we want the renderer to be. We keep this in a separate
+ // variable because resizing in GTK+ is async.
+ gfx::Size requested_size_;
+
+ // The number of times the user has dragged against horizontal edge of the
+ // monitor (if the widget is aligned with that edge). Negative values
+ // indicate the left edge, positive the right.
+ int dragged_at_horizontal_edge_;
+
+ // The number of times the user has dragged against vertical edge of the
+ // monitor (if the widget is aligned with that edge). Negative values
+ // indicate the top edge, positive the bottom.
+ int dragged_at_vertical_edge_;
+
+ gfx::PluginWindowHandle compositing_surface_;
+
+ // The event for the last mouse down we handled. We need this for context
+ // menus and drags.
+ GdkEventButton* last_mouse_down_;
+
+#if defined(OS_CHROMEOS)
+ // Custimized tooltip window.
+ scoped_ptr<views::TooltipWindowGtk> tooltip_window_;
+#endif // defined(OS_CHROMEOS)
+};
+
+#endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_
diff --git a/content/common/content_paths.cc b/content/common/content_paths.cc
index 4e9b32b..2664349 100644
--- a/content/common/content_paths.cc
+++ b/content/common/content_paths.cc
@@ -4,6 +4,7 @@
#include "content/common/content_paths.h"
+#include "base/file_util.h"
#include "base/path_service.h"
namespace content {
@@ -12,8 +13,22 @@ bool PathProvider(int key, FilePath* result) {
switch (key) {
case CHILD_PROCESS_EXE:
return PathService::Get(base::FILE_EXE, result);
- default:
+ case DIR_TEST_DATA: {
+ FilePath cur;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur))
+ return false;
+ cur = cur.Append(FILE_PATH_LITERAL("content"));
+ cur = cur.Append(FILE_PATH_LITERAL("test"));
+ cur = cur.Append(FILE_PATH_LITERAL("data"));
+ if (!file_util::PathExists(cur)) // we don't want to create this
+ return false;
+
+ *result = cur;
+ return true;
break;
+ }
+ default:
+ return false;
}
return false;
diff --git a/content/common/content_paths.h b/content/common/content_paths.h
index 49720a0..cdf07a7 100644
--- a/content/common/content_paths.h
+++ b/content/common/content_paths.h
@@ -17,6 +17,9 @@ enum {
// Path and filename to the executable to use for child processes.
CHILD_PROCESS_EXE = PATH_START,
+ // Valid only in development environment
+ DIR_TEST_DATA,
+
PATH_END
};
diff --git a/content/common/content_switches.cc b/content/common/content_switches.cc
index 20dca2a..4058d4e 100644
--- a/content/common/content_switches.cc
+++ b/content/common/content_switches.cc
@@ -471,6 +471,11 @@ const char kAuditHandles[] = "enable-handle-auditing";
const char kAuditAllHandles[] = "enable-handle-auditing-all";
#endif
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+// Specify the amount the trackpad should scroll by.
+const char kScrollPixels[] = "scroll-pixels";
+#endif
+
#if !defined(OFFICIAL_BUILD)
// Causes the renderer process to throw an assertion on launch.
const char kRendererCheckFalseTest[] = "renderer-check-false-test";
diff --git a/content/common/content_switches.h b/content/common/content_switches.h
index a61a35b..adc93dd 100644
--- a/content/common/content_switches.h
+++ b/content/common/content_switches.h
@@ -152,6 +152,10 @@ extern const char kAuditHandles[];
extern const char kAuditAllHandles[];
#endif
+#if defined(OS_POSIX) && !defined(OS_MACOSX)
+extern const char kScrollPixels[];
+#endif
+
#if !defined(OFFICIAL_BUILD)
extern const char kRendererCheckFalseTest[];
#endif
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index c4ac49d..b96735b 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -322,6 +322,10 @@
'browser/renderer_host/global_request_id.h',
'browser/renderer_host/gpu_message_filter.cc',
'browser/renderer_host/gpu_message_filter.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/media/audio_common.cc',
'browser/renderer_host/media/audio_common.h',
'browser/renderer_host/media/audio_input_device_manager.cc',
@@ -387,6 +391,8 @@
'browser/renderer_host/render_widget_host_mac.cc',
'browser/renderer_host/render_widget_host_view.cc',
'browser/renderer_host/render_widget_host_view.h',
+ 'browser/renderer_host/render_widget_host_view_gtk.cc',
+ 'browser/renderer_host/render_widget_host_view_gtk.h',
'browser/renderer_host/resource_dispatcher_host.cc',
'browser/renderer_host/resource_dispatcher_host.h',
'browser/renderer_host/resource_dispatcher_host_delegate.h',
@@ -566,6 +572,20 @@
'../sandbox/sandbox.gyp:sandbox',
],
}],
+ ['chromeos==1', {
+ 'sources!': [
+ 'browser/renderer_host/gtk_key_bindings_handler.cc',
+ 'browser/renderer_host/gtk_key_bindings_handler.h',
+ ],
+ }],
+ ['touchui==1', {
+ 'sources/': [
+ ['exclude', '^browser/renderer_host/gtk_im_context_wrapper.cc'],
+ ['exclude', '^browser/renderer_host/gtk_im_context_wrapper.h'],
+ ['exclude', '^browser/renderer_host/render_widget_host_view_gtk.cc'],
+ ['exclude', '^browser/renderer_host/render_widget_host_view_gtk.h'],
+ ],
+ }],
],
},
],
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 1c59e934..b06c848 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -50,6 +50,7 @@
'sources': [
'browser/browser_thread_unittest.cc',
'browser/child_process_security_policy_unittest.cc',
+ 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc',
'browser/ssl/ssl_host_state_unittest.cc',
'browser/trace_subscriber_stdio_unittest.cc',
'common/process_watcher_unittest.cc',
@@ -79,6 +80,11 @@
'../base/allocator/allocator.gyp:allocator',
],
}],
+ ['chromeos==1', {
+ 'sources/': [
+ ['exclude', '^browser/renderer_host/gtk_key_bindings_handler_unittest.cc'],
+ ],
+ }],
],
},
],
diff --git a/content/test/content_test_suite.cc b/content/test/content_test_suite.cc
index 2cfc1e5..a6ebf01 100644
--- a/content/test/content_test_suite.cc
+++ b/content/test/content_test_suite.cc
@@ -7,8 +7,10 @@
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "content/common/content_client.h"
+#include "content/common/content_paths.h"
#include "content/test/test_content_client.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_paths.h"
namespace {
@@ -47,6 +49,9 @@ ContentTestSuite::~ContentTestSuite() {
void ContentTestSuite::Initialize() {
base::TestSuite::Initialize();
+ content::RegisterPathProvider();
+ ui::RegisterPathProvider();
+
testing::TestEventListeners& listeners =
testing::UnitTest::GetInstance()->listeners();
listeners.Append(new TestContentClientInitializer);
diff --git a/content/test/data/gtk_key_bindings_test_gtkrc b/content/test/data/gtk_key_bindings_test_gtkrc
new file mode 100644
index 0000000..812971c
--- /dev/null
+++ b/content/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"