diff options
author | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-23 18:10:18 +0000 |
---|---|---|
committer | erg@google.com <erg@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-23 18:10:18 +0000 |
commit | 3c56b38d2055b16592d499ea4933c27cbb0fd809 (patch) | |
tree | 82c593ce4c84e4d0c538d10cd89da3f001ac67c7 /content | |
parent | 03eb9d27a79f876a039ca30eba54d1155ab5500b (diff) | |
download | chromium_src-3c56b38d2055b16592d499ea4933c27cbb0fd809.zip chromium_src-3c56b38d2055b16592d499ea4933c27cbb0fd809.tar.gz chromium_src-3c56b38d2055b16592d499ea4933c27cbb0fd809.tar.bz2 |
content: Move render_widget_host_view_gtk to content/ try 2.
This was r97750, which was reverted at r97756. This patch, in addition to all
the other things it did, marks OwnedWidgetGtk for export and moves it to the
ui:: namespace.
Original list of things this patch does:
- 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
Original Review URL: http://codereview.chromium.org/7669040
Review URL: http://codereview.chromium.org/7708021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97889 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r-- | content/browser/renderer_host/gtk_im_context_wrapper.cc | 619 | ||||
-rw-r--r-- | content/browser/renderer_host/gtk_im_context_wrapper.h | 197 | ||||
-rw-r--r-- | content/browser/renderer_host/gtk_key_bindings_handler.cc | 325 | ||||
-rw-r--r-- | content/browser/renderer_host/gtk_key_bindings_handler.h | 128 | ||||
-rw-r--r-- | content/browser/renderer_host/gtk_key_bindings_handler_unittest.cc | 222 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_view_gtk.cc | 1210 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_view_gtk.h | 273 | ||||
-rw-r--r-- | content/common/content_paths.cc | 17 | ||||
-rw-r--r-- | content/common/content_paths.h | 3 | ||||
-rw-r--r-- | content/common/content_switches.cc | 5 | ||||
-rw-r--r-- | content/common/content_switches.h | 4 | ||||
-rw-r--r-- | content/content_browser.gypi | 20 | ||||
-rw-r--r-- | content/content_tests.gypi | 7 | ||||
-rw-r--r-- | content/test/content_test_suite.cc | 5 | ||||
-rw-r--r-- | content/test/data/gtk_key_bindings_test_gtkrc | 83 |
15 files changed, 3117 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..9b7d6dc --- /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); + + ui::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, >krc); + 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..5dbac2e --- /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. + ui::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 6231386..0f554c6 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -324,6 +324,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', @@ -388,6 +392,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', @@ -567,6 +573,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 99636f6..e5ddb36 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -65,6 +65,7 @@ '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', '../third_party/libjingle/libjingle.gyp:libjingle', + '../ui/ui.gyp:ui', ], 'include_dirs': [ '..', @@ -79,6 +80,7 @@ 'browser/download/download_status_updater_unittest.cc', 'browser/geolocation/gateway_data_provider_common_unittest.cc', 'browser/gpu/gpu_blacklist_unittest.cc', + 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc', 'browser/renderer_host/media/audio_input_device_manager_unittest.cc', 'browser/renderer_host/media/audio_renderer_host_unittest.cc', 'browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc', @@ -113,6 +115,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 3777570..0f0ea29 100644 --- a/content/test/content_test_suite.cc +++ b/content/test/content_test_suite.cc @@ -8,9 +8,11 @@ #include "base/memory/scoped_ptr.h" #include "content/browser/mock_content_browser_client.h" #include "content/common/content_client.h" +#include "content/common/content_paths.h" #include "content/common/notification_service.h" #include "content/test/test_content_client.h" #include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/ui_base_paths.h" namespace { @@ -60,6 +62,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" |